Moving files around

This commit is contained in:
Erlend Hamnaberg
2009-11-06 21:26:32 +01:00
parent ba5f0a2f5f
commit 9b615de8ed
86 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,612 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.lang;
import com.twelvemonkeys.util.convert.ConversionException;
import com.twelvemonkeys.util.convert.Converter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.Arrays;
/**
* A utility class with some useful bean-related functions.
* <p/>
* <em>NOTE: This class is not considered part of the public API and may be
* changed without notice</em>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
*
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/BeanUtil.java#2 $
*/
public final class BeanUtil {
// Disallow creating objects of this type
private BeanUtil() {
}
/**
* Gets a property value from the given object, using reflection.
* Now supports getting values from properties of properties
* (recursive).
*
* @param pObject The object to get the property from
* @param pProperty The name of the property
*
* @return A string containing the value of the given property, or null
* if it can not be found.
* @todo Remove System.err's... Create new Exception? Hmm..
*/
public static Object getPropertyValue(Object pObject, String pProperty) {
//
// TODO: Support get(Object) method of Collections!
// Handle lists and arrays with [] (index) operator
//
if (pObject == null || pProperty == null || pProperty.length() < 1) {
return null;
}
Class objClass = pObject.getClass();
Object result = pObject;
// Method for method...
String subProp;
int begIdx = 0;
int endIdx = begIdx;
while (begIdx < pProperty.length() && begIdx >= 0) {
endIdx = pProperty.indexOf(".", endIdx + 1);
if (endIdx > 0) {
subProp = pProperty.substring(begIdx, endIdx);
begIdx = endIdx + 1;
}
else {
// The final property!
// If there's just the first-level property, subProp will be
// equal to property
subProp = pProperty.substring(begIdx);
begIdx = -1;
}
// Check for "[" and "]"
Object[] param = null;
Class[] paramClass = new Class[0];
int begBracket;
if ((begBracket = subProp.indexOf("[")) > 0) {
// An error if there is no matching bracket
if (!subProp.endsWith("]")) {
return null;
}
String between = subProp.substring(begBracket + 1,
subProp.length() - 1);
subProp = subProp.substring(0, begBracket);
// If brackets exist, check type of argument between brackets
param = new Object[1];
paramClass = new Class[1];
//try {
// TODO: isNumber returns true, even if too big for integer...
if (StringUtil.isNumber(between)) {
// We have a number
// Integer -> array subscript -> getXXX(int i)
try {
// Insert param and it's Class
param[0] = Integer.valueOf(between);
paramClass[0] = Integer.TYPE; // int.class
}
catch (NumberFormatException e) {
// ??
// Probably too small or too large value..
}
}
else {
//catch (NumberFormatException e) {
// Not a number... Try String
// String -> Hashtable key -> getXXX(String str)
// Insert param and it's Class
param[0] = between.toLowerCase();
paramClass[0] = String.class;
}
}
Method method;
String methodName = "get" + StringUtil.capitalize(subProp);
try {
// Try to get the "get" method for the given property
method = objClass.getMethod(methodName, paramClass);
}
catch (NoSuchMethodException e) {
System.err.print("No method named \"" + methodName + "()\"");
// The array might be of size 0...
if (paramClass != null && paramClass.length > 0) {
System.err.print(" with the parameter "
+ paramClass[0].getName());
}
System.err.println(" in class " + objClass.getName() + "!");
return null;
}
// If method for some reason should be null, give up
if (method == null) {
return null;
}
try {
// We have a method, try to invoke it
// The resutling object will be either the property we are
// Looking for, or the parent
// System.err.println("Trying " + objClass.getName() + "." + method.getName() + "(" + ((param != null && param.length > 0) ? param[0] : "") + ")");
result = method.invoke(result, param);
}
catch (InvocationTargetException e) {
System.err.println("property=" + pProperty + " & result="
+ result + " & param=" + Arrays.toString(param));
e.getTargetException().printStackTrace();
e.printStackTrace();
return null;
}
catch (IllegalAccessException e) {
e.printStackTrace();
return null;
}
catch (NullPointerException e) {
System.err.println(objClass.getName() + "." + method.getName()
+ "(" + ((paramClass != null && paramClass.length > 0) ? paramClass[0].getName() : "") + ")");
e.printStackTrace();
return null;
}
if (result != null) {
// Get the class of the reulting object
objClass = result.getClass();
}
else {
return null;
}
} // while
return result;
}
/**
* Sets the property value to an object using reflection.
* Supports setting values of properties that are properties of
* properties (recursive).
*
* @param pObject The object to get a property from
* @param pProperty The name of the property
* @param pValue The property value
*
* @throws NoSuchMethodException if there's no write method for the
* given property
* @throws InvocationTargetException if invoking the write method failed
* @throws IllegalAccessException if the caller class has no access to the
* write method
*/
public static void setPropertyValue(Object pObject, String pProperty,
Object pValue)
throws NoSuchMethodException, InvocationTargetException,
IllegalAccessException {
//
// TODO: Support set(Object, Object)/put(Object, Object) methods
// of Collections!
// Handle lists and arrays with [] (index) operator
Class paramType = pValue != null ? pValue.getClass() : Object.class;
// Preserve references
Object obj = pObject;
String property = pProperty;
// Recurse and find real parent if property contains a '.'
int dotIdx = property.indexOf('.');
if (dotIdx >= 0) {
// Get real parent
obj = getPropertyValue(obj, property.substring(0, dotIdx));
// Get the property of the parent
property = property.substring(dotIdx + 1);
}
// Find method
Object[] params = {pValue};
Method method = getMethodMayModifyParams(obj, "set" + StringUtil.capitalize(property),
new Class[] {paramType}, params);
// Invoke it
method.invoke(obj, params);
}
private static Method getMethodMayModifyParams(Object pObject, String pName, Class[] pParams, Object[] pValues) throws NoSuchMethodException {
// NOTE: This method assumes pParams.length == 1 && pValues.length == 1
Method method = null;
Class paramType = pParams[0];
try {
method = pObject.getClass().getMethod(pName, pParams);
}
catch (NoSuchMethodException e) {
// No direct match
// 1: If primitive wrapper, try unwrap conversion first
/*if (paramType.isPrimitive()) { // NOTE: Can't be primitive type
params[0] = ReflectUtil.wrapType(paramType);
}
else*/ if (ReflectUtil.isPrimitiveWrapper(paramType)) {
pParams[0] = ReflectUtil.unwrapType(paramType);
}
try {
// If this does not throw an excption, it works
method = pObject.getClass().getMethod(pName, pParams);
}
catch (Throwable t) {
// Ignore
}
// 2: Try any supertypes of paramType, to see if we have a match
if (method == null) {
while ((paramType = paramType.getSuperclass()) != null) {
pParams[0] = paramType;
try {
// If this does not throw an excption, it works
method = pObject.getClass().getMethod(pName, pParams);
}
catch (Throwable t) {
// Ignore/Continue
continue;
}
break;
}
}
// 3: Try to find a different method with the same name, that has
// a parameter type we can convert to...
// NOTE: There's no ordering here..
// TODO: Should we try to do that? What would the ordering be?
if (method == null) {
Method[] methods = pObject.getClass().getMethods();
for (Method candidate : methods) {
if (Modifier.isPublic(candidate.getModifiers())
&& candidate.getName().equals(pName)
&& candidate.getReturnType() == Void.TYPE
&& candidate.getParameterTypes().length == 1) {
// NOTE: Assumes paramTypes.length == 1
Class type = candidate.getParameterTypes()[0];
try {
pValues[0] = convertValueToType(pValues[0], type);
}
catch (Throwable t) {
continue;
}
// We were able to convert the parameter, let's try
method = candidate;
break;
}
}
}
// Give up...
if (method == null) {
throw e;
}
}
return method;
}
private static Object convertValueToType(Object pValue, Class pType) throws ConversionException {
if (pType.isPrimitive()) {
if (pType == Boolean.TYPE && pValue instanceof Boolean) {
return pValue;
}
else if (pType == Byte.TYPE && pValue instanceof Byte) {
return pValue;
}
else if (pType == Character.TYPE && pValue instanceof Character) {
return pValue;
}
else if (pType == Double.TYPE && pValue instanceof Double) {
return pValue;
}
else if (pType == Float.TYPE && pValue instanceof Float) {
return pValue;
}
else if (pType == Integer.TYPE && pValue instanceof Integer) {
return pValue;
}
else if (pType == Long.TYPE && pValue instanceof Long) {
return pValue;
}
else if (pType == Short.TYPE && pValue instanceof Short) {
return pValue;
}
}
// TODO: Convert other types
if (pValue instanceof String) {
Converter converter = Converter.getInstance();
return converter.toObject((String) pValue, pType);
}
else if (pType == String.class) {
Converter converter = Converter.getInstance();
return converter.toString(pValue);
}
else {
throw new ConversionException("Cannot convert " + pValue.getClass().getName() + " to " + pType.getName());
}
}
/**
* Creates an object from the given class' single argument constructor.
*
* @param pClass The class to create instance from
* @param pParam The parameters to the constructor
*
* @return The object created from the constructor.
* If the constructor could not be invoked for any reason, null is
* returned.
*
* @throws InvocationTargetException if the constructor failed
*/
// TODO: Move to ReflectUtil
public static Object createInstance(Class pClass, Object pParam)
throws InvocationTargetException {
return createInstance(pClass, new Object[] {pParam});
}
/**
* Creates an object from the given class' constructor that matches
* the given paramaters.
*
* @param pClass The class to create instance from
* @param pParams The parameters to the constructor
*
* @return The object created from the constructor.
* If the constructor could not be invoked for any reason, null is
* returned.
*
* @throws InvocationTargetException if the constructor failed
*/
// TODO: Move to ReflectUtil
public static Object createInstance(Class pClass, Object... pParams)
throws InvocationTargetException {
Object value;
try {
// Create param and argument arrays
Class[] paramTypes = null;
if (pParams != null && pParams.length > 0) {
paramTypes = new Class[pParams.length];
for (int i = 0; i < pParams.length; i++) {
paramTypes[i] = pParams[i].getClass();
}
}
// Get constructor
//Constructor constructor = pClass.getDeclaredConstructor(paramTypes);
Constructor constructor = pClass.getConstructor(paramTypes);
// Invoke and create instance
value = constructor.newInstance(pParams);
}
/* All this to let InvocationTargetException pass on */
catch (NoSuchMethodException nsme) {
return null;
}
catch (IllegalAccessException iae) {
return null;
}
catch (IllegalArgumentException iarge) {
return null;
}
catch (InstantiationException ie) {
return null;
}
catch (ExceptionInInitializerError err) {
return null;
}
return value;
}
/**
* Gets an object from any given static method, with the given parameter.
*
* @param pClass The class to invoke method on
* @param pMethod The name of the method to invoke
* @param pParam The parameter to the method
*
* @return The object returned by the static method.
* If the return type of the method is a primitive type, it is wrapped in
* the corresponding wrapper object (int is wrapped in an Integer).
* If the return type of the method is void, null is returned.
* If the method could not be invoked for any reason, null is returned.
*
* @throws InvocationTargetException if the invocaton failed
*/
// TODO: Move to ReflectUtil
// TODO: Rename to invokeStatic?
public static Object invokeStaticMethod(Class pClass, String pMethod,
Object pParam)
throws InvocationTargetException {
return invokeStaticMethod(pClass, pMethod, new Object[] {pParam});
}
/**
* Gets an object from any given static method, with the given parameter.
*
* @param pClass The class to invoke method on
* @param pMethod The name of the method to invoke
* @param pParams The parameters to the method
*
* @return The object returned by the static method.
* If the return type of the method is a primitive type, it is wrapped in
* the corresponding wrapper object (int is wrapped in an Integer).
* If the return type of the method is void, null is returned.
* If the method could not be invoked for any reason, null is returned.
*
* @throws InvocationTargetException if the invocaton failed
*/
// TODO: Move to ReflectUtil
// TODO: Rename to invokeStatic?
public static Object invokeStaticMethod(Class pClass, String pMethod,
Object[] pParams)
throws InvocationTargetException {
Object value = null;
try {
// Create param and argument arrays
Class[] paramTypes = new Class[pParams.length];
for (int i = 0; i < pParams.length; i++) {
paramTypes[i] = pParams[i].getClass();
}
// Get method
// *** If more than one such method is found in the class, and one
// of these methods has a RETURN TYPE that is more specific than
// any of the others, that method is reflected; otherwise one of
// the methods is chosen ARBITRARILY.
// java/lang/Class.html#getMethod(java.lang.String, java.lang.Class[])
Method method = pClass.getMethod(pMethod, paramTypes);
// Invoke public static method
if (Modifier.isPublic(method.getModifiers())
&& Modifier.isStatic(method.getModifiers())) {
value = method.invoke(null, pParams);
}
}
/* All this to let InvocationTargetException pass on */
catch (NoSuchMethodException nsme) {
return null;
}
catch (IllegalAccessException iae) {
return null;
}
catch (IllegalArgumentException iarge) {
return null;
}
return value;
}
/**
* Configures the bean according to the given mapping.
* For each {@code Map.Entry} in {@code Map.values()},
* a method named
* {@code set + capitalize(entry.getKey())} is called on the bean,
* with {@code entry.getValue()} as its argument.
* <p/>
* Properties that has no matching set-method in the bean, are simply
* discarded.
*
* @param pBean The bean to configure
* @param pMapping The mapping for the bean
*
* @throws NullPointerException if any of the parameters are null.
* @throws InvocationTargetException if an error occurs when invoking the
* setter-method.
*/
// TODO: Add a version that takes a ConfigurationErrorListener callback interface
// TODO: ...or a boolean pFailOnError parameter
// TODO: ...or return Exceptions as an array?!
// TODO: ...or something whatsoever that makes clients able to determine something's not right
public static void configure(final Object pBean, final Map<String, ?> pMapping) throws InvocationTargetException {
configure(pBean, pMapping, false);
}
/**
* Configures the bean according to the given mapping.
* For each {@code Map.Entry} in {@code Map.values()},
* a method named
* {@code set + capitalize(entry.getKey())} is called on the bean,
* with {@code entry.getValue()} as its argument.
* <p/>
* Optionally, lisp-style names are allowed, and automatically converted
* to Java-style camel-case names.
* <p/>
* Properties that has no matching set-method in the bean, are simply
* discarded.
*
* @see StringUtil#lispToCamel(String)
*
* @param pBean The bean to configure
* @param pMapping The mapping for the bean
* @param pLispToCamel Allow lisp-style names, and automatically convert
* them to Java-style camel-case.
*
* @throws NullPointerException if any of the parameters are null.
* @throws InvocationTargetException if an error occurs when invoking the
* setter-method.
*/
public static void configure(final Object pBean, final Map<String, ?> pMapping, final boolean pLispToCamel) throws InvocationTargetException {
// Loop over properties in mapping
for (final Map.Entry<String, ?> entry : pMapping.entrySet()) {
try {
// Configure each property in turn
final String property = StringUtil.valueOf(entry.getKey());
try {
setPropertyValue(pBean, property, entry.getValue());
}
catch (NoSuchMethodException ignore) {
// If invocation failed, convert lisp-style and try again
if (pLispToCamel && property.indexOf('-') > 0) {
setPropertyValue(pBean, StringUtil.lispToCamel(property, false),
entry.getValue());
}
}
}
catch (NoSuchMethodException nsme) {
// This property was not configured
}
catch (IllegalAccessException iae) {
// This property was not configured
}
}
}
}

View File

@@ -0,0 +1,210 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.lang;
import java.util.Date;
import java.util.TimeZone;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.text.ParseException;
/**
* A utility class with useful date manipulation methods and constants.
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/DateUtil.java#1 $
*/
public final class DateUtil {
/** One second: 1000 milliseconds. */
public static final long SECOND = 1000l;
/** One minute: 60 seconds (60 000 milliseconds). */
public static final long MINUTE = 60l * SECOND;
/**
* One hour: 60 minutes (3 600 000 milliseconds).
* 60 minutes = 3 600 seconds = 3 600 000 milliseconds
*/
public static final long HOUR = 60l * MINUTE;
/**
* One day: 24 hours (86 400 000 milliseconds).
* 24 hours = 1 440 minutes = 86 400 seconds = 86 400 000 milliseconds.
*/
public static final long DAY = 24l * HOUR;
/**
* One calendar year: 365.2425 days (31556952000 milliseconds).
* 365.2425 days = 8765.82 hours = 525949.2 minutes = 31556952 seconds
* = 31556952000 milliseconds.
*/
public static final long CALENDAR_YEAR = 3652425l * 24l * 60l * 6l;
private DateUtil() {
}
/**
* Returns the time between the given start time and now (as defined by
* {@link System#currentTimeMillis()}).
*
* @param pStart the start time
*
* @return the time between the given start time and now.
*/
public static long delta(long pStart) {
return System.currentTimeMillis() - pStart;
}
/**
* Returns the time between the given start time and now (as defined by
* {@link System#currentTimeMillis()}).
*
* @param pStart the start time
*
* @return the time between the given start time and now.
*/
public static long delta(Date pStart) {
return System.currentTimeMillis() - pStart.getTime();
}
/**
* Gets the current time, rounded down to the closest second.
* Equivalent to invoking
* {@code roundToSecond(System.currentTimeMillis())}.
*
* @return the current time, rounded to the closest second.
*/
public static long currentTimeSecond() {
return roundToSecond(System.currentTimeMillis());
}
/**
* Gets the current time, rounded down to the closest minute.
* Equivalent to invoking
* {@code roundToMinute(System.currentTimeMillis())}.
*
* @return the current time, rounded to the closest minute.
*/
public static long currentTimeMinute() {
return roundToMinute(System.currentTimeMillis());
}
/**
* Gets the current time, rounded down to the closest hour.
* Equivalent to invoking
* {@code roundToHour(System.currentTimeMillis())}.
*
* @return the current time, rounded to the closest hour.
*/
public static long currentTimeHour() {
return roundToHour(System.currentTimeMillis());
}
/**
* Gets the current time, rounded down to the closest day.
* Equivalent to invoking
* {@code roundToDay(System.currentTimeMillis())}.
*
* @return the current time, rounded to the closest day.
*/
public static long currentTimeDay() {
return roundToDay(System.currentTimeMillis());
}
/**
* Rounds the given time down to the closest second.
*
* @param pTime time
* @return the time rounded to the closest second.
*/
public static long roundToSecond(long pTime) {
return (pTime / SECOND) * SECOND;
}
/**
* Rounds the given time down to the closest minute.
*
* @param pTime time
* @return the time rounded to the closest minute.
*/
public static long roundToMinute(long pTime) {
return (pTime / MINUTE) * MINUTE;
}
/**
* Rounds the given time down to the closest hour, using the default timezone.
*
* @param pTime time
* @return the time rounded to the closest hour.
*/
public static long roundToHour(long pTime) {
// TODO: What if timezone offset is sub hour? Are there any? I think so...
return ((pTime / HOUR) * HOUR);
}
/**
* Rounds the given time down to the closest day, using the default timezone.
*
* @param pTime time
* @return the time rounded to the closest day.
*/
public static long roundToDay(long pTime) {
return roundToDay(pTime, TimeZone.getDefault());
}
/**
* Rounds the given time down to the closest day, using the given timezone.
*
* @param pTime time
* @param pTimeZone the timezone to use when rounding
* @return the time rounded to the closest day.
*/
public static long roundToDay(long pTime, TimeZone pTimeZone) {
int offset = pTimeZone.getOffset(pTime);
return (((pTime + offset) / DAY) * DAY) - offset;
}
public static void main(String[] pArgs) throws ParseException {
DateFormat format = new SimpleDateFormat("yyyy.MM.dd HH.mm.ss S");
long time = pArgs.length > 0 ? format.parse(pArgs[0]).getTime() : System.currentTimeMillis();
System.out.println(time + ": " + format.format(new Date(time)));
time = roundToSecond(time);
System.out.println(time + ": " + format.format(new Date(time)));
time = roundToMinute(time);
System.out.println(time + ": " + format.format(new Date(time)));
time = roundToHour(time);
System.out.println(time + ": " + format.format(new Date(time)));
time = roundToDay(time);
System.out.println(time + ": " + format.format(new Date(time)));
}
}

View File

@@ -0,0 +1,129 @@
package com.twelvemonkeys.lang;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.UndeclaredThrowableException;
import java.sql.SQLException;
/**
* ExceptionUtil
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/ExceptionUtil.java#2 $
*/
public final class ExceptionUtil {
/*public*/ static void launder(final Throwable pThrowable, Class<? extends Throwable>... pExpectedTypes) {
if (pThrowable instanceof Error) {
throw (Error) pThrowable;
}
if (pThrowable instanceof RuntimeException) {
throw (RuntimeException) pThrowable;
}
for (Class<? extends Throwable> expectedType : pExpectedTypes) {
if (expectedType.isInstance(pThrowable)) {
throw new RuntimeException(pThrowable);
}
}
throw new UndeclaredThrowableException(pThrowable);
}
@SuppressWarnings({"unchecked", "UnusedDeclaration"})
static <T extends Throwable> void throwAs(final Class<T> pType, final Throwable pThrowable) throws T {
throw (T) pThrowable;
}
public static void throwUnchecked(final Throwable pThrowable) {
throwAs(RuntimeException.class, pThrowable);
}
/*public*/ static void handle(final Throwable pThrowable, final ThrowableHandler<? extends Throwable>... pHandler) {
handleImpl(pThrowable, pHandler);
}
@SuppressWarnings({"unchecked"})
private static <T extends Throwable> void handleImpl(final Throwable pThrowable, final ThrowableHandler<T>... pHandler) {
// TODO: Sort more specific throwable handlers before less specific?
for (ThrowableHandler<T> handler : pHandler) {
if (handler.handles(pThrowable)) {
handler.handle((T) pThrowable);
return;
}
}
throwUnchecked(pThrowable);
}
public static abstract class ThrowableHandler<T extends Throwable> {
private Class<? extends T>[] mThrowables;
protected ThrowableHandler(final Class<? extends T>... pThrowables) {
// TODO: Assert not null
mThrowables = pThrowables.clone();
}
final public boolean handles(final Throwable pThrowable) {
for (Class<? extends T> throwable : mThrowables) {
if (throwable.isAssignableFrom(pThrowable.getClass())) {
return true;
}
}
return false;
}
public abstract void handle(T pThrowable);
}
@SuppressWarnings({"InfiniteLoopStatement"})
public static void main(String[] pArgs) {
while (true) {
foo();
}
}
private static void foo() {
try {
bar();
}
catch (Throwable t) {
handle(t,
new ThrowableHandler<IOException>(IOException.class) {
public void handle(final IOException pThrowable) {
System.out.println("IOException: " + pThrowable + " handled");
}
},
new ThrowableHandler<Exception>(SQLException.class, NumberFormatException.class) {
public void handle(final Exception pThrowable) {
System.out.println("Exception: " + pThrowable + " handled");
}
},
new ThrowableHandler<Throwable>(Throwable.class) {
public void handle(final Throwable pThrowable) {
System.err.println("Generic throwable: " + pThrowable + " NOT handled");
throwUnchecked(pThrowable);
}
}
);
}
}
private static void bar() {
baz();
}
@SuppressWarnings({"ThrowableInstanceNeverThrown"})
private static void baz() {
double random = Math.random();
if (random < (1.0 / 3.0)) {
throwUnchecked(new FileNotFoundException("FNF Boo"));
}
if (random < (2.0 / 3.0)) {
throwUnchecked(new SQLException("SQL Boo"));
}
else {
throwUnchecked(new Exception("Some Boo"));
}
}
}

View File

@@ -0,0 +1,168 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.lang;
/**
* The class MathUtil contains methods for performing basic numeric operations
* such as the elementary exponential, logarithm, square root, and
* trigonometric functions.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
*
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/MathUtil.java#1 $
*/
public final class MathUtil {
/** */
private MathUtil() {
}
/**
* Converts an angle measured in degrees to the equivalent angle measured
* in radians.
* This method is a replacement for the Math.toRadians() method in
* Java versions < 1.2 (typically for Applets).
*
* @param pAngDeg an angle, in degrees
* @return the measurement of the angle {@code angdeg} in radians.
*
* @see java.lang.Math#toRadians(double)
*/
public static double toRadians(final double pAngDeg) {
return pAngDeg * Math.PI / 180.0;
}
/**
* Converts an angle measured in radians to the equivalent angle measured
* in degrees.
* This method is a replacement for the Math.toDegrees() method in
* Java versions < 1.2 (typically for Applets).
*
* @param pAngRad an angle, in radians
* @return the measurement of the angle {@code angrad} in degrees.
*
* @see java.lang.Math#toDegrees(double)
*/
public static double toDegrees(final double pAngRad) {
return pAngRad * 180.0 / Math.PI;
}
/**
* Returns the natural logarithm (base <I>e</I>) of a double value.
* Equivalent to {@code java.lang.Math.log}, just with a proper name.
*
* @param pArg a number greater than 0.0.
* @return the value ln {@code pArg}, the natural logarithm of
* {@code pArg}.
*
* @see java.lang.Math#log(double)
*/
public static double ln(final double pArg) {
return Math.log(pArg);
}
private final static double LN_10 = Math.log(10);
/**
* Returns the base 10 logarithm of a double value.
*
* @param pArg a number greater than 0.0.
* @return the value log {@code pArg}, the base 10 logarithm of
* {@code pArg}.
*/
public static double log(final double pArg) {
return Math.log(pArg) / LN_10;
}
private final static double LN_2 = Math.log(10);
/**
* Returns the base 2 logarithm of a double value.
*
* @param pArg a number greater than 0.0.
* @return the value log<SUB>2</SUB> {@code pArg}, the base 2
* logarithm of {@code pArg}.
*/
public static double log2(final double pArg) {
return Math.log(pArg) / LN_2;
}
/**
* Returns the base <i>N</i> logarithm of a double value, for a given base
* <i>N</i>.
*
* @param pArg a number greater than 0.0.
* @param pBase a number greater than 0.0.
*
* @return the value log<SUB>pBase</SUB> {@code pArg}, the base
* {@code pBase} logarithm of {@code pArg}.
*/
public static double log(final double pArg, final double pBase) {
return Math.log(pArg) / Math.log(pBase);
}
/**
* A replacement for {@code Math.abs}, that never returns negative values.
* {@code Math.abs(long)} does this for {@code Long.MIN_VALUE}.
*
* @see Math#abs(long)
* @see Long#MIN_VALUE
*
* @param pNumber
* @return the absolute value of {@code pNumber}
*
* @throws ArithmeticException if {@code pNumber == Long.MIN_VALUE}
*/
public static long abs(final long pNumber) {
if (pNumber == Long.MIN_VALUE) {
throw new ArithmeticException("long overflow: 9223372036854775808");
}
return (pNumber < 0) ? -pNumber : pNumber;
}
/**
* A replacement for {@code Math.abs}, that never returns negative values.
* {@code Math.abs(int)} does this for {@code Integer.MIN_VALUE}.
*
* @see Math#abs(int)
* @see Integer#MIN_VALUE
*
* @param pNumber
* @return the absolute value of {@code pNumber}
*
* @throws ArithmeticException if {@code pNumber == Integer.MIN_VALUE}
*/
public static int abs(final int pNumber) {
if (pNumber == Integer.MIN_VALUE) {
throw new ArithmeticException("int overflow: 2147483648");
}
return (pNumber < 0) ? -pNumber : pNumber;
}
}

View File

@@ -0,0 +1,244 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.lang;
/**
* Platform
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/Platform.java#1 $
*/
public final class Platform {
/**
* Normalized operating system constant
*/
final OperatingSystem mOS;
/**
* Unormalized operating system version constant (for completeness)
*/
final String mVersion;
/**
* Normalized system architecture constant
*/
final Architecture mArchitecture;
static final private Platform INSTANCE = new Platform();
private Platform() {
mOS = normalizeOperatingSystem();
mVersion = System.getProperty("os.version");
mArchitecture = normalizeArchitecture(mOS);
}
private static OperatingSystem normalizeOperatingSystem() {
String os = System.getProperty("os.name");
if (os == null) {
throw new IllegalStateException("System property \"os.name\" == null");
}
os = os.toLowerCase();
if (os.startsWith("windows")) {
return OperatingSystem.Windows;
}
else if (os.startsWith("linux")) {
return OperatingSystem.Linux;
}
else if (os.startsWith("mac os")) {
return OperatingSystem.MacOS;
}
else if (os.startsWith("solaris") || os.startsWith("sunos")) {
return OperatingSystem.Solaris;
}
return OperatingSystem.Unknown;
}
private static Architecture normalizeArchitecture(final OperatingSystem pOsName) {
String arch = System.getProperty("os.arch");
if (arch == null) {
throw new IllegalStateException("System property \"os.arch\" == null");
}
arch = arch.toLowerCase();
if (pOsName == OperatingSystem.Windows
&& (arch.startsWith("x86") || arch.startsWith("i386"))) {
return Architecture.X86;
// TODO: 64 bit
}
else if (pOsName == OperatingSystem.Linux) {
if (arch.startsWith("x86") || arch.startsWith("i386")) {
return Architecture.I386;
}
else if (arch.startsWith("i686")) {
return Architecture.I686;
}
// TODO: More Linux options?
// TODO: 64 bit
}
else if (pOsName == OperatingSystem.MacOS) {
if (arch.startsWith("power") || arch.startsWith("ppc")) {
return Architecture.PPC;
}
else if (arch.startsWith("i386")) {
return Architecture.I386;
}
}
else if (pOsName == OperatingSystem.Solaris) {
if (arch.startsWith("sparc")) {
return Architecture.SPARC;
}
if (arch.startsWith("x86")) {
// TODO: Should we use i386 as Linux and Mac does?
return Architecture.X86;
}
// TODO: 64 bit
}
return Architecture.Unknown;
}
/**
* Returns the current {@code Platform}.
* @return the current {@code Platform}.
*/
public static Platform get() {
return INSTANCE;
}
/**
* @return this platform's OS.
*/
public OperatingSystem getOS() {
return mOS;
}
/**
* @return this platform's OS version.
*/
public String getVersion() {
return mVersion;
}
/**
* @return this platform's architecture.
*/
public Architecture getArchitecture() {
return mArchitecture;
}
/**
* Shorthand for {@code Platform.get().getOS()}.
* @return the current {@code OperatingSystem}.
*/
public static OperatingSystem os() {
return INSTANCE.mOS;
}
/**
* Shorthand for {@code Platform.get().getVersion()}.
* @return the current OS version.
*/
public static String version() {
return INSTANCE.mVersion;
}
/**
* Shorthand for {@code Platform.get().getArchitecture()}.
* @return the current {@code Architecture}.
*/
public static Architecture arch() {
return INSTANCE.mArchitecture;
}
/**
* Enumeration of common System {@code Architecture}s.
* <p/>
* For {@link #Unknown unknown architectures}, {@code toString()} will return
* the the same value as {@code System.getProperty("os.arch")}.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/Platform.java#1 $
*/
public static enum Architecture {
X86("x86"),
I386("i386"),
I686("i686"),
PPC("ppc"),
SPARC("sparc"),
Unknown(System.getProperty("os.arch"));
final String mName;// for debug only
private Architecture(String pName) {
mName = pName;
}
public String toString() {
return mName;
}
}
/**
* Enumeration of common {@code OperatingSystem}s.
* <p/>
* For {@link #Unknown unknown operating systems}, {@code getName()} will return
* the the same value as {@code System.getProperty("os.name")}.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/Platform.java#1 $
*/
public static enum OperatingSystem {
Windows("Windows", "win"),
Linux("Linux", "lnx"),
Solaris("Solaris", "sun"),
MacOS("Mac OS", "osx"),
Unknown(System.getProperty("os.name"), "");
final String mId;
final String mName;// for debug only
private OperatingSystem(String pName, String pId) {
mName = pName;
mId = pId;
}
public String getName() {
return mName;
}
public String toString() {
return mId;
}
}
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.lang;
/**
* Util class for various reflection-based operations.
* <p/>
* <em>NOTE: This class is not considered part of the public API and may be
* changed without notice</em>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/ReflectUtil.java#1 $
*/
public final class ReflectUtil {
/** Don't allow instances */
private ReflectUtil() {}
/**
* Returns the primitive type for the given wrapper type.
*
* @param pType the wrapper type
*
* @return the primitive type
*
* @throws IllegalArgumentException if {@code pType} is not a primitive
* wrapper
*/
public static Class unwrapType(Class pType) {
if (pType == Boolean.class) {
return Boolean.TYPE;
}
else if (pType == Byte.class) {
return Byte.TYPE;
}
else if (pType == Character.class) {
return Character.TYPE;
}
else if (pType == Double.class) {
return Double.TYPE;
}
else if (pType == Float.class) {
return Float.TYPE;
}
else if (pType == Integer.class) {
return Integer.TYPE;
}
else if (pType == Long.class) {
return Long.TYPE;
}
else if (pType == Short.class) {
return Short.TYPE;
}
throw new IllegalArgumentException("Not a primitive wrapper: " + pType);
}
/**
* Returns the wrapper type for the given primitive type.
*
* @param pType the primitive tpye
*
* @return the wrapper type
*
* @throws IllegalArgumentException if {@code pType} is not a primitive
* type
*/
public static Class wrapType(Class pType) {
if (pType == Boolean.TYPE) {
return Boolean.class;
}
else if (pType == Byte.TYPE) {
return Byte.class;
}
else if (pType == Character.TYPE) {
return Character.class;
}
else if (pType == Double.TYPE) {
return Double.class;
}
else if (pType == Float.TYPE) {
return Float.class;
}
else if (pType == Integer.TYPE) {
return Integer.class;
}
else if (pType == Long.TYPE) {
return Long.class;
}
else if (pType == Short.TYPE) {
return Short.class;
}
throw new IllegalArgumentException("Not a primitive type: " + pType);
}
/**
* Returns {@code true} if the given type is a primitive wrapper.
*
* @param pType
*
* @return {@code true} if the given type is a primitive wrapper, otherwise
* {@code false}
*/
public static boolean isPrimitiveWrapper(Class pType) {
return pType == Boolean.class || pType == Byte.class
|| pType == Character.class || pType == Double.class
|| pType == Float.class || pType == Integer.class
|| pType == Long.class || pType == Short.class;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,693 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.lang;
import com.twelvemonkeys.util.XMLProperties;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* A utility class with some useful system-related functions.
* <p/>
* <em>NOTE: This class is not considered part of the public API and may be
* changed without notice</em>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
*
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/SystemUtil.java#3 $
*
*/
public final class SystemUtil {
/** {@code ".xml"} */
public static String XML_PROPERTIES = ".xml";
/** {@code ".properties"} */
public static String STD_PROPERTIES = ".properties";
// Disallow creating objects of this type
private SystemUtil() {
}
/** This class marks an inputstream as containing XML, does nothing */
private static class XMLPropertiesInputStream extends FilterInputStream {
public XMLPropertiesInputStream(InputStream pIS) {
super(pIS);
}
}
/**
* Gets the named resource as a stream from the given Class' Classoader.
* If the pGuessSuffix parameter is true, the method will try to append
* typical properties file suffixes, such as ".properties" or ".xml".
*
* @param pClassLoader the class loader to use
* @param pName name of the resource
* @param pGuessSuffix guess suffix
*
* @return an input stream reading from the resource
*/
private static InputStream getResourceAsStream(ClassLoader pClassLoader, String pName,
boolean pGuessSuffix) {
InputStream is;
if (!pGuessSuffix) {
is = pClassLoader.getResourceAsStream(pName);
// If XML, wrap stream
if (is != null && pName.endsWith(XML_PROPERTIES)) {
is = new XMLPropertiesInputStream(is);
}
}
else {
// Try normal properties
is = pClassLoader.getResourceAsStream(pName + STD_PROPERTIES);
// Try XML
if (is == null) {
is = pClassLoader.getResourceAsStream(pName + XML_PROPERTIES);
// Wrap stream
if (is != null) {
is = new XMLPropertiesInputStream(is);
}
}
}
// Return stream
return is;
}
/**
* Gets the named file as a stream from the current directory.
* If the pGuessSuffix parameter is true, the method will try to append
* typical properties file suffixes, such as ".properties" or ".xml".
*
* @param pName name of the resource
* @param pGuessSuffix guess suffix
*
* @return an input stream reading from the resource
*/
private static InputStream getFileAsStream(String pName,
boolean pGuessSuffix) {
InputStream is = null;
File propertiesFile;
try {
if (!pGuessSuffix) {
// Get file
propertiesFile = new File(pName);
if (propertiesFile.exists()) {
is = new FileInputStream(propertiesFile);
// If XML, wrap stream
if (pName.endsWith(XML_PROPERTIES)) {
is = new XMLPropertiesInputStream(is);
}
}
}
else {
// Try normal properties
propertiesFile = new File(pName + STD_PROPERTIES);
if (propertiesFile.exists()) {
is = new FileInputStream(propertiesFile);
}
else {
// Try XML
propertiesFile = new File(pName + XML_PROPERTIES);
if (propertiesFile.exists()) {
// Wrap stream
is = new XMLPropertiesInputStream(new FileInputStream(propertiesFile));
}
}
}
}
catch (FileNotFoundException fnf) {
// Should not happen, as we always test that the file .exists()
// before creating InputStream
// assert false;
}
return is;
}
/**
* Utility method for loading a named properties-file for a class.
* <P>
* The properties-file is loaded through either:
* <OL>
* <LI>The given class' class loader (from classpath)</LI>
* <LI>Or, the system class loader (from classpath)</LI>
* <LI>Or, if it cannot be found in the classpath, an attempt to read from
* the current directory (or full path if given).</LI>
* </OL>
* <P>
* Both normal java.util.Properties and com.twelvemonkeys.util.XMLProperties
* are supported (XML-properties must have ".xml" as its file extension).
*
* @param pClass The class to load properties for. If this parameter is
* {@code null}, the method will work exactly as
* {@link #loadProperties(String)}
* @param pName The name of the properties-file. If this parameter is
* {@code null}, the method will work exactly as
* {@link #loadProperties(Class)}
*
* @return A Properties mapping read from the given file or for the given
* class. <!--If no properties-file was found, an empty Properties object is
* returned.-->
*
* @throws NullPointerException if both {@code pName} and
* {@code pClass} paramters are {@code null}
* @throws IOException if an error occurs during load.
* @throws FileNotFoundException if no properties-file could be found.
*
* @see #loadProperties(String)
* @see #loadProperties(Class)
* @see java.lang.ClassLoader#getResourceAsStream
* @see java.lang.ClassLoader#getSystemResourceAsStream
*
* @todo Reconsider ever using the System ClassLoader: http://www.javaworld.com/javaworld/javaqa/2003-06/01-qa-0606-load.html
* @todo Consider using Context Classloader instead?
*/
public static Properties loadProperties(Class pClass, String pName)
throws IOException
{
// Convert to name the classloader understands
String name = !StringUtil.isEmpty(pName) ? pName : pClass.getName().replace('.', '/');
// Should we try to guess suffix?
boolean guessSuffix = (pName == null || pName.indexOf('.') < 0);
InputStream is;
// TODO: WHAT IF MULTIPLE RESOURCES EXISTS?!
// Try loading resource through the current class' classloader
if (pClass != null
&& (is = getResourceAsStream(pClass.getClassLoader(), name, guessSuffix)) != null) {
//&& (is = getResourceAsStream(pClass, name, guessSuffix)) != null) {
// Nothing to do
//System.out.println(((is instanceof XMLPropertiesInputStream) ?
// "XML-properties" : "Normal .properties")
// + " from Class' ClassLoader");
}
// If that fails, try the system classloader
else if ((is = getResourceAsStream(ClassLoader.getSystemClassLoader(),
name, guessSuffix)) != null) {
//else if ((is = getSystemResourceAsStream(name, guessSuffix)) != null) {
// Nothing to do
//System.out.println(((is instanceof XMLPropertiesInputStream) ?
// "XML-properties" : "Normal .properties")
// + " from System ClassLoader");
}
// All failed, try loading from file
else if ((is = getFileAsStream(name, guessSuffix)) != null) {
//System.out.println(((is instanceof XMLPropertiesInputStream) ?
// "XML-properties" : "Normal .properties")
// + " from System ClassLoader");
}
else {
if (guessSuffix) {
// TODO: file extension iterator or something...
throw new FileNotFoundException(name + ".properties or " + name + ".xml");
}
else {
throw new FileNotFoundException(name);
}
}
// We have inputstream now, load...
try {
return loadProperties(is);
}
finally {
// NOTE: If is == null, a FileNotFoundException must have been thrown above
try {
is.close();
}
catch (IOException ioe) {
// Not critical...
}
}
}
/**
* Utility method for loading a properties-file for a given class.
* The properties are searched for on the form
* "com/package/ClassName.properties" or
* "com/package/ClassName.xml".
* <P>
* The properties-file is loaded through either:
* <OL>
* <LI>The given class' class loader (from classpath)</LI>
* <LI>Or, the system class loader (from classpath)</LI>
* <LI>Or, if it cannot be found in the classpath, an attempt to read from
* the current directory (or full path if given).</LI>
* </OL>
* <P>
* Both normal java.util.Properties and com.twelvemonkeys.util.XMLProperties
* are supported (XML-properties must have ".xml" as its file extension).
*
* @param pClass The class to load properties for
* @return A Properties mapping for the given class. <!--If no properties-
* file was found, an empty Properties object is returned.-->
*
* @throws NullPointerException if the {@code pClass} paramters is
* {@code null}
* @throws IOException if an error occurs during load.
* @throws FileNotFoundException if no properties-file could be found.
*
* @see #loadProperties(String)
* @see #loadProperties(Class, String)
* @see java.lang.ClassLoader#getResourceAsStream
* @see java.lang.ClassLoader#getSystemResourceAsStream
*
*/
public static Properties loadProperties(Class pClass) throws IOException {
return loadProperties(pClass, null);
}
/**
* Utility method for loading a named properties-file.
* <P>
* The properties-file is loaded through either:
* <OL>
* <LI>The system class loader (from classpath)</LI>
* <LI>Or, if it cannot be found in the classpath, an attempt to read from
* the current directory.</LI>
* </OL>
* <P>
* Both normal java.util.Properties and com.twelvemonkeys.util.XMLProperties
* are supported (XML-properties must have ".xml" as its file extension).
*
* @param pName The name of the properties-file.
* @return A Properties mapping read from the given file. <!--If no properties-
* file was found, an empty Properties object is returned.-->
*
* @throws NullPointerException if the {@code pName} paramters is
* {@code null}
* @throws IOException if an error occurs during load.
* @throws FileNotFoundException if no properties-file could be found.
*
* @see #loadProperties(Class)
* @see #loadProperties(Class, String)
* @see java.lang.ClassLoader#getSystemResourceAsStream
*
*/
public static Properties loadProperties(String pName) throws IOException {
return loadProperties(null, pName);
}
/*
* Utility method for loading a properties-file.
* <P>
* The properties files may also be contained in a zip/jar-file named
* by the {@code com.twelvemonkeys.util.Config} system property (use "java -D"
* to override). Default is "config.zip" in the current directory.
*
* @param pName The name of the file to loaded
* @return A Properties mapping for the given class. If no properties-
* file was found, an empty Properties object is returned.
*
*/
/*
public static Properties loadProperties(String pName) throws IOException {
// Use XML?
boolean useXML = pName.endsWith(XML_PROPERTIES) ? true : false;
InputStream is = null;
File file = new File(pName);
String configName = System.getProperty("com.twelvemonkeys.util.Config");
File configArchive = new File(!StringUtil.isEmpty(configName)
? configName : DEFAULT_CONFIG);
// Get input stream to the file containing the properties
if (file.exists()) {
// Try reading from file, normal way
is = new FileInputStream(file);
}
else if (configArchive.exists()) {
// Try reading properties from zip-file
ZipFile zip = new ZipFile(configArchive);
ZipEntry ze = zip.getEntry(pName);
if (ze != null) {
is = zip.getInputStream(ze);
}
}
// Do the loading
try {
// Load the properties
return loadProperties(is, useXML);
}
finally {
// Try closing the archive to free resources
if (is != null) {
try {
is.close();
}
catch (IOException ioe) {
// Not critical...
}
}
}
}
*/
/**
* Returns a Properties, loaded from the given inputstream. If the given
* inputstream is null, then an empty Properties object is returned.
*
* @param pInput the inputstream to read from
*
* @return a Properties object read from the given stream, or an empty
* Properties mapping, if the stream is null.
*
* @throws IOException if an error occurred when reading from the input
* stream.
*
*/
private static Properties loadProperties(InputStream pInput)
throws IOException {
if (pInput == null) {
throw new IllegalArgumentException("InputStream == null!");
}
Properties mapping;
if (pInput instanceof XMLPropertiesInputStream) {
mapping = new XMLProperties();
}
else {
mapping = new Properties();
}
// Load the properties
mapping.load(pInput);
return mapping;
}
@SuppressWarnings({"SuspiciousSystemArraycopy"})
public static Object clone(final Cloneable pObject) throws CloneNotSupportedException {
if (pObject == null) {
return null; // Null is clonable.. Easy. ;-)
}
// All arrays does have a clone method, but it's invisible for reflection...
// By luck, multi-dimensional primitive arrays are instances of Object[]
if (pObject instanceof Object[]) {
return ((Object[]) pObject).clone();
}
else if (pObject.getClass().isArray()) {
// One-dimensional primitive array, cloned manually
int lenght = Array.getLength(pObject);
Object clone = Array.newInstance(pObject.getClass().getComponentType(), lenght);
System.arraycopy(pObject, 0, clone, 0, lenght);
return clone;
}
try {
// Find the clone method
Method clone = null;
Class clazz = pObject.getClass();
do {
try {
clone = clazz.getDeclaredMethod("clone");
break; // Found, or throws exception above
}
catch (NoSuchMethodException ignore) {
// Ignore
}
}
while ((clazz = clazz.getSuperclass()) != null);
// NOTE: This should never happen
if (clone == null) {
throw new CloneNotSupportedException(pObject.getClass().getName());
}
// Override access if needed
if (!clone.isAccessible()) {
clone.setAccessible(true);
}
// Invoke clone method on original object
return clone.invoke(pObject);
}
catch (SecurityException e) {
CloneNotSupportedException cns = new CloneNotSupportedException(pObject.getClass().getName());
cns.initCause(e);
throw cns;
}
catch (IllegalAccessException e) {
throw new CloneNotSupportedException(pObject.getClass().getName());
}
catch (InvocationTargetException e) {
if (e.getTargetException() instanceof CloneNotSupportedException) {
throw (CloneNotSupportedException) e.getTargetException();
}
else if (e.getTargetException() instanceof RuntimeException) {
throw (RuntimeException) e.getTargetException();
}
else if (e.getTargetException() instanceof Error) {
throw (Error) e.getTargetException();
}
throw new CloneNotSupportedException(pObject.getClass().getName());
}
}
// public static void loadLibrary(String pLibrary) {
// NativeLoader.loadLibrary(pLibrary);
// }
//
// public static void loadLibrary(String pLibrary, ClassLoader pLoader) {
// NativeLoader.loadLibrary(pLibrary, pLoader);
// }
public static void main(String[] args) throws CloneNotSupportedException {
System.out.println("clone: " + args.clone().length + " (" + args.length + ")");
System.out.println("copy: " + ((String[]) clone(args)).length + " (" + args.length + ")");
int[] ints = {1,2,3};
int[] copies = (int[]) clone(ints);
System.out.println("Copies: " + copies.length + " (" + ints.length + ")");
int[][] intsToo = {{1}, {2,3}, {4,5,6}};
int[][] copiesToo = (int[][]) clone(intsToo);
System.out.println("Copies: " + copiesToo.length + " (" + intsToo.length + ")");
System.out.println("Copies0: " + copiesToo[0].length + " (" + intsToo[0].length + ")");
System.out.println("Copies1: " + copiesToo[1].length + " (" + intsToo[1].length + ")");
System.out.println("Copies2: " + copiesToo[2].length + " (" + intsToo[2].length + ")");
Map<String, String> map = new HashMap<String, String>();
for (String arg : args) {
map.put(arg, arg);
}
Map copy = (Map) clone((Cloneable) map);
System.out.println("Map : " + map);
System.out.println("Copy: " + copy);
/*
SecurityManager sm = System.getSecurityManager();
try {
System.setSecurityManager(new SecurityManager() {
public void checkPermission(Permission perm) {
if (perm.getName().equals("suppressAccessChecks")) {
throw new SecurityException();
}
//super.checkPermission(perm);
}
});
*/
Cloneable cloneable = new Cloneable() {}; // No public clone method
Cloneable clone = (Cloneable) clone(cloneable);
System.out.println("cloneable: " + cloneable);
System.out.println("clone: " + clone);
/*
}
finally {
System.setSecurityManager(sm);
}
*/
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
return null;
}
}, AccessController.getContext());
//String string = args.length > 0 ? args[0] : "jaffa";
//clone(string);
}
/**
* Tests if a named class is generally available.
* If a class is considered available, a call to
* {@code Class.forName(pClassName)} will not result in an exception.
*
* @param pClassName the class name to test
* @return {@code true} if available
*/
public static boolean isClassAvailable(String pClassName) {
return isClassAvailable(pClassName, (ClassLoader) null);
}
/**
* Tests if a named class is available from another class.
* If a class is considered available, a call to
* {@code Class.forName(pClassName, true, pFromClass.getClassLoader())}
* will not result in an exception.
*
* @param pClassName the class name to test
* @param pFromClass the class to test from
* @return {@code true} if available
*/
public static boolean isClassAvailable(String pClassName, Class pFromClass) {
ClassLoader loader = pFromClass != null ? pFromClass.getClassLoader() : null;
return isClassAvailable(pClassName, loader);
}
private static boolean isClassAvailable(String pClassName, ClassLoader pLoader) {
try {
// TODO: Sometimes init is not needed, but need to find a way to know...
getClass(pClassName, true, pLoader);
return true;
}
catch (SecurityException ignore) {
// Ignore
}
catch (ClassNotFoundException ignore) {
// Ignore
}
catch (LinkageError ignore) {
// Ignore
}
return false;
}
public static boolean isFieldAvailable(final String pClassName, final String pFieldName) {
return isFieldAvailable(pClassName, pFieldName, (ClassLoader) null);
}
public static boolean isFieldAvailable(final String pClassName, final String pFieldName, final Class pFromClass) {
ClassLoader loader = pFromClass != null ? pFromClass.getClassLoader() : null;
return isFieldAvailable(pClassName, pFieldName, loader);
}
private static boolean isFieldAvailable(final String pClassName, final String pFieldName, final ClassLoader pLoader) {
try {
Class cl = getClass(pClassName, false, pLoader);
Field field = cl.getField(pFieldName);
if (field != null) {
return true;
}
}
catch (ClassNotFoundException ignore) {
// Ignore
}
catch (LinkageError ignore) {
// Ignore
}
catch (NoSuchFieldException ignore) {
// Ignore
}
return false;
}
public static boolean isMethodAvailable(String pClassName, String pMethodName) {
// Finds void only
return isMethodAvailable(pClassName, pMethodName, null, (ClassLoader) null);
}
public static boolean isMethodAvailable(String pClassName, String pMethodName, Class[] pParams) {
return isMethodAvailable(pClassName, pMethodName, pParams, (ClassLoader) null);
}
public static boolean isMethodAvailable(String pClassName, String pMethodName, Class[] pParams, Class pFromClass) {
ClassLoader loader = pFromClass != null ? pFromClass.getClassLoader() : null;
return isMethodAvailable(pClassName, pMethodName, pParams, loader);
}
private static boolean isMethodAvailable(String pClassName, String pMethodName, Class[] pParams, ClassLoader pLoader) {
try {
Class cl = getClass(pClassName, false, pLoader);
Method method = cl.getMethod(pMethodName, pParams);
if (method != null) {
return true;
}
}
catch (ClassNotFoundException ignore) {
// Ignore
}
catch (LinkageError ignore) {
// Ignore
}
catch (NoSuchMethodException ignore) {
// Ignore
}
return false;
}
private static Class getClass(String pClassName, boolean pInitialize, ClassLoader pLoader) throws ClassNotFoundException {
// NOTE: We need the context classloader, as SystemUtil's
// classloader may have a totally different classloader than
// the original caller class (as in Class.forName(cn, false, null)).
ClassLoader loader = pLoader != null ? pLoader :
Thread.currentThread().getContextClassLoader();
return Class.forName(pClassName, pInitialize, loader);
}
}

View File

@@ -0,0 +1,124 @@
package com.twelvemonkeys.lang;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
/**
* Kind of like {@code org.apache.commons.lang.Validate}. Just smarter. ;-)
* <p/>
* Uses type parameterized return values, thus making it possible to check
* constructor arguments before
* they are passed on to {@code super} or {@code this} type constructors.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/Validate.java#1 $
*/
public final class Validate {
private static final String UNSPECIFIED_PARAM_NAME = "method parameter";
private Validate() {}
// Not null...
public static <T> T notNull(final T pParameter) {
return notNull(pParameter, null);
}
public static <T> T notNull(final T pParameter, final String pParamName) {
if (pParameter == null) {
throw new IllegalArgumentException(String.format("%s may not be null", pParamName == null ? UNSPECIFIED_PARAM_NAME : pParamName));
}
return pParameter;
}
// Not empty...
public static <T extends CharSequence> T notEmpty(final T pParameter) {
return notEmpty(pParameter, null);
}
public static <T extends CharSequence> T notEmpty(final T pParameter, final String pParamName) {
if (pParameter == null || pParameter.length() == 0) {
throw new IllegalArgumentException(String.format("%s may not be empty", pParamName == null ? UNSPECIFIED_PARAM_NAME : pParamName));
}
return pParameter;
}
public static <T> T[] notEmpty(final T[] pParameter) {
return notEmpty(pParameter, null);
}
public static <T> T[] notEmpty(final T[] pParameter, final String pParamName) {
if (pParameter == null || pParameter.length == 0) {
throw new IllegalArgumentException(String.format("%s may not be empty", pParamName == null ? UNSPECIFIED_PARAM_NAME : pParamName));
}
return pParameter;
}
public static <T> Collection<T> notEmpty(final Collection<T> pParameter) {
return notEmpty(pParameter, null);
}
public static <T> Collection<T> notEmpty(final Collection<T> pParameter, final String pParamName) {
if (pParameter == null || pParameter.isEmpty()) {
throw new IllegalArgumentException(String.format("%s may not be empty", pParamName == null ? UNSPECIFIED_PARAM_NAME : pParamName));
}
return pParameter;
}
public static <K, V> Map<K, V> notEmpty(final Map<K, V> pParameter) {
return notEmpty(pParameter, null);
}
public static <K, V> Map<K, V> notEmpty(final Map<K, V> pParameter, final String pParamName) {
if (pParameter == null || pParameter.isEmpty()) {
throw new IllegalArgumentException(String.format("%s may not be empty", pParamName == null ? UNSPECIFIED_PARAM_NAME : pParamName));
}
return pParameter;
}
// No null elements
public static <T> T[] noNullElements(final T[] pParameter) {
return noNullElements(pParameter, null);
}
public static <T> T[] noNullElements(final T[] pParameter, final String pParamName) {
noNullElements(Arrays.asList(pParameter), pParamName);
return pParameter;
}
public static <T> Collection<T> noNullElements(final Collection<T> pParameter) {
return noNullElements(pParameter, null);
}
public static <T> Collection<T> noNullElements(final Collection<T> pParameter, final String pParamName) {
for (T element : pParameter) {
if (element == null) {
throw new IllegalArgumentException(String.format("%s may not contain null elements", pParamName == null ? UNSPECIFIED_PARAM_NAME : pParamName));
}
}
return pParameter;
}
public static <K, V> Map<K, V> noNullElements(final Map<K, V> pParameter) {
return noNullElements(pParameter, null);
}
public static <K, V> Map<K, V> noNullElements(final Map<K, V> pParameter, final String pParamName) {
for (V element : pParameter.values()) {
if (element == null) {
throw new IllegalArgumentException(String.format("%s may not contain null elements", pParamName == null ? UNSPECIFIED_PARAM_NAME : pParamName));
}
}
return pParameter;
}
}

View File

@@ -0,0 +1,4 @@
/**
*Contains utils/helpers for classes in {@code java.lang}.
*/
package com.twelvemonkeys.lang;

View File

@@ -0,0 +1,400 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.util.*;
import java.io.Serializable;
/**
* AbstractDecoratedMap
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/AbstractDecoratedMap.java#2 $
*/
// TODO: The generics in this class looks suspicious..
abstract class AbstractDecoratedMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Serializable, Cloneable {
protected Map<K, Entry<K, V>> mEntries;
protected transient volatile int mModCount;
private transient volatile Set<Entry<K, V>> mEntrySet = null;
private transient volatile Set<K> mKeySet = null;
private transient volatile Collection<V> mValues = null;
/**
* Creates a {@code Map} backed by a {@code HashMap}.
*/
public AbstractDecoratedMap() {
this(new HashMap<K, Entry<K, V>>(), null);
}
/**
* Creates a {@code Map} backed by a {@code HashMap}, containing all
* key/value mappings from the given {@code Map}.
* <p/>
* <small>This is constructor is here to comply with the reccomendations for
* "standard" constructors in the {@code Map} interface.</small>
*
* @see #AbstractDecoratedMap(java.util.Map, java.util.Map)
*
* @param pContents the map whose mappings are to be placed in this map.
* May be {@code null}.
*/
public AbstractDecoratedMap(Map<? extends K, ? extends V> pContents) {
this(new HashMap<K, Entry<K, V>>(), pContents);
}
/**
* Creates a {@code Map} backed by the given backing-{@code Map},
* containing all key/value mappings from the given contents-{@code Map}.
* <p/>
* NOTE: The backing map is structuraly cahnged, and it should NOT be
* accessed directly, after the wrapped map is created.
*
* @param pBacking the backing map of this map. Must be either empty, or
* the same map as {@code pContents}.
* @param pContents the map whose mappings are to be placed in this map.
* May be {@code null}.
*
* @throws IllegalArgumentException if {@code pBacking} is {@code null}
* or if {@code pBacking} differs from {@code pContent} and is not empty.
*/
public AbstractDecoratedMap(Map<K, Entry<K, V>> pBacking, Map<? extends K, ? extends V> pContents) {
if (pBacking == null) {
throw new IllegalArgumentException("backing == null");
}
Entry<? extends K, ? extends V>[] entries = null;
if (pBacking == pContents) {
// NOTE: Special treatment to avoid ClassCastExceptions
Set<? extends Entry<? extends K, ? extends V>> es = pContents.entrySet();
//noinspection unchecked
entries = new Entry[es.size()];
entries = es.toArray(entries);
pContents = null;
pBacking.clear();
}
else if (!pBacking.isEmpty()) {
throw new IllegalArgumentException("backing must be empty");
}
mEntries = pBacking;
init();
if (pContents != null) {
putAll(pContents);
}
else if (entries != null) {
// Reinsert entries, this time wrapped
for (Entry<? extends K, ? extends V> entry : entries) {
put(entry.getKey(), entry.getValue());
}
}
}
/**
* Default implementation, does nothing.
*/
protected void init() {
}
public int size() {
return mEntries.size();
}
public void clear() {
mEntries.clear();
mModCount++;
init();
}
public boolean isEmpty() {
return mEntries.isEmpty();
}
public boolean containsKey(Object pKey) {
return mEntries.containsKey(pKey);
}
/**
* Returns {@code true} if this map maps one or more keys to the
* specified pValue. More formally, returns {@code true} if and only if
* this map contains at least one mapping to a pValue {@code v} such that
* {@code (pValue==null ? v==null : pValue.equals(v))}.
* <p/>
* This implementation requires time linear in the map size for this
* operation.
*
* @param pValue pValue whose presence in this map is to be tested.
* @return {@code true} if this map maps one or more keys to the
* specified pValue.
*/
public boolean containsValue(Object pValue) {
for (V value : values()) {
if (value == pValue || (value != null && value.equals(pValue))) {
return true;
}
}
return false;
}
public Collection<V> values() {
Collection<V> values = mValues;
return values != null ? values : (mValues = new Values());
}
public Set<Entry<K, V>> entrySet() {
Set<Entry<K, V>> es = mEntrySet;
return es != null ? es : (mEntrySet = new EntrySet());
}
public Set<K> keySet() {
Set<K> ks = mKeySet;
return ks != null ? ks : (mKeySet = new KeySet());
}
/**
* Returns a shallow copy of this {@code AbstractMap} instance: the keys
* and values themselves are not cloned.
*
* @return a shallow copy of this map.
*/
protected Object clone() throws CloneNotSupportedException {
AbstractDecoratedMap map = (AbstractDecoratedMap) super.clone();
map.mValues = null;
map.mEntrySet = null;
map.mKeySet = null;
// TODO: Implement: Need to clone the backing map...
return map;
}
// Subclass overrides these to alter behavior of views' iterator() method
protected abstract Iterator<K> newKeyIterator();
protected abstract Iterator<V> newValueIterator();
protected abstract Iterator<Entry<K, V>> newEntryIterator();
// TODO: Implement these (get/put/remove)?
public abstract V get(Object pKey);
public abstract V remove(Object pKey);
public abstract V put(K pKey, V pValue);
/*protected*/ Entry<K, V> createEntry(K pKey, V pValue) {
return new BasicEntry<K, V>(pKey, pValue);
}
/*protected*/ Entry<K, V> getEntry(K pKey) {
return mEntries.get(pKey);
}
/**
* Removes the given entry from the Map.
*
* @param pEntry the entry to be removed
*
* @return the removed entry, or {@code null} if nothing was removed.
*/
protected Entry<K, V> removeEntry(Entry<K, V> pEntry) {
if (pEntry == null) {
return null;
}
// Find candidate entry for this key
Entry<K, V> candidate = getEntry(pEntry.getKey());
if (candidate == pEntry || (candidate != null && candidate.equals(pEntry))) {
// Remove
remove(pEntry.getKey());
return pEntry;
}
return null;
}
protected class Values extends AbstractCollection<V> {
public Iterator<V> iterator() {
return newValueIterator();
}
public int size() {
return AbstractDecoratedMap.this.size();
}
public boolean contains(Object o) {
return containsValue(o);
}
public void clear() {
AbstractDecoratedMap.this.clear();
}
}
protected class EntrySet extends AbstractSet<Entry<K, V>> {
public Iterator<Entry<K, V>> iterator() {
return newEntryIterator();
}
public boolean contains(Object o) {
if (!(o instanceof Entry))
return false;
Entry e = (Entry) o;
//noinspection SuspiciousMethodCalls
Entry<K, V> candidate = mEntries.get(e.getKey());
return candidate != null && candidate.equals(e);
}
public boolean remove(Object o) {
if (!(o instanceof Entry)) {
return false;
}
/*
// NOTE: Extra cautions is taken, to only remove the entry if it
// equals the entry in the map
Object key = ((Entry) o).getKey();
Entry entry = (Entry) mEntries.get(key);
// Same entry?
if (entry != null && entry.equals(o)) {
return AbstractWrappedMap.this.remove(key) != null;
}
return false;
*/
//noinspection unchecked
return AbstractDecoratedMap.this.removeEntry((Entry) o) != null;
}
public int size() {
return AbstractDecoratedMap.this.size();
}
public void clear() {
AbstractDecoratedMap.this.clear();
}
}
protected class KeySet extends AbstractSet<K> {
public Iterator<K> iterator() {
return newKeyIterator();
}
public int size() {
return AbstractDecoratedMap.this.size();
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
return AbstractDecoratedMap.this.remove(o) != null;
}
public void clear() {
AbstractDecoratedMap.this.clear();
}
}
/**
* A simple Map.Entry implementaton.
*/
static class BasicEntry<K, V> implements Entry<K, V>, Serializable {
K mKey;
V mValue;
BasicEntry(K pKey, V pValue) {
mKey = pKey;
mValue = pValue;
}
/**
* Default implementation does nothing.
*
* @param pMap the map that is accessed
*/
protected void recordAccess(Map<K, V> pMap) {
}
/**
* Default implementation does nothing.
* @param pMap the map that is removed from
*/
protected void recordRemoval(Map<K, V> pMap) {
}
public V getValue() {
return mValue;
}
public V setValue(V pValue) {
V oldValue = mValue;
mValue = pValue;
return oldValue;
}
public K getKey() {
return mKey;
}
public boolean equals(Object pOther) {
if (!(pOther instanceof Map.Entry)) {
return false;
}
Map.Entry entry = (Map.Entry) pOther;
Object k1 = mKey;
Object k2 = entry.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = mValue;
Object v2 = entry.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2))) {
return true;
}
}
return false;
}
public int hashCode() {
return (mKey == null ? 0 : mKey.hashCode()) ^
(mValue == null ? 0 : mValue.hashCode());
}
public String toString() {
return getKey() + "=" + getValue();
}
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
/**
* AbstractResource class description.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/AbstractResource.java#1 $
*/
abstract class AbstractResource implements Resource {
protected final Object mResourceId;
protected final Object mWrappedResource;
/**
* Creates a {@code Resource}.
*
* @param pResourceId
* @param pWrappedResource
*/
protected AbstractResource(Object pResourceId, Object pWrappedResource) {
if (pResourceId == null) {
throw new IllegalArgumentException("id == null");
}
if (pWrappedResource == null) {
throw new IllegalArgumentException("resource == null");
}
mResourceId = pResourceId;
mWrappedResource = pWrappedResource;
}
public final Object getId() {
return mResourceId;
}
/**
* Default implementation simply returns {@code asURL().toExternalForm()}.
*
* @return a string representation of this resource
*/
public String toString() {
return asURL().toExternalForm();
}
/**
* Defautl implementation returns {@code mWrapped.hashCode()}.
*
* @return {@code mWrapped.hashCode()}
*/
public int hashCode() {
return mWrappedResource.hashCode();
}
/**
* Default implementation
*
* @param pObject
* @return
*/
public boolean equals(Object pObject) {
return pObject instanceof AbstractResource
&& mWrappedResource.equals(((AbstractResource) pObject).mWrappedResource);
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
/**
* Abstract base class for {@code TokenIterator}s to extend.
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/AbstractTokenIterator.java#1 $
*/
public abstract class AbstractTokenIterator implements TokenIterator {
/**
* Not supported.
*
* @throws UnsupportedOperationException {@code remove} is not supported by
* this Iterator.
*/
public void remove() {
// TODO: This is not difficult:
// - Convert String to StringBuilder in constructor
// - delete(pos, mNext.lenght())
// - Add toString() method
// BUT: Would it ever be useful? :-)
throw new UnsupportedOperationException("remove");
}
public final boolean hasMoreTokens() {
return hasNext();
}
/**
* Returns the next element in the iteration as a {@code String}.
* This implementation simply returns {@code (String) next()}.
*
* @return the next element in the iteration.
* @exception java.util.NoSuchElementException iteration has no more elements.
* @see #next()
*/
public final String nextToken() {
return next();
}
/**
* This implementation simply returns {@code hasNext()}.
* @see #hasNext()
*/
public final boolean hasMoreElements() {
return hasNext();
}
/**
* This implementation simply returns {@code next()}.
* @see #next()
*/
public final String nextElement() {
return next();
}
}

View File

@@ -0,0 +1,245 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.beans.IndexedPropertyDescriptor;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.io.Serializable;
/**
* A {@code Map} adapter for a Java Bean.
* <p/>
* Ruhtlessly stolen from
* <a href="http://binkley.blogspot.com/2006/08/mapping-java-bean.html>Binkley's Blog</a>
*/
public final class BeanMap extends AbstractMap<String, Object> implements Serializable, Cloneable {
private final Object mBean;
private transient Set<PropertyDescriptor> mDescriptors;
public BeanMap(Object pBean) throws IntrospectionException {
if (pBean == null) {
throw new IllegalArgumentException("bean == null");
}
mBean = pBean;
mDescriptors = initDescriptors(pBean);
}
private static Set<PropertyDescriptor> initDescriptors(Object pBean) throws IntrospectionException {
final Set<PropertyDescriptor> descriptors = new HashSet<PropertyDescriptor>();
PropertyDescriptor[] propertyDescriptors = Introspector.getBeanInfo(pBean.getClass()).getPropertyDescriptors();
for (PropertyDescriptor descriptor : propertyDescriptors) {
// Skip Object.getClass(), as you probably never want it
if ("class".equals(descriptor.getName()) && descriptor.getPropertyType() == Class.class) {
continue;
}
// Only support simple setter/getters.
if (!(descriptor instanceof IndexedPropertyDescriptor)) {
descriptors.add(descriptor);
}
}
return Collections.unmodifiableSet(descriptors);
}
public Set<Entry<String, Object>> entrySet() {
return new BeanSet();
}
public Object get(final Object pKey) {
return super.get(pKey);
}
public Object put(final String pKey, final Object pValue) {
checkKey(pKey);
for (Entry<String, Object> entry : entrySet()) {
if (entry.getKey().equals(pKey)) {
return entry.setValue(pValue);
}
}
return null;
}
public Object remove(final Object pKey) {
return super.remove(checkKey(pKey));
}
public int size() {
return mDescriptors.size();
}
private String checkKey(final Object pKey) {
if (pKey == null) {
throw new IllegalArgumentException("key == null");
}
// NB - the cast forces CCE if key is the wrong type.
final String name = (String) pKey;
if (!containsKey(name)) {
throw new IllegalArgumentException("Bad key: " + pKey);
}
return name;
}
private Object readResolve() throws IntrospectionException {
// Initialize the property descriptors
mDescriptors = initDescriptors(mBean);
return this;
}
private class BeanSet extends AbstractSet<Entry<String, Object>> {
public Iterator<Entry<String, Object>> iterator() {
return new BeanIterator(mDescriptors.iterator());
}
public int size() {
return mDescriptors.size();
}
}
private class BeanIterator implements Iterator<Entry<String, Object>> {
private final Iterator<PropertyDescriptor> mIterator;
public BeanIterator(final Iterator<PropertyDescriptor> pIterator) {
mIterator = pIterator;
}
public boolean hasNext() {
return mIterator.hasNext();
}
public BeanEntry next() {
return new BeanEntry(mIterator.next());
}
public void remove() {
mIterator.remove();
}
}
private class BeanEntry implements Entry<String, Object> {
private final PropertyDescriptor mDescriptor;
public BeanEntry(final PropertyDescriptor pDescriptor) {
this.mDescriptor = pDescriptor;
}
public String getKey() {
return mDescriptor.getName();
}
public Object getValue() {
return unwrap(new Wrapped() {
public Object run() throws IllegalAccessException, InvocationTargetException {
final Method method = mDescriptor.getReadMethod();
// A write-only bean.
if (method == null) {
throw new UnsupportedOperationException("No getter: " + mDescriptor.getName());
}
return method.invoke(mBean);
}
});
}
public Object setValue(final Object pValue) {
return unwrap(new Wrapped() {
public Object run() throws IllegalAccessException, InvocationTargetException {
final Method method = mDescriptor.getWriteMethod();
// A read-only bean.
if (method == null) {
throw new UnsupportedOperationException("No write method for property: " + mDescriptor.getName());
}
final Object old = getValue();
method.invoke(mBean, pValue);
return old;
}
});
}
public boolean equals(Object pOther) {
if (!(pOther instanceof Map.Entry)) {
return false;
}
Map.Entry entry = (Map.Entry) pOther;
Object k1 = getKey();
Object k2 = entry.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = entry.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2))) {
return true;
}
}
return false;
}
public int hashCode() {
return (getKey() == null ? 0 : getKey().hashCode()) ^
(getValue() == null ? 0 : getValue().hashCode());
}
public String toString() {
return getKey() + "=" + getValue();
}
}
private static interface Wrapped {
Object run() throws IllegalAccessException, InvocationTargetException;
}
private static Object unwrap(final Wrapped wrapped) {
try {
return wrapped.run();
}
catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
catch (final InvocationTargetException e) {
// Javadocs for setValue indicate cast is ok.
throw (RuntimeException) e.getCause();
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.io.Serializable;
/**
* BooleanKey class description.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/BooleanKey.java#1 $
*/
public class BooleanKey extends TypedMap.AbstractKey implements Serializable {
public BooleanKey() {
super();
}
public BooleanKey(String pName) {
super(pName);
}
public boolean isCompatibleValue(Object pValue) {
return pValue instanceof Boolean;
}
}

View File

@@ -0,0 +1,642 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.lang.reflect.Array;
import java.util.*;
/**
* A utility class with some useful collection-related functions.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author <A href="mailto:eirik.torske@twelvemonkeys.no">Eirik Torske</A>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/CollectionUtil.java#3 $
* @todo move makeList and makeSet to StringUtil?
* @see Collections
* @see Arrays
*/
public final class CollectionUtil {
/**
* Testing only.
*
* @param pArgs command line arguents
*/
@SuppressWarnings({"UnusedDeclaration", "UnusedAssignment", "unchecked"})
public static void main(String[] pArgs) {
test();
int howMany = 1000;
if (pArgs.length > 0) {
howMany = Integer.parseInt(pArgs[0]);
}
long start;
long end;
/*
int[] intArr1 = new int[] {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
};
int[] intArr2 = new int[] {
10, 11, 12, 13, 14, 15, 16, 17, 18, 19
};
start = System.currentTimeMillis();
for (int i = 0; i < howMany; i++) {
intArr1 = (int[]) mergeArrays(intArr1, 0, intArr1.length, intArr2, 0, intArr2.length);
}
end = System.currentTimeMillis();
System.out.println("mergeArrays: " + howMany + " * " + intArr2.length + " ints took " + (end - start) + " milliseconds (" + intArr1.length
+ ")");
*/
////////////////////////////////
String[] stringArr1 = new String[]{
"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"
};
/*
String[] stringArr2 = new String[] {
"ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"
};
start = System.currentTimeMillis();
for (int i = 0; i < howMany; i++) {
stringArr1 = (String[]) mergeArrays(stringArr1, 0, stringArr1.length, stringArr2, 0, stringArr2.length);
}
end = System.currentTimeMillis();
System.out.println("mergeArrays: " + howMany + " * " + stringArr2.length + " Strings took " + (end - start) + " milliseconds ("
+ stringArr1.length + ")");
start = System.currentTimeMillis();
while (intArr1.length > stringArr2.length) {
intArr1 = (int[]) subArray(intArr1, 0, intArr1.length - stringArr2.length);
}
end = System.currentTimeMillis();
System.out.println("subArray: " + howMany + " * " + intArr2.length + " ints took " + (end - start) + " milliseconds (" + intArr1.length
+ ")");
start = System.currentTimeMillis();
while (stringArr1.length > stringArr2.length) {
stringArr1 = (String[]) subArray(stringArr1, stringArr2.length);
}
end = System.currentTimeMillis();
System.out.println("subArray: " + howMany + " * " + stringArr2.length + " Strings took " + (end - start) + " milliseconds ("
+ stringArr1.length + ")");
*/
stringArr1 = new String[]{
"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
"fifteen", "sixteen", "seventeen", "eighteen", "nineteen"
};
System.out.println("\nFilterIterators:\n");
List list = Arrays.asList(stringArr1);
Iterator iter = new FilterIterator(list.iterator(), new FilterIterator.Filter() {
public boolean accept(Object pElement) {
return ((String) pElement).length() > 5;
}
});
while (iter.hasNext()) {
String str = (String) iter.next();
System.out.println(str + " has more than 5 letters!");
}
iter = new FilterIterator(list.iterator(), new FilterIterator.Filter() {
public boolean accept(Object pElement) {
return ((String) pElement).length() <= 5;
}
});
while (iter.hasNext()) {
String str = (String) iter.next();
System.out.println(str + " has less than, or exactly 5 letters!");
}
start = System.currentTimeMillis();
for (int i = 0; i < howMany; i++) {
iter = new FilterIterator(list.iterator(), new FilterIterator.Filter() {
public boolean accept(Object pElement) {
return ((String) pElement).length() <= 5;
}
});
while (iter.hasNext()) {
iter.next();
System.out.print("");
}
}
// end = System.currentTimeMillis();
// System.out.println("Time: " + (end - start) + " ms");
// System.out.println("\nClosureCollection:\n");
// forEach(list, new Closure() {
//
// public void execute(Object pElement) {
//
// String str = (String) pElement;
//
// if (str.length() > 5) {
// System.out.println(str + " has more than 5 letters!");
// }
// else {
// System.out.println(str + " has less than, or exactly 5 letters!");
// }
// }
// });
// start = System.currentTimeMillis();
// for (int i = 0; i < howMany; i++) {
// forEach(list, new Closure() {
//
// public void execute(Object pElement) {
//
// String str = (String) pElement;
//
// if (str.length() <= 5) {
// System.out.print("");
// }
// }
// });
// }
// end = System.currentTimeMillis();
// System.out.println("Time: " + (end - start) + " ms");
}
// Disallow creating objects of this type
private CollectionUtil() {}
/**
* Merges two arrays into a new array. Elements from array1 and array2 will
* be copied into a new array, that has array1.length + array2.length
* elements.
*
* @param pArray1 First array
* @param pArray2 Second array, must be compatible with (assignable from)
* the first array
* @return A new array, containing the values of array1 and array2. The
* array (wrapped as an object), will have the length of array1 +
* array2, and can be safely cast to the type of the array1
* parameter.
* @see #mergeArrays(Object,int,int,Object,int,int)
* @see java.lang.System#arraycopy(Object,int,Object,int,int)
*/
public static Object mergeArrays(Object pArray1, Object pArray2) {
return mergeArrays(pArray1, 0, Array.getLength(pArray1), pArray2, 0, Array.getLength(pArray2));
}
/**
* Merges two arrays into a new array. Elements from pArray1 and pArray2 will
* be copied into a new array, that has pLength1 + pLength2 elements.
*
* @param pArray1 First array
* @param pOffset1 the offset into the first array
* @param pLength1 the number of elements to copy from the first array
* @param pArray2 Second array, must be compatible with (assignable from)
* the first array
* @param pOffset2 the offset into the second array
* @param pLength2 the number of elements to copy from the second array
* @return A new array, containing the values of pArray1 and pArray2. The
* array (wrapped as an object), will have the length of pArray1 +
* pArray2, and can be safely cast to the type of the pArray1
* parameter.
* @see java.lang.System#arraycopy(Object,int,Object,int,int)
*/
@SuppressWarnings({"SuspiciousSystemArraycopy"})
public static Object mergeArrays(Object pArray1, int pOffset1, int pLength1, Object pArray2, int pOffset2, int pLength2) {
Class class1 = pArray1.getClass();
Class type = class1.getComponentType();
// Create new array of the new length
Object array = Array.newInstance(type, pLength1 + pLength2);
System.arraycopy(pArray1, pOffset1, array, 0, pLength1);
System.arraycopy(pArray2, pOffset2, array, pLength1, pLength2);
return array;
}
/**
* Creates an array containing a subset of the original array.
* If the sub array is same length as the original
* ({@code pStart == 0}), the original array will be returned.
*
* @param pArray the origianl array
* @param pStart the start index of the original array
* @return a subset of the original array, or the original array itself,
* if {@code pStart} is 0.
*
* @throws IllegalArgumentException if {@code pArray} is {@code null} or
* if {@code pArray} is not an array.
* @throws ArrayIndexOutOfBoundsException if {@code pStart} < 0
*/
public static Object subArray(Object pArray, int pStart) {
return subArray(pArray, pStart, -1);
}
/**
* Creates an array containing a subset of the original array.
* If the {@code pLength} parameter is negative, it will be ignored.
* If there are not {@code pLength} elements in the original array
* after {@code pStart}, the {@code pLength} paramter will be
* ignored.
* If the sub array is same length as the original, the original array will
* be returned.
*
* @param pArray the origianl array
* @param pStart the start index of the original array
* @param pLength the length of the new array
* @return a subset of the original array, or the original array itself,
* if {@code pStart} is 0 and {@code pLength} is either
* negative, or greater or equal to {@code pArray.length}.
*
* @throws IllegalArgumentException if {@code pArray} is {@code null} or
* if {@code pArray} is not an array.
* @throws ArrayIndexOutOfBoundsException if {@code pStart} < 0
*/
@SuppressWarnings({"SuspiciousSystemArraycopy"})
public static Object subArray(Object pArray, int pStart, int pLength) {
if (pArray == null) {
throw new IllegalArgumentException("array == null");
}
// Get component type
Class type;
// Sanity check start index
if (pStart < 0) {
throw new ArrayIndexOutOfBoundsException(pStart + " < 0");
}
// Check if argument is array
else if ((type = pArray.getClass().getComponentType()) == null) {
// NOTE: No need to test class.isArray(), really
throw new IllegalArgumentException("Not an array: " + pArray);
}
// Store original length
int originalLength = Array.getLength(pArray);
// Find new length, stay within bounds
int newLength = (pLength < 0)
? Math.max(0, originalLength - pStart)
: Math.min(pLength, Math.max(0, originalLength - pStart));
// Store result
Object result;
if (newLength < originalLength) {
// Create subarray & copy into
result = Array.newInstance(type, newLength);
System.arraycopy(pArray, pStart, result, 0, newLength);
}
else {
// Just return original array
// NOTE: This can ONLY happen if pStart == 0
result = pArray;
}
// Return
return result;
}
public static <T> Iterator<T> iterator(final Enumeration<T> pEnum) {
return new Iterator<T>() {
public boolean hasNext() {
return pEnum.hasMoreElements();
}
public T next() {
return pEnum.nextElement();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
/**
* Adds all elements of the iterator to the collection.
*
* @param pCollection the collection
* @param pIterator the elements to add
*
* @throws UnsupportedOperationException if {@code add} is not supported by
* the given collection.
* @throws ClassCastException class of the specified element prevents it
* from being added to this collection.
* @throws NullPointerException if the specified element is null and this
* collection does not support null elements.
* @throws IllegalArgumentException some aspect of this element prevents
* it from being added to this collection.
*/
public static <E> void addAll(Collection<E> pCollection, Iterator<? extends E> pIterator) {
while (pIterator.hasNext()) {
pCollection.add(pIterator.next());
}
}
// Is there a usecase where Arrays.asList(pArray).iterator() can't ne used?
/**
* Creates a thin {@link Iterator} wrapper around an array.
*
* @param pArray the array to iterate
* @return a new {@link ListIterator}
* @throws IllegalArgumentException if {@code pArray} is {@code null},
* {@code pStart < 0}, or
* {@code pLength > pArray.length - pStart}
*/
public static <E> ListIterator<E> iterator(final E[] pArray) {
return iterator(pArray, 0, pArray.length);
}
/**
* Creates a thin {@link Iterator} wrapper around an array.
*
* @param pArray the array to iterate
* @param pStart the offset into the array
* @param pLength the number of elements to include in the iterator
* @return a new {@link ListIterator}
* @throws IllegalArgumentException if {@code pArray} is {@code null},
* {@code pStart < 0}, or
* {@code pLength > pArray.length - pStart}
*/
public static <E> ListIterator<E> iterator(final E[] pArray, final int pStart, final int pLength) {
return new ArrayIterator<E>(pArray, pStart, pLength);
}
/**
* Creates an inverted mapping of the key/value pairs in the given map.
*
* @param pSource the source map
* @return a new {@code Map} of same type as {@code pSource}
* @throws IllegalArgumentException if {@code pSource == null},
* or if a new map can't be instantiated,
* or if source map contains duplaicates.
*
* @see #invert(java.util.Map, java.util.Map, DuplicateHandler)
*/
public static <K, V> Map<V, K> invert(Map<K, V> pSource) {
return invert(pSource, null, null);
}
/**
* Creates an inverted mapping of the key/value pairs in the given map.
* Optionally, a duplicate handler may be specified, to resolve duplicate keys in the result map.
*
* @param pSource the source map
* @param pResult the map used to contain the result, may be {@code null},
* in that case a new {@code Map} of same type as {@code pSource} is created.
* The result map <em>should</em> be empty, otherwise duplicate values will need to be resolved.
* @param pHandler duplicate handler, may be {@code null} if source map don't contain dupliate values
* @return {@code pResult}, or a new {@code Map} if {@code pResult == null}
* @throws IllegalArgumentException if {@code pSource == null},
* or if result map is {@code null} and a new map can't be instantiated,
* or if source map contains duplicate values and {@code pHandler == null}.
*/
// TODO: Create a better duplicate handler, that takes Entries as parameters and returns an Entry
public static <K, V> Map<V, K> invert(Map<K, V> pSource, Map<V, K> pResult, DuplicateHandler<K> pHandler) {
if (pSource == null) {
throw new IllegalArgumentException("source == null");
}
Map<V, K> result = pResult;
if (result == null) {
try {
//noinspection unchecked
result = pSource.getClass().newInstance();
}
catch (InstantiationException e) {
// Handled below
}
catch (IllegalAccessException e) {
// Handled below
}
if (result == null) {
throw new IllegalArgumentException("result == null and source class " + pSource.getClass() + " cannot be instantiated.");
}
}
// Copy entries into result map, inversed
Set<Map.Entry<K, V>> entries = pSource.entrySet();
for (Map.Entry<K, V> entry : entries) {
V newKey = entry.getValue();
K newValue = entry.getKey();
// Handle dupliates
if (result.containsKey(newKey)) {
if (pHandler != null) {
newValue = pHandler.resolve(result.get(newKey), newValue);
}
else {
throw new IllegalArgumentException("Result would include duplicate keys, but no DuplicateHandler specified.");
}
}
result.put(newKey, newValue);
}
return result;
}
public static <T> Comparator<T> reverseOrder(Comparator<T> pOriginal) {
return new ReverseComparator<T>(pOriginal);
}
private static class ReverseComparator<T> implements Comparator<T> {
private Comparator<T> mComparator;
public ReverseComparator(Comparator<T> pComparator) {
mComparator = pComparator;
}
public int compare(T pLeft, T pRight) {
int result = mComparator.compare(pLeft, pRight);
// We can't simply return -result, as -Integer.MIN_VALUE == Integer.MIN_VALUE.
return -(result | (result >>> 1));
}
}
@SuppressWarnings({"unchecked", "UnusedDeclaration"})
static <T extends Iterator<? super E>, E> T generify(final Iterator<?> pIterator, final Class<E> pElementType) {
return (T) pIterator;
}
@SuppressWarnings({"unchecked", "UnusedDeclaration"})
static <T extends Collection<? super E>, E> T generify(final Collection<?> pCollection, final Class<E> pElementType) {
return (T) pCollection;
}
@SuppressWarnings({"unchecked", "UnusedDeclaration"})
static <T extends Map<? super K, ? super V>, K, V> T generify(final Map<?, ?> pMap, final Class<K> pKeyType, final Class<V> pValueType) {
return (T) pMap;
}
@SuppressWarnings({"unchecked"})
static <T extends Collection<? super E>, E> T generify2(Collection<?> pCollection) {
return (T) pCollection;
}
@SuppressWarnings({"UnusedDeclaration"})
static void test() {
List list = Collections.singletonList("foo");
@SuppressWarnings({"unchecked"})
Set set = new HashSet(list);
List<String> strs0 = CollectionUtil.generify(list, String.class);
List<Object> objs0 = CollectionUtil.generify(list, String.class);
// List<String> strs01 = CollectionUtil.generify(list, Object.class); // Not okay
try {
List<String> strs1 = CollectionUtil.generify(set, String.class); // Not ok, runtime CCE unless set is null
}
catch (RuntimeException e) {
e.printStackTrace();
}
try {
ArrayList<String> strs01 = CollectionUtil.generify(list, String.class); // Not ok, runtime CCE unless list is null
}
catch (RuntimeException e) {
e.printStackTrace();
}
Set<String> setstr1 = CollectionUtil.generify(set, String.class);
Set<Object> setobj1 = CollectionUtil.generify(set, String.class);
try {
Set<Object> setobj44 = CollectionUtil.generify(list, String.class); // Not ok, runtime CCE unless list is null
}
catch (RuntimeException e) {
e.printStackTrace();
}
List<String> strs2 = CollectionUtil.<List<String>, String>generify2(list);
List<Object> objs2 = CollectionUtil.<List<Object>, String>generify2(list);
// List<String> morestrs = CollectionUtil.<List<Object>, String>generify2(list); // Not ok
try {
List<String> strs3 = CollectionUtil.<List<String>, String>generify2(set); // Not ok, runtime CCE unless set is null
}
catch (RuntimeException e) {
e.printStackTrace();
}
}
private static class ArrayIterator<E> implements ListIterator<E> {
private int mIndex;
private final int mStart;
private final int mLength;
private final E[] mArray;
public ArrayIterator(E[] pArray, int pStart, int pLength) {
if (pArray == null) {
throw new IllegalArgumentException("array == null");
}
if (pStart < 0) {
throw new IllegalArgumentException("start < 0");
}
if (pLength > pArray.length - pStart) {
throw new IllegalArgumentException("length > array.length - start");
}
mArray = pArray;
mStart = pStart;
mLength = pLength;
mIndex = mStart;
}
public boolean hasNext() {
return mIndex < mLength + mStart;
}
public E next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
try {
return mArray[mIndex++];
}
catch (ArrayIndexOutOfBoundsException e) {
NoSuchElementException nse = new NoSuchElementException(e.getMessage());
nse.initCause(e);
throw nse;
}
}
public void remove() {
throw new UnsupportedOperationException();
}
public void add(E pElement) {
throw new UnsupportedOperationException();
}
public boolean hasPrevious() {
return mIndex > mStart;
}
public int nextIndex() {
return mIndex + 1;
}
public E previous() {
if (!hasPrevious()) {
throw new NoSuchElementException();
}
try {
return mArray[mIndex--];
}
catch (ArrayIndexOutOfBoundsException e) {
NoSuchElementException nse = new NoSuchElementException(e.getMessage());
nse.initCause(e);
throw nse;
}
}
public int previousIndex() {
return mIndex - 1;
}
public void set(E pElement) {
mArray[mIndex] = pElement;
}
}
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
/**
* DuplicateHandler
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/DuplicateHandler.java#2 $
*/
public interface DuplicateHandler<T> {
/**
* Resolves duplicates according to a certain strategy.
*
* @param pOld the old value
* @param pNew the new value
*
* @return the resolved value.
*
* @throws IllegalArgumentException is the arguments cannot be resolved for
* some reason.
*/
public T resolve(T pOld, T pNew);
/**
* Will use the first (old) value. Any new values will be discarded.
*
* @see CollectionUtil#invert(java.util.Map, java.util.Map, DuplicateHandler)
*/
public final static DuplicateHandler<?> USE_FIRST_VALUE = new DuplicateHandler() {
/**
* Returns {@code pOld}.
*
* @param pOld the old value
* @param pNew the new value
*
* @return {@code pOld}
*/
public Object resolve(Object pOld, Object pNew) {
return pOld;
}
};
/**
* Will use the last (new) value. Any old values will be discarded
* (overwritten).
*
* @see CollectionUtil#invert(java.util.Map, java.util.Map, DuplicateHandler)
*/
public final static DuplicateHandler<?> USE_LAST_VALUE = new DuplicateHandler() {
/**
* Returns {@code pNew}.
*
* @param pOld the old value
* @param pNew the new value
*
* @return {@code pNew}
*/
public Object resolve(Object pOld, Object pNew) {
return pNew;
}
};
/**
* Converts duplicats to an {@code Object} array.
*
* @see CollectionUtil#invert(java.util.Map, java.util.Map, DuplicateHandler)
*/
public final static DuplicateHandler<?> DUPLICATES_AS_ARRAY = new DuplicateHandler() {
/**
* Returns an {@code Object} array, containing {@code pNew} as its
* last element.
*
* @param pOld the old value
* @param pNew the new value
*
* @return an {@code Object} array, containing {@code pNew} as its
* last element.
*/
public Object resolve(Object pOld, Object pNew) {
Object[] result;
if (pOld instanceof Object[]) {
Object[] old = ((Object[]) pOld);
result = new Object[old.length + 1];
System.arraycopy(old, 0, result, 0, old.length);
result[old.length] = pNew;
}
else {
result = new Object[] {pOld, pNew};
}
return result;
}
};
/**
* Converts duplicates to a comma-separated {@code String}.
* Note that all values should allready be {@code String}s if using this
* handler.
*
* @see CollectionUtil#invert(java.util.Map, java.util.Map, DuplicateHandler)
*/
public final static DuplicateHandler<String> DUPLICATES_AS_CSV = new DuplicateHandler<String>() {
/**
* Returns a comma-separated {@code String}, with the string
* representation of {@code pNew} as the last element.
*
* @param pOld the old value
* @param pNew the new value
*
* @return a comma-separated {@code String}, with the string
* representation of {@code pNew} as the last element.
*/
public String resolve(String pOld, String pNew) {
StringBuilder result = new StringBuilder(String.valueOf(pOld));
result.append(',');
result.append(pNew);
return result.toString();
}
};
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.util.Map;
/**
* Call-back inteface for {@code Map} implementations that have mappings that
* may expire.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/ExpiringMap.java#1 $
*/
public interface ExpiringMap<K, V> extends Map<K, V> {
/**
* May be used by clients as a call-back to notify when mappings expire from
* the map.
*
* @param pRemoved the removed mapping
*/
void processRemoved(Entry<K, V> pRemoved);
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.io.InputStream;
import java.io.IOException;
import java.io.File;
import java.io.FileInputStream;
import java.net.MalformedURLException;
import java.net.URL;
/**
* FileResource class description.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/FileResource.java#1 $
*/
final class FileResource extends AbstractResource {
/**
* Creates a {@code FileResource}.
*
* @param pResourceId the resource id
* @param pFile the file resource
*/
public FileResource(Object pResourceId, File pFile) {
super(pResourceId, pFile);
}
private File getFile() {
return (File) mWrappedResource;
}
public URL asURL() {
try {
return getFile().toURL();
}
catch (MalformedURLException e) {
throw new IllegalStateException("The file \"" + getFile().getAbsolutePath()
+ "\" is not parseable as an URL: " + e.getMessage());
}
}
public InputStream asStream() throws IOException {
return new FileInputStream(getFile());
}
public long lastModified() {
return getFile().lastModified();
}
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* Wraps (decorates) an {@code Iterator} with extra functionality, to allow
* element filtering. Each
* element is filtered against the given {@code Filter}, and only elements
* that are {@code accept}ed are returned by the {@code next} method.
* <p/>
* The optional {@code remove} operation is implemented, but may throw
* {@code UnsupportedOperationException} if the underlying iterator does not
* support the remove operation.
*
* @see FilterIterator.Filter
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/FilterIterator.java#1 $
*/
public class FilterIterator<E> implements Iterator<E> {
protected final Filter<E> mFilter;
protected final Iterator<E> mIterator;
private E mNext = null;
private E mCurrent = null;
/**
* Creates a {@code FilterIterator} that wraps the {@code Iterator}. Each
* element is filtered against the given {@code Filter}, and only elements
* that are {@code accept}ed are returned by the {@code next} method.
*
* @param pIterator the iterator to filter
* @param pFilter the filter
* @see FilterIterator.Filter
*/
public FilterIterator(final Iterator<E> pIterator, final Filter<E> pFilter) {
if (pIterator == null) {
throw new IllegalArgumentException("iterator == null");
}
if (pFilter == null) {
throw new IllegalArgumentException("filter == null");
}
mIterator = pIterator;
mFilter = pFilter;
}
/**
* Returns {@code true} if the iteration has more elements. (In other
* words, returns {@code true} if {@code next} would return an element
* rather than throwing an exception.)
*
* @return {@code true} if the iterator has more elements.
* @see FilterIterator.Filter#accept
*/
public boolean hasNext() {
while (mNext == null && mIterator.hasNext()) {
E element = mIterator.next();
if (mFilter.accept(element)) {
mNext = element;
break;
}
}
return mNext != null;
}
/**
* Returns the next element in the iteration.
*
* @return the next element in the iteration.
* @see FilterIterator.Filter#accept
*/
public E next() {
if (hasNext()) {
mCurrent = mNext;
// Make sure we advance next time
mNext = null;
return mCurrent;
}
else {
throw new NoSuchElementException("Iteration has no more elements.");
}
}
/**
* Removes from the underlying collection the last element returned by the
* iterator (optional operation). This method can be called only once per
* call to {@code next}. The behavior of an iterator is unspecified if
* the underlying collection is modified while the iteration is in
* progress in any way other than by calling this method.
*/
public void remove() {
if (mCurrent != null) {
mIterator.remove();
}
else {
throw new IllegalStateException("Iteration has no current element.");
}
}
/**
* Used to tests whether or not an element fulfills certain criteria, and
* hence should be accepted by the FilterIterator instance.
*/
public static interface Filter<E> {
/**
* Tests whether or not the element fulfills certain criteria, and hence
* should be accepted.
*
* @param pElement the element to test
* @return {@code true} if the object is accepted, otherwise
* {@code false}
*/
public boolean accept(E pElement);
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.io.Serializable;
/**
* FloatKey class description.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/FloatKey.java#1 $
*/
public class FloatKey extends TypedMap.AbstractKey implements Serializable {
public FloatKey() {
super();
}
public FloatKey(String pName) {
super(pName);
}
public boolean isCompatibleValue(Object pValue) {
return pValue instanceof Float;
}
}

View File

@@ -0,0 +1,176 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.util.Iterator;
import java.util.Map;
import java.io.Serializable;
/**
* A {@code Map} decorator that makes the mappings in the backing map
* case insensitive
* (this is implemented by converting all keys to uppercase),
* if the keys used are {@code Strings}. If the keys
* used are not {@code String}s, it wil work as a normal
* {@code java.util.Map}.
* <p/>
*
* @see java.util.Map
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
*/
public class IgnoreCaseMap<V> extends AbstractDecoratedMap<String, V> implements Serializable, Cloneable {
/**
* Constructs a new empty {@code Map}.
* The backing map will be a {@link java.util.HashMap}
*/
public IgnoreCaseMap() {
super();
}
/**
* Constructs a new {@code Map} with the same key-value mappings as the
* given {@code Map}.
* The backing map will be a {@link java.util.HashMap}
* <p/>
* NOTE: As the keys in the given map parameter will be converted to
* uppercase (if they are strings), any duplicate key/value pair where
* {@code key instanceof String && key.equalsIgnoreCase(otherKey)}
* is true, will be lost.
*
* @param pMap the map whose mappings are to be placed in this map.
*/
public IgnoreCaseMap(Map<String, ? extends V> pMap) {
super(pMap);
}
/**
* Constructs a new {@code Map} with the same key-value mappings as the
* given {@code Map}.
* <p/>
* NOTE: The backing map is structuraly cahnged, and it should NOT be
* accessed directly, after the wrapped map is created.
* <p/>
* NOTE: As the keys in the given map parameter will be converted to
* uppercase (if they are strings), any duplicate key/value pair where
* {@code key instanceof String && key.equalsIgnoreCase(otherKey)}
* is true, will be lost.
*
* @param pBacking the backing map of this map. Must be either empty, or
* the same map as {@code pContents}.
* @param pContents the map whose mappings are to be placed in this map.
* May be {@code null}
*
* @throws IllegalArgumentException if {@code pBacking} is {@code null}
* @throws IllegalArgumentException if {@code pBacking} differs from
* {@code pContent} and is not empty.
*/
public IgnoreCaseMap(Map pBacking, Map<String, ? extends V> pContents) {
super(pBacking, pContents);
}
/**
* Maps the specified key to the specified value in this map.
* Note: If the key used is a string, the key will not be case-sensitive.
*
* @param pKey the map key.
* @param pValue the value.
* @return the previous value of the specified key in this map,
* or null if it did not have one.
*/
public V put(String pKey, V pValue) {
String key = (String) toUpper(pKey);
return unwrap(mEntries.put(key, new BasicEntry<String, V>(key, pValue)));
}
private V unwrap(Entry<String, V> pEntry) {
return pEntry != null ? pEntry.getValue() : null;
}
/**
* Returns the value to which the specified key is mapped in this
* map.
* Note: If the key used is a string, the key will not be case-sensitive.
*
* @param pKey a key in the map
* @return the value to which the key is mapped in this map; null if
* the key is not mapped to any value in this map.
*/
public V get(Object pKey) {
return unwrap(mEntries.get(toUpper(pKey)));
}
/**
* Removes the key (and its corresponding value) from this map. This
* method does nothing if the key is not in the map.
* Note: If the key used is a string, the key will not be case-sensitive.
*
* @param pKey the key that needs to be removed.
* @return the value to which the key had been mapped in this map,
* or null if the key did not have a mapping.
*/
public V remove(Object pKey) {
return unwrap(mEntries.remove(toUpper(pKey)));
}
/**
* Tests if the specified object is a key in this map.
* Note: If the key used is a string, the key will not be case-sensitive.
*
* @param pKey possible key.
* @return true if and only if the specified object is a key in this
* map, as determined by the equals method; false otherwise.
*/
public boolean containsKey(Object pKey) {
return mEntries.containsKey(toUpper(pKey));
}
/**
* Converts the parameter to uppercase, if it's a String.
*/
protected static Object toUpper(final Object pObject) {
if (pObject instanceof String) {
return ((String) pObject).toUpperCase();
}
return pObject;
}
protected Iterator<Entry<String, V>> newEntryIterator() {
return (Iterator) mEntries.entrySet().iterator();
}
protected Iterator<String> newKeyIterator() {
return mEntries.keySet().iterator();
}
protected Iterator<V> newValueIterator() {
return (Iterator<V>) mEntries.values().iterator();
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.io.Serializable;
/**
* IntegerKey class description.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/IntegerKey.java#1 $
*/
public class IntegerKey extends TypedMap.AbstractKey implements Serializable {
public IntegerKey(String pName) {
super(pName);
}
public IntegerKey() {
}
public boolean isCompatibleValue(Object pValue) {
return pValue instanceof Integer;
}
}

View File

@@ -0,0 +1,214 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.Iterator;
/**
* Map implementation with size limit, that keeps its entries in LRU
* (least recently used) order, also known as <em>access-order</em>.
* When the size limit is reached, the least recently accessed mappings are
* removed. The number of mappings to be removed from the map, is
* controlled by the trim factor.
* <p>
* <ul>
* <li>Default size limit is 1000 elements.
* See {@link #setMaxSize(int)}/{@link #getMaxSize()}.</li>
* <li>Default trim factor is 1% ({@code 0.01f}).
* See {@link #setTrimFactor(float)}/{@link #getTrimFactor()}.</li>
* </ul>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/LRUHashMap.java#1 $
*/
public class LRUHashMap<K, V> extends LinkedHashMap<K, V> implements ExpiringMap<K, V> {
private int mMaxSize = 1000;
private float mTrimFactor = 0.01f;
/**
* Creates an LRUHashMap with default max size (1000 entries).
*
* <small>This is constructor is here to comply with the reccomendations for
* "standard" constructors in the {@code Map} interface.</small>
*
* @see #LRUHashMap(int)
*/
public LRUHashMap() {
super(16, .75f, true);
}
/**
* Creates an LRUHashMap with the given max size.
*
* @param pMaxSize size limit
*/
public LRUHashMap(int pMaxSize) {
super(16, .75f, true);
setMaxSize(pMaxSize);
}
/**
* Creates an LRUHashMap with initial mappings from the given map,
* and default max size (1000 entries).
*
* <small>This is constructor is here to comply with the reccomendations for
* "standard" constructors in the {@code Map} interface.</small>
*
* @param pContents the map whose mappings are to be placed in this map.
* May be {@code null}.
*
* @see #LRUHashMap(java.util.Map, int)
*/
public LRUHashMap(Map<? extends K, ? extends V> pContents) {
super(16, .75f, true);
putAll(pContents);
}
/**
* Creates an LRUHashMap with initial mappings from the given map,
* and the given max size.
*
* @param pContents the map whose mappings are to be placed in this map.
* May be {@code null}.
* @param pMaxSize size limit
*/
public LRUHashMap(Map<? extends K, ? extends V> pContents, int pMaxSize) {
super(16, .75f, true);
setMaxSize(pMaxSize);
putAll(pContents);
}
/**
* Returns the maximum number of mappings in this map.
*
* @return the size limit
*/
public int getMaxSize() {
return mMaxSize;
}
/**
* Sets the maximum number of elements in this map.
*
* If the current size is greater than the new max size, the map will be
* trimmed to fit the new max size constraint.
*
* @see #removeLRU()
*
* @param pMaxSize new size limit
*/
public void setMaxSize(int pMaxSize) {
if (pMaxSize < 0) {
throw new IllegalArgumentException("max size must be positive");
}
mMaxSize = pMaxSize;
while(size() > mMaxSize) {
removeLRU();
}
}
/**
* Returns the current trim factor.
* <p>
* The trim factor controls how many percent of the maps current size is
* reclaimed, when performing an {@code removeLRU} operation.
* Defaults to 1% ({@code 0.01f}).
*
* @return the current trim factor
*/
public float getTrimFactor() {
return mTrimFactor;
}
/**
* Sets the trim factor.
* <p>
* The trim factor controls how many percent of the maps current size is
* reclaimed, when performing an {@code removeLRU} operation.
* Defaults to 1% ({@code 0.01f}).
*
* @param pTrimFactor the new trim factor. Acceptable values are between
* 0 (inclusive) and 1 (exclusive).
*
* @see #removeLRU()
*/
public void setTrimFactor(float pTrimFactor) {
if (pTrimFactor < 0f || pTrimFactor >= 1f) {
throw new IllegalArgumentException("trim factor must be between 0 and 1");
}
mTrimFactor = pTrimFactor;
}
/**
* always returns {@code false}, and instead invokes {@code removeLRU()}
* if {@code size >= maxSize}.
*/
protected boolean removeEldestEntry(Map.Entry<K, V> pEldest) {
// NOTE: As removeLRU() may remove more than one entry, this is better
// than simply removing the eldest entry.
if (size() >= mMaxSize) {
removeLRU();
}
return false;
}
/**
* Default implementation does nothing.
* May be used by clients as a call-back to notify when mappings expire from
* the map.
*
* @param pRemoved the removed mapping
*/
public void processRemoved(Map.Entry<K, V> pRemoved) {
}
/**
* Removes the least recently used mapping(s) from this map.
* <p>
* How many mappings are removed from the map, is controlled by the
* trim factor.
* In any case, at least one mapping will be removed.
*
* @see #getTrimFactor()
*/
public void removeLRU() {
int removeCount = (int) Math.max((size() * mTrimFactor), 1);
Iterator<Map.Entry<K, V>> entries = entrySet().iterator();
while ((removeCount--) > 0 && entries.hasNext()) {
entries.next();
entries.remove();
}
}
}

View File

@@ -0,0 +1,229 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.util.Map;
/**
* Map implementation with size limit, that keeps its entries in LRU
* (least recently used) order, also known as <em>access-order</em>.
* When the size limit is reached, the least recently accessed mappings are
* removed. The number of mappings to be removed from the map, is
* controlled by the trim factor.
* <p>
* <ul>
* <li>Default size limit is 1000 elements.
* See {@link #setMaxSize(int)}/{@link #getMaxSize()}.</li>
* <li>Default trim factor is 1% ({@code 0.01f}).
* See {@link #setTrimFactor(float)}/{@link #getTrimFactor()}.</li>
* </ul>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/LRUMap.java#1 $
*/
public class LRUMap<K, V> extends LinkedMap<K, V> implements ExpiringMap<K, V> {
private int mMaxSize = 1000;
private float mTrimFactor = 0.01f;
/**
* Creates an LRUMap with default max size (1000 entries).
*
* <small>This is constructor is here to comply with the reccomendations for
* "standard" constructors in the {@code Map} interface.</small>
*
* @see #LRUMap(int)
*/
public LRUMap() {
super(null, true);
}
/**
* Creates an LRUMap with the given max size.
*
* @param pMaxSize size limit
*/
public LRUMap(int pMaxSize) {
super(null, true);
setMaxSize(pMaxSize);
}
/**
* Creates an LRUMap with initial mappings from the given map,
* and default max size (1000 entries).
*
* <small>This is constructor is here to comply with the reccomendations for
* "standard" constructors in the {@code Map} interface.</small>
*
* @param pContents the map whose mappings are to be placed in this map.
* May be {@code null}.
*
* @see #LRUMap(Map, int)
*/
public LRUMap(Map<? extends K, ? extends V> pContents) {
super(pContents, true);
}
/**
* Creates an LRUMap with initial mappings from the given map,
* and the given max size.
*
* @param pContents the map whose mappings are to be placed in this map.
* May be {@code null}.
* @param pMaxSize size limit
*/
public LRUMap(Map<? extends K, ? extends V> pContents, int pMaxSize) {
super(pContents, true);
setMaxSize(pMaxSize);
}
/**
* Creates an LRUMap with initial mappings from the given map,
* and the given max size.
*
* @param pBacking the backing map of this map. Must be either empty, or
* the same map as {@code pContents}.
* @param pContents the map whose mappings are to be placed in this map.
* May be {@code null}.
* @param pMaxSize max size
*/
public LRUMap(Map<K, Entry<K, V>> pBacking, Map<? extends K, ? extends V> pContents, int pMaxSize) {
super(pBacking, pContents, true);
setMaxSize(pMaxSize);
}
/**
* Returns the maximum number of mappings in this map.
*
* @return the size limit
*/
public int getMaxSize() {
return mMaxSize;
}
/**
* Sets the maximum number of elements in this map.
*
* If the current size is greater than the new max size, the map will be
* trimmed to fit the new max size constraint.
*
* @see #removeLRU()
*
* @param pMaxSize new size limit
*/
public void setMaxSize(int pMaxSize) {
if (pMaxSize < 0) {
throw new IllegalArgumentException("max size must be positive");
}
mMaxSize = pMaxSize;
while(size() > mMaxSize) {
removeLRU();
}
}
/**
* Returns the current trim factor.
* <p>
* The trim factor controls how many percent of the maps current size is
* reclaimed, when performing an {@code removeLRU} operation.
* Defaults to 1% ({@code 0.01f}).
*
* @return the current trim factor
*/
public float getTrimFactor() {
return mTrimFactor;
}
/**
* Sets the trim factor.
* <p>
* The trim factor controls how many percent of the maps current size is
* reclaimed, when performing an {@code removeLRU} operation.
* Defaults to 1% ({@code 0.01f}).
*
* @param pTrimFactor the new trim factor. Acceptable values are between
* 0 (inclusive) and 1 (exclusive).
*
* @see #removeLRU()
*/
public void setTrimFactor(float pTrimFactor) {
if (pTrimFactor < 0f || pTrimFactor >= 1f) {
throw new IllegalArgumentException("trim factor must be between 0 and 1");
}
mTrimFactor = pTrimFactor;
}
/**
* always returns {@code false}, and instead invokes {@code removeLRU()}
* if {@code size >= maxSize}.
*/
protected boolean removeEldestEntry(Entry pEldest) {
// NOTE: As removeLRU() may remove more than one entry, this is better
// than simply removing the eldest entry.
if (size() >= mMaxSize) {
removeLRU();
}
return false;
}
protected Entry<K, V> removeEntry(Entry<K, V> pEntry) {
Entry<K, V> entry = super.removeEntry(pEntry);
processRemoved(pEntry);
return entry;
}
/**
* Default implementation does nothing.
* May be used by clients as a call-back to notify when mappings expire from
* the map.
*
* @param pRemoved the removed mapping
*/
public void processRemoved(Entry<K, V> pRemoved) {
}
/**
* Removes the least recently used mapping(s) from this map.
* <p>
* How many mappings are removed from the map, is controlled by the
* trim factor.
* In any case, at least one mapping will be removed.
*
* @see #getTrimFactor()
*/
public void removeLRU() {
int removeCount = (int) Math.max((size() * mTrimFactor), 1);
while ((removeCount--) > 0) {
removeEntry(mHead.mNext);
}
}
}

View File

@@ -0,0 +1,466 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.util.*;
import java.io.Serializable;
/**
* Generic map and linked list implementation of the {@code Map} interface,
* with predictable iteration order.
* <p>
* Resembles {@code LinkedHashMap} from JDK 1.4+, but is backed by a generic
* {@code Map}, rather than implementing a particular algoritm.
* <p>
* This linked list defines the iteration ordering, which is normally the order
* in which keys were inserted into the map (<em>insertion-order</em>).
* Note that insertion order is not affected if a key is <em>re-inserted</em>
* into the map (a key {@code k} is reinserted into a map {@code m} if
* {@code m.put(k, v)} is invoked when {@code m.containsKey(k)} would return
* {@code true} immediately prior to the invocation).
* <p>
* A special {@link #LinkedMap(boolean) constructor} is provided to create a
* linked hash map whose order of iteration is the order in which its entries
* were last accessed, from least-recently accessed to most-recently
* (<em>access-order</em>).
* This kind of map is well-suited to building LRU caches.
* Invoking the {@code put} or {@code get} method results in an access to the
* corresponding entry (assuming it exists after the invocation completes).
* The {@code putAll} method generates one entry access for each mapping in
* the specified map, in the order that key-value mappings are provided by the
* specified map's entry set iterator.
* <em>No other methods generate entry accesses.</em>
* In particular, operations on collection-views do not affect the order of
* iteration of the backing map.
* <p>
* The {@link #removeEldestEntry(Map.Entry)} method may be overridden to impose
* a policy for removing stale mappings automatically when new mappings are
* added to the map.
*
* @author inspired by LinkedHashMap from JDK 1.4+, by Josh Bloch
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/LinkedMap.java#1 $
*
* @see LinkedHashMap
* @see LRUMap
*/
public class LinkedMap<K, V> extends AbstractDecoratedMap<K, V> implements Serializable {
transient LinkedEntry<K, V> mHead;
protected final boolean mAccessOrder;
/**
* Creates a {@code LinkedMap} backed by a {@code HashMap}, with default
* (insertion) order.
*/
public LinkedMap() {
this(null, false);
}
/**
* Creates a {@code LinkedMap} backed by a {@code HashMap}, with the
* given order.
*
* @param pAccessOrder if {@code true}, ordering will be "least recently
* accessed item" to "latest accessed item", otherwise "first inserted item"
* to "latest inserted item".
*/
public LinkedMap(boolean pAccessOrder) {
this(null, pAccessOrder);
}
/**
* Creates a {@code LinkedMap} backed by a {@code HashMap}, with key/value
* pairs copied from {@code pContents} and default (insertion) order.
*
* @param pContents the map whose mappings are to be placed in this map.
* May be {@code null}.
*/
public LinkedMap(Map<? extends K, ? extends V> pContents) {
this(pContents, false);
}
/**
* Creates a {@code LinkedMap} backed by a {@code HashMap}, with key/value
* pairs copied from {@code pContents} and the given order.
*
* @param pContents the map whose mappings are to be placed in this map.
* May be {@code null}.
* @param pAccessOrder if {@code true}, ordering will be "least recently
* accessed item" to "latest accessed item", otherwise "first inserted item"
* to "latest inserted item".
*/
public LinkedMap(Map<? extends K, ? extends V> pContents, boolean pAccessOrder) {
super(pContents);
mAccessOrder = pAccessOrder;
}
/**
* Creates a {@code LinkedMap} backed by the given map, with key/value
* pairs copied from {@code pContents} and default (insertion) order.
*
* @param pBacking the backing map of this map. Must be either empty, or
* the same map as {@code pContents}.
* @param pContents the map whose mappings are to be placed in this map.
* May be {@code null}.
*/
public LinkedMap(Map<K, Entry<K, V>> pBacking, Map<? extends K, ? extends V> pContents) {
this(pBacking, pContents, false);
}
/**
* Creates a {@code LinkedMap} backed by the given map, with key/value
* pairs copied from {@code pContents} and the given order.
*
* @param pBacking the backing map of this map. Must be either empty, or
* the same map as {@code pContents}.
* @param pContents the map whose mappings are to be placed in this map.
* May be {@code null}.
* @param pAccessOrder if {@code true}, ordering will be "least recently
* accessed item" to "latest accessed item", otherwise "first inserted item"
* to "latest inserted item".
*/
public LinkedMap(Map<K, Entry<K, V>> pBacking, Map<? extends K, ? extends V> pContents, boolean pAccessOrder) {
super(pBacking, pContents);
mAccessOrder = pAccessOrder;
}
protected void init() {
mHead = new LinkedEntry<K, V>(null, null, null) {
void addBefore(LinkedEntry pExisting) {
throw new Error();
}
void remove() {
throw new Error();
}
public void recordAccess(Map pMap) {
throw new Error();
}
public void recordRemoval(Map pMap) {
throw new Error();
}
public void recordRemoval() {
throw new Error();
}
public V getValue() {
throw new Error();
}
public V setValue(V pValue) {
throw new Error();
}
public K getKey() {
throw new Error();
}
public String toString() {
return "head";
}
};
mHead.mPrevious = mHead.mNext = mHead;
}
public boolean containsValue(Object pValue) {
// Overridden to take advantage of faster iterator
if (pValue == null) {
for (LinkedEntry e = mHead.mNext; e != mHead; e = e.mNext) {
if (e.mValue == null) {
return true;
}
}
} else {
for (LinkedEntry e = mHead.mNext; e != mHead; e = e.mNext) {
if (pValue.equals(e.mValue)) {
return true;
}
}
}
return false;
}
protected Iterator<K> newKeyIterator() {
return new KeyIterator();
}
protected Iterator<V> newValueIterator() {
return new ValueIterator();
}
protected Iterator<Entry<K, V>> newEntryIterator() {
return new EntryIterator();
}
private abstract class LinkedMapIterator<E> implements Iterator<E> {
LinkedEntry<K, V> mNextEntry = mHead.mNext;
LinkedEntry<K, V> mLastReturned = null;
/**
* The modCount value that the iterator believes that the backing
* List should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
int mExpectedModCount = mModCount;
public boolean hasNext() {
return mNextEntry != mHead;
}
public void remove() {
if (mLastReturned == null) {
throw new IllegalStateException();
}
if (mModCount != mExpectedModCount) {
throw new ConcurrentModificationException();
}
LinkedMap.this.remove(mLastReturned.mKey);
mLastReturned = null;
mExpectedModCount = mModCount;
}
LinkedEntry<K, V> nextEntry() {
if (mModCount != mExpectedModCount) {
throw new ConcurrentModificationException();
}
if (mNextEntry == mHead) {
throw new NoSuchElementException();
}
LinkedEntry<K, V> e = mLastReturned = mNextEntry;
mNextEntry = e.mNext;
return e;
}
}
private class KeyIterator extends LinkedMap<K, V>.LinkedMapIterator<K> {
public K next() {
return nextEntry().mKey;
}
}
private class ValueIterator extends LinkedMap<K, V>.LinkedMapIterator<V> {
public V next() {
return nextEntry().mValue;
}
}
private class EntryIterator extends LinkedMap<K, V>.LinkedMapIterator<Entry<K, V>> {
public Entry<K, V> next() {
return nextEntry();
}
}
public V get(Object pKey) {
LinkedEntry<K, V> entry = (LinkedEntry<K, V>) mEntries.get(pKey);
if (entry != null) {
entry.recordAccess(this);
return entry.mValue;
}
return null;
}
public V remove(Object pKey) {
LinkedEntry<K, V> entry = (LinkedEntry<K, V>) mEntries.remove(pKey);
if (entry != null) {
entry.remove();
mModCount++;
return entry.mValue;
}
return null;
}
public V put(K pKey, V pValue) {
LinkedEntry<K, V> entry = (LinkedEntry<K, V>) mEntries.get(pKey);
V oldValue;
if (entry == null) {
oldValue = null;
// Remove eldest entry if instructed, else grow capacity if appropriate
LinkedEntry<K, V> eldest = mHead.mNext;
if (removeEldestEntry(eldest)) {
removeEntry(eldest);
}
entry = createEntry(pKey, pValue);
entry.addBefore(mHead);
mEntries.put(pKey, entry);
}
else {
oldValue = entry.mValue;
entry.mValue = pValue;
entry.recordAccess(this);
}
mModCount++;
return oldValue;
}
/**
* Creates a new {@code LinkedEntry}.
*
* @param pKey the key
* @param pValue the value
* @return a new LinkedEntry
*/
/*protected*/ LinkedEntry<K, V> createEntry(K pKey, V pValue) {
return new LinkedEntry<K, V>(pKey, pValue, null);
}
/**
* @todo
*
* @return a copy of this map, with the same order and same key/value pairs.
*/
public Object clone() throws CloneNotSupportedException {
LinkedMap map;
map = (LinkedMap) super.clone();
// TODO: The rest of the work is PROBABLY handled by
// AbstractDecoratedMap, but need to verify that.
return map;
}
/**
* Returns {@code true} if this map should remove its eldest entry.
* This method is invoked by {@code put} and {@code putAll} after
* inserting a new entry into the map. It provides the implementer
* with the opportunity to remove the eldest entry each time a new one
* is added. This is useful if the map represents a cache: it allows
* the map to reduce memory consumption by deleting stale entries.
*
* <p>Sample use: this override will allow the map to grow up to 100
* entries and then delete the eldest entry each time a new entry is
* added, maintaining a steady state of 100 entries.
* <pre>
* private static final int MAX_ENTRIES = 100;
*
* protected boolean removeEldestEntry(Map.Entry eldest) {
* return size() > MAX_ENTRIES;
* }
* </pre>
*
* <p>This method typically does not modify the map in any way,
* instead allowing the map to modify itself as directed by its
* return value. It <i>is</i> permitted for this method to modify
* the map directly, but if it does so, it <i>must</i> return
* {@code false} (indicating that the map should not attempt any
* further modification). The effects of returning {@code true}
* after modifying the map from within this method are unspecified.
*
* <p>This implementation merely returns {@code false} (so that this
* map acts like a normal map - the eldest element is never removed).
*
* @param pEldest The least recently inserted entry in the map, or if
* this is an access-ordered map, the least recently accessed
* entry. This is the entry that will be removed it this
* method returns {@code true}. If the map was empty prior
* to the {@code put} or {@code putAll} invocation resulting
* in this invocation, this will be the entry that was just
* inserted; in other words, if the map contains a single
* entry, the eldest entry is also the newest.
* @return {@code true} if the eldest entry should be removed
* from the map; {@code false} if it should be retained.
*/
protected boolean removeEldestEntry(Entry<K, V> pEldest) {
return false;
}
/**
* Linked list implementation of {@code Map.Entry}.
*/
protected static class LinkedEntry<K, V> extends BasicEntry<K, V> implements Serializable {
LinkedEntry<K, V> mPrevious;
LinkedEntry<K, V> mNext;
LinkedEntry(K pKey, V pValue, LinkedEntry<K, V> pNext) {
super(pKey, pValue);
mNext = pNext;
}
/**
* Adds this entry before the given entry (which must be an existing
* entry) in the list.
*
* @param pExisting the entry to add before
*/
void addBefore(LinkedEntry<K, V> pExisting) {
mNext = pExisting;
mPrevious = pExisting.mPrevious;
mPrevious.mNext = this;
mNext.mPrevious = this;
}
/**
* Removes this entry from the linked list.
*/
void remove() {
mPrevious.mNext = mNext;
mNext.mPrevious = mPrevious;
}
/**
* If the entry is part of an access ordered list, moves the entry to
* the end of the list.
*
* @param pMap the map to record access for
*/
protected void recordAccess(Map<K, V> pMap) {
LinkedMap<K, V> linkedMap = (LinkedMap<K, V>) pMap;
if (linkedMap.mAccessOrder) {
linkedMap.mModCount++;
remove();
addBefore(linkedMap.mHead);
}
}
/**
* Removes this entry from the linked list.
*
* @param pMap the map to record remoal from
*/
protected void recordRemoval(Map<K, V> pMap) {
// TODO: Is this REALLY correct?
remove();
}
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.util.*;
import java.io.Serializable;
/**
* Generic map and linked list implementation of the {@code Set} interface,
* with predictable iteration order.
* <p>
* Resembles {@code LinkedHashSet} from JDK 1.4+, but is backed by a generic
* {@code LinkedMap}, rather than implementing a particular algoritm.
* <p/>
* @see LinkedMap
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/LinkedSet.java#1 $
*/
public class LinkedSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, Serializable {
private final static Object DUMMY = new Object();
private final Map<E, Object> mMap;
public LinkedSet() {
mMap = new LinkedMap<E, Object>();
}
public LinkedSet(Collection<E> pCollection) {
this();
addAll(pCollection);
}
public boolean addAll(Collection<? extends E> pCollection) {
boolean changed = false;
for (E value : pCollection) {
if (add(value) && !changed) {
changed = true;
}
}
return changed;
}
public boolean add(E pValue) {
return mMap.put(pValue, DUMMY) == null;
}
public int size() {
return mMap.size();
}
public Iterator<E> iterator() {
return mMap.keySet().iterator();
}
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.util.*;
import java.io.Serializable;
/**
* An (immutable) empty {@link Map}, that supports all {@code Map} operations
* without throwing expcetions (in contrast to {@link Collections#EMPTY_MAP}
* that will throw exceptions on {@code put}/{@code remove}).
* <p/>
* NOTE: This is not a general purpose {@code Map} implementation,
* as the {@code put} and {@code putAll} methods will not modify the map.
* Instances of this class will always be an empty map.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/NullMap.java#2 $
*/
public final class NullMap<K, V> implements Map<K, V>, Serializable {
public final int size() {
return 0;
}
public final void clear() {
}
public final boolean isEmpty() {
return true;
}
public final boolean containsKey(Object pKey) {
return false;
}
public final boolean containsValue(Object pValue) {
return false;
}
public final Collection<V> values() {
return Collections.emptyList();
}
public final void putAll(Map pMap) {
}
public final Set<Entry<K, V>> entrySet() {
return Collections.emptySet();
}
public final Set<K> keySet() {
return Collections.emptySet();
}
public final V get(Object pKey) {
return null;
}
public final V remove(Object pKey) {
return null;
}
public final V put(Object pKey, Object pValue) {
return null;
}
/**
* Tests the given object for equality (wether it is also an empty
* {@code Map}).
* This is consistent with the standard {@code Map} implementations of the
* Java Collections Framework.
*
* @param pOther the object to compare with
* @return {@code true} if {@code pOther} is an empty {@code Map},
* otherwise {@code false}
*/
public boolean equals(Object pOther) {
return (pOther instanceof Map) && ((Map) pOther).isEmpty();
}
/**
* Returns the {@code hashCode} of the empty map, {@code 0}.
* This is consistent with the standard {@code Map} implementations of the
* Java Collections Framework.
*
* @return {@code 0}, always
*/
public int hashCode() {
return 0;
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.awt.*;
import java.io.Serializable;
/**
* PaintKey class description.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/PaintKey.java#1 $
*/
public class PaintKey extends TypedMap.AbstractKey implements Serializable {
public PaintKey() {
super();
}
public PaintKey(String pName) {
super(pName);
}
public boolean isCompatibleValue(Object pValue) {
return pValue instanceof Paint;
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.awt.geom.Rectangle2D;
import java.io.Serializable;
/**
* Rectangle2DKey class description.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/Rectangle2DKey.java#1 $
*/
public class Rectangle2DKey extends TypedMap.AbstractKey implements Serializable {
public Rectangle2DKey() {
super();
}
public Rectangle2DKey(String pName) {
super(pName);
}
public boolean isCompatibleValue(Object pValue) {
return pValue instanceof Rectangle2D;
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.io.InputStream;
import java.io.IOException;
import java.net.URL;
/**
* Resource class description.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/Resource.java#1 $
*/
public interface Resource {
/**
* Returns the id of this resource.
*
* The id might be a {@code URL}, a {@code File} or a string
* representation of some system resource.
* It will always be equal to the {@code reourceId} parameter
* sent to the {@link ResourceMonitor#addResourceChangeListener} method
* for a given resource.
*
* @return the id of this resource
*/
public Object getId();
/**
* Returns the {@code URL} for the resource.
*
* @return the URL for the resource
*/
public URL asURL();
/**
* Opens an input stream, that reads from this reource.
*
* @return an intput stream, that reads frmo this resource.
*
* @throws IOException if an I/O exception occurs
*/
public InputStream asStream() throws IOException;
/**
* Returns the last modified time.
* Should only be used for comparison.
*
* @return the time of the last modification of this resource, or
* {@code -1} if the last modification time cannot be determined.
*/
public long lastModified();
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.util.EventListener;
/**
* An {@code EventListener} that listens for updates in file or system
* resources.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/ResourceChangeListener.java#1 $
*/
public interface ResourceChangeListener extends EventListener {
/**
* Invoked when a resource is changed.
* Implementations that listens for multiple eventes, should check that
* {@code Resource.getId()} is equal to the {@code reourceId} parameter
* sent to the {@link ResourceMonitor#addResourceChangeListener} method.
*
* @param pResource changed file/url/etc.
*/
void resourceChanged(Resource pResource);
}

View File

@@ -0,0 +1,207 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Map;
import java.util.HashMap;
// TODO: Could this be used for change-aware classloader? Woo..
/**
* Monitors changes is files and system resources.
*
* Based on example code and ideas from
* <A href="http://www.javaworld.com/javaworld/javatips/jw-javatip125.html">Java
* Tip 125: Set your timer for dynamic properties</A>.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/ResourceMonitor.java#1 $
*/
public abstract class ResourceMonitor {
private static final ResourceMonitor INSTANCE = new ResourceMonitor() {};
private Timer mTimer;
private Map mTimerEntries;
public static ResourceMonitor getInstance() {
return INSTANCE;
}
/**
* Creates a {@code ResourceMonitor}.
*/
protected ResourceMonitor() {
// Create timer, run timer thread as daemon...
mTimer = new Timer(true);
mTimerEntries = new HashMap();
}
/**
* Add a monitored {@code Resource} with a {@code ResourceChangeListener}.
*
* The {@code reourceId} might be a {@code File} a {@code URL} or a
* {@code String} containing a file path, or a path to a resource in the
* class path. Note that class path resources are resolved using the
* given {@code ResourceChangeListener}'s {@code ClassLoader}, then
* the current {@code Thread}'s context class loader, if not found.
*
* @param pListener pListener to notify when the file changed.
* @param pResourceId id of the resource to monitor (a {@code File}
* a {@code URL} or a {@code String} containing a file path).
* @param pPeriod polling pPeriod in milliseconds.
*
* @see ClassLoader#getResource(String)
*/
public void addResourceChangeListener(ResourceChangeListener pListener,
Object pResourceId, long pPeriod) throws IOException {
// Create the task
ResourceMonitorTask task = new ResourceMonitorTask(pListener, pResourceId);
// Get unique Id
Object resourceId = getResourceId(pResourceId, pListener);
// Remove the old task for this Id, if any, and register the new one
synchronized (mTimerEntries) {
removeListenerInternal(resourceId);
mTimerEntries.put(resourceId, task);
}
mTimer.schedule(task, pPeriod, pPeriod);
}
/**
* Remove the {@code ResourceChangeListener} from the notification list.
*
* @param pListener the pListener to be removed.
* @param pResourceId name of the resource to monitor.
*/
public void removeResourceChangeListener(ResourceChangeListener pListener, Object pResourceId) {
synchronized (mTimerEntries) {
removeListenerInternal(getResourceId(pResourceId, pListener));
}
}
private void removeListenerInternal(Object pResourceId) {
ResourceMonitorTask task = (ResourceMonitorTask) mTimerEntries.remove(pResourceId);
if (task != null) {
task.cancel();
}
}
private Object getResourceId(Object pResourceName, ResourceChangeListener pListener) {
return pResourceName.toString() + System.identityHashCode(pListener);
}
private void fireResourceChangeEvent(ResourceChangeListener pListener, Resource pResource) {
pListener.resourceChanged(pResource);
}
/**
*
*/
private class ResourceMonitorTask extends TimerTask {
ResourceChangeListener mListener;
Resource mMonitoredResource;
long mLastModified;
public ResourceMonitorTask(ResourceChangeListener pListener, Object pResourceId) throws IOException {
mListener = pListener;
mLastModified = 0;
String resourceId = null;
File file = null;
URL url = null;
if (pResourceId instanceof File) {
file = (File) pResourceId;
resourceId = file.getAbsolutePath(); // For use by exception only
}
else if (pResourceId instanceof URL) {
url = (URL) pResourceId;
if ("file".equals(url.getProtocol())) {
file = new File(url.getFile());
}
resourceId = url.toExternalForm(); // For use by exception only
}
else if (pResourceId instanceof String) {
resourceId = (String) pResourceId; // This one might be looked up
file = new File((String) resourceId);
}
if (file != null && file.exists()) {
// Easy, this is a file
mMonitoredResource = new FileResource(pResourceId, file);
//System.out.println("File: " + mMonitoredResource);
}
else {
// No file there, but is it on CLASSPATH?
if (url == null) {
url = pListener.getClass().getClassLoader().getResource(resourceId);
}
if (url == null) {
url = Thread.currentThread().getContextClassLoader().getResource(resourceId);
}
if (url != null && "file".equals(url.getProtocol())
&& (file = new File(url.getFile())).exists()) {
// It's a file in classpath, so try this as an optimization
mMonitoredResource = new FileResource(pResourceId, file);
//System.out.println("File: " + mMonitoredResource);
}
else if (url != null) {
// No, not a file, might even be an external resource
mMonitoredResource = new URLResource(pResourceId, url);
//System.out.println("URL: " + mMonitoredResource);
}
else {
throw new FileNotFoundException(resourceId);
}
}
mLastModified = mMonitoredResource.lastModified();
}
public void run() {
long lastModified = mMonitoredResource.lastModified();
if (lastModified != mLastModified) {
mLastModified = lastModified;
fireResourceChangeEvent(mListener, mMonitoredResource);
}
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.io.Serializable;
/**
* StringKey class description.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/StringKey.java#1 $
*/
public class StringKey extends TypedMap.AbstractKey implements Serializable {
public StringKey() {
super();
}
public StringKey(String pName) {
super(pName);
}
public boolean isCompatibleValue(Object pValue) {
return pValue instanceof String;
}
}

View File

@@ -0,0 +1,329 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.util.NoSuchElementException;
/**
* StringTokenIterator, a drop-in replacement for {@code StringTokenizer}.
* StringTokenIterator has the following features:
* <ul>
* <li>Iterates over a strings, 20-50% faster than {@code StringTokenizer}
* (and magnitudes faster than {@code String.split(..)} or
* {@code Pattern.split(..)})</li>
* <li>Implements the {@code Iterator} interface</li>
* <li>Optionally returns delimiters</li>
* <li>Optionally returns empty elements</li>
* <li>Optionally iterates in reverse</li>
* <li>Resettable</li>
* </ul>
*
* @see java.util.StringTokenizer
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/StringTokenIterator.java#1 $
*/
public class StringTokenIterator extends AbstractTokenIterator {
private final String mString;
private final char[] mDelimiters;
private int mPosition;
private final int mMaxPosition;
private String mNext;
private String mNextDelimiter;
private final boolean mIncludeDelimiters;
private final boolean mIncludeEmpty;
private final boolean mReverse;
public final static int FORWARD = 1;
public final static int REVERSE = -1;
/**
* Stores the value of the delimiter character with the highest value.
* It is used to optimize the detection of delimiter characters.
*/
private final char mMaxDelimiter;
/**
* Creates a StringTokenIterator
*
* @param pString the string to be parsed.
*/
public StringTokenIterator(String pString) {
this(pString, " \t\n\r\f".toCharArray(), FORWARD, false, false);
}
/**
* Creates a StringTokenIterator
*
* @param pString the string to be parsed.
* @param pDelimiters the delimiters.
*/
public StringTokenIterator(String pString, String pDelimiters) {
this(pString, toCharArray(pDelimiters), FORWARD, false, false);
}
/**
* Creates a StringTokenIterator
*
* @param pString the string to be parsed.
* @param pDelimiters the delimiters.
* @param pDirection iteration direction.
*/
public StringTokenIterator(String pString, String pDelimiters, int pDirection) {
this(pString, toCharArray(pDelimiters), pDirection, false, false);
}
/**
* Creates a StringTokenIterator
*
* @param pString the string to be parsed.
* @param pDelimiters the delimiters.
* @param pIncludeDelimiters flag indicating whether to return delimiters as tokens.
*/
public StringTokenIterator(String pString, String pDelimiters, boolean pIncludeDelimiters) {
this(pString, toCharArray(pDelimiters), FORWARD, pIncludeDelimiters, false);
}
/**
* Creates a StringTokenIterator
*
* @param pString the string to be parsed.
* @param pDelimiters the delimiters.
* @param pDirection iteration direction.
* @param pIncludeDelimiters flag indicating whether to return delimiters as tokens.
* @param pIncludeEmpty flag indicating whether to return empty tokens
*
*/
public StringTokenIterator(String pString, String pDelimiters, int pDirection,
boolean pIncludeDelimiters, boolean pIncludeEmpty) {
this(pString, toCharArray(pDelimiters), pDirection, pIncludeDelimiters, pIncludeEmpty);
}
/**
* Implementation.
*
* @param pString the string to be parsed.
* @param pDelimiters the delimiters.
* @param pDirection iteration direction.
* @param pIncludeDelimiters flag indicating whether to return delimiters as tokens.
* @param pIncludeEmpty flag indicating whether to return empty tokens
*/
private StringTokenIterator(String pString, char[] pDelimiters,
int pDirection, boolean pIncludeDelimiters, boolean pIncludeEmpty) {
if (pString == null) {
throw new IllegalArgumentException("string == null");
}
mString = pString;
mMaxPosition = pString.length();
mDelimiters = pDelimiters;
mIncludeDelimiters = pIncludeDelimiters;
mReverse = (pDirection == REVERSE);
mIncludeEmpty = pIncludeEmpty;
mMaxDelimiter = initMaxDelimiter(pDelimiters);
reset();
}
private static char[] toCharArray(String pDelimiters) {
if (pDelimiters == null) {
throw new IllegalArgumentException("delimiters == null");
}
return pDelimiters.toCharArray();
}
/**
* Returns the highest char in the delimiter set.
* @param pDelimiters the delimiter set
* @return the highest char
*/
private static char initMaxDelimiter(char[] pDelimiters) {
if (pDelimiters == null) {
return 0;
}
char max = 0;
for (char c : pDelimiters) {
if (max < c) {
max = c;
}
}
return max;
}
/**
* Resets this iterator.
*
*/
public void reset() {
mPosition = 0;
mNext = null;
mNextDelimiter = null;
}
/**
* Returns {@code true} if the iteration has more elements. (In other
* words, returns {@code true} if {@code next} would return an element
* rather than throwing an exception.)
*
* @return {@code true} if the iterator has more elements.
*/
public boolean hasNext() {
return (mNext != null || fetchNext() != null);
}
private String fetchNext() {
// If next is delimiter, return fast
if (mNextDelimiter != null) {
mNext = mNextDelimiter;
mNextDelimiter = null;
return mNext;
}
// If no more chars, return null
if (mPosition >= mMaxPosition) {
return null;
}
return mReverse ? fetchReverse() : fetchForward();
}
private String fetchReverse() {
// Get previous position
int prevPos = scanForPrev();
// Store next string
mNext = mString.substring(prevPos + 1, mMaxPosition - mPosition);
if (mIncludeDelimiters && prevPos >= 0 && prevPos < mMaxPosition) {
mNextDelimiter = mString.substring(prevPos, prevPos + 1);
}
mPosition = mMaxPosition - prevPos;
// Skip empty
if (mNext.length() == 0 && !mIncludeEmpty) {
return fetchNext();
}
return mNext;
}
private String fetchForward() {
// Get next position
int nextPos = scanForNext();
// Store next string
mNext = mString.substring(mPosition, nextPos);
if (mIncludeDelimiters && nextPos >= 0 && nextPos < mMaxPosition) {
mNextDelimiter = mString.substring(nextPos, nextPos + 1);
}
mPosition = ++nextPos;
// Skip empty
if (mNext.length() == 0 && !mIncludeEmpty) {
return fetchNext();
}
return mNext;
}
private int scanForNext() {
int position = mPosition;
while (position < mMaxPosition) {
// Find next match, using all delimiters
char c = mString.charAt(position);
if (c <= mMaxDelimiter) {
// Find first delimiter match
for (char delimiter : mDelimiters) {
if (c == delimiter) {
return position;// Return if match
}
}
}
// Next...
position++;
}
// Return last position, if no match
return position;
}
private int scanForPrev() {
int position = (mMaxPosition - 1) - mPosition;
while (position >= 0) {
// Find next match, using all delimiters
char c = mString.charAt(position);
if (c <= mMaxDelimiter) {
// Find first delimiter match
for (char delimiter : mDelimiters) {
if (c == delimiter) {
return position;// Return if match
}
}
}
// Next...
position--;
}
// Return first position, if no match
return position;
}
/**
* Returns the next element in the iteration.
*
* @return the next element in the iteration.
* @exception java.util.NoSuchElementException iteration has no more elements.
*/
public String next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
String next = mNext;
mNext = fetchNext();
return next;
}
}

View File

@@ -0,0 +1,181 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
/**
* Utility class for storing times in a simple way. The internal time is stored
* as an int, counting seconds.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @todo Milliseconds!
*/
public class Time {
private int mTime = -1;
public final static int SECONDS_IN_MINUTE = 60;
/**
* Creates a new time with 0 seconds, 0 minutes.
*/
public Time() {
this(0);
}
/**
* Creates a new time with the given time (in seconds).
*/
public Time(int pTime) {
setTime(pTime);
}
/**
* Sets the full time in seconds
*/
public void setTime(int pTime) {
if (pTime < 0) {
throw new IllegalArgumentException("Time argument must be 0 or positive!");
}
mTime = pTime;
}
/**
* Gets the full time in seconds.
*/
public int getTime() {
return mTime;
}
/**
* Gets the full time in milliseconds, for use in creating dates or
* similar.
*
* @see java.util.Date#setTime(long)
*/
public long getTimeInMillis() {
return (long) mTime * 1000L;
}
/**
* Sets the seconds part of the time. Note, if the seconds argument is 60
* or greater, the value will "wrap", and increase the minutes also.
*
* @param pSeconds an integer that should be between 0 and 59.
*/
public void setSeconds(int pSeconds) {
mTime = getMinutes() * SECONDS_IN_MINUTE + pSeconds;
}
/**
* Gets the seconds part of the time.
*
* @return an integer between 0 and 59
*/
public int getSeconds() {
return mTime % SECONDS_IN_MINUTE;
}
/**
* Sets the minutes part of the time.
*
* @param pMinutes an integer
*/
public void setMinutes(int pMinutes) {
mTime = pMinutes * SECONDS_IN_MINUTE + getSeconds();
}
/**
* Gets the minutes part of the time.
*
* @return an integer
*/
public int getMinutes() {
return mTime / SECONDS_IN_MINUTE;
}
/**
* Creates a string representation of the time object.
* The string is returned on the form m:ss,
* where m is variable digits minutes and ss is two digits seconds.
*
* @return a string representation of the time object
* @see #toString(String)
*/
public String toString() {
return "" + getMinutes() + ":"
+ (getSeconds() < 10 ? "0" : "") + getSeconds();
}
/**
* Creates a string representation of the time object.
* The string returned is on the format of the formatstring.
* <DL>
* <DD>m (or any multiple of m's)
* <DT>the minutes part (padded with 0's, if number has less digits than
* the number of m's)
* m -> 0,1,...,59,60,61,...
* mm -> 00,01,...,59,60,61,...
* <DD>s or ss
* <DT>the seconds part (padded with 0's, if number has less digits than
* the number of s's)
* s -> 0,1,...,59
* ss -> 00,01,...,59
* <DD>S
* <DT>all seconds (including the ones above 59)
* </DL>
*
* @param pFormatStr the format where
* @return a string representation of the time object
* @throws NumberFormatException
* @see TimeFormat#format(Time)
* @see #parseTime(String)
* @deprecated
*/
public String toString(String pFormatStr) {
TimeFormat tf = new TimeFormat(pFormatStr);
return tf.format(this);
}
/**
* Creates a string representation of the time object.
* The string is returned on the form m:ss,
* where m is variable digits minutes and ss is two digits seconds.
*
* @return a string representation of the time object
* @throws NumberFormatException
* @see TimeFormat#parse(String)
* @see #toString(String)
* @deprecated
*/
public static Time parseTime(String pStr) {
TimeFormat tf = TimeFormat.getInstance();
return tf.parse(pStr);
}
}

View File

@@ -0,0 +1,449 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.text.FieldPosition;
import java.text.ParsePosition;
import java.util.StringTokenizer;
import java.util.Vector;
/**
* Format for converting and parsing time.
* <P>
* The format is expressed in a string as follows:
* <DL>
* <DD>m (or any multiple of m's)
* <DT>the minutes part (padded with 0's, if number has less digits than
* the number of m's)
* m -> 0,1,...,59,60,61,...
* mm -> 00,01,...,59,60,61,...
* <DD>s or ss
* <DT>the seconds part (padded with 0's, if number has less digits than
* the number of s's)
* s -> 0,1,...,59
* ss -> 00,01,...,59
* <DD>S
* <DT>all seconds (including the ones above 59)
* </DL>
* <P>
* May not handle all cases, and formats... ;-)
* Safest is: Always delimiters between the minutes (m) and seconds (s) part.
* <P>
* TODO:
* Move to com.twelvemonkeys.text?
* Milliseconds!
* Fix bugs.
* Known bugs:
* <P>
* The last character in the formatString is not escaped, while it should be.
* The first character after an escaped character is escaped while is shouldn't
* be.
* <P>
* This is not a 100% compatible implementation of a java.text.Format.
*
* @see com.twelvemonkeys.util.Time
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
*/
public class TimeFormat extends java.text.Format {
final static String MINUTE = "m";
final static String SECOND = "s";
final static String TIME = "S";
final static String ESCAPE = "\\";
/**
* The default time format
*/
private final static TimeFormat DEFAULT_FORMAT = new TimeFormat("m:ss");
protected String mFormatString = null;
/**
* Main method for testing ONLY
*/
static void main(String[] argv) {
Time time = null;
TimeFormat in = null;
TimeFormat out = null;
if (argv.length >= 3) {
System.out.println("Creating out TimeFormat: \"" + argv[2] + "\"");
out = new TimeFormat(argv[2]);
}
if (argv.length >= 2) {
System.out.println("Creating in TimeFormat: \"" + argv[1] + "\"");
in = new TimeFormat(argv[1]);
}
else {
System.out.println("Using default format for in");
in = DEFAULT_FORMAT;
}
if (out == null)
out = in;
if (argv.length >= 1) {
System.out.println("Parsing: \"" + argv[0] + "\" with format \""
+ in.mFormatString + "\"");
time = in.parse(argv[0]);
}
else
time = new Time();
System.out.println("Time is \"" + out.format(time) +
"\" according to format \"" + out.mFormatString + "\"");
}
/**
* The formatter array.
*/
protected TimeFormatter[] mFormatter;
/**
* Creates a new TimeFormat with the given formatString,
*/
public TimeFormat(String pStr) {
mFormatString = pStr;
Vector formatter = new Vector();
StringTokenizer tok = new StringTokenizer(pStr, "\\msS", true);
String previous = null;
String current = null;
int previousCount = 0;
while (tok.hasMoreElements()) {
current = tok.nextToken();
if (previous != null && previous.equals(ESCAPE)) {
// Handle escaping of s, S or m
current = ((current != null) ? current : "")
+ (tok.hasMoreElements() ? tok.nextToken() : "");
previous = null;
previousCount = 0;
}
// Skip over first,
// or if current is the same, increase count, and try again
if (previous == null || previous.equals(current)) {
previousCount++;
previous = current;
}
else {
// Create new formatter for each part
if (previous.equals(MINUTE))
formatter.add(new MinutesFormatter(previousCount));
else if (previous.equals(SECOND))
formatter.add(new SecondsFormatter(previousCount));
else if (previous.equals(TIME))
formatter.add(new SecondsFormatter(-1));
else
formatter.add(new TextFormatter(previous));
previousCount = 1;
previous = current;
}
}
// Add new formatter for last part
if (previous != null) {
if (previous.equals(MINUTE))
formatter.add(new MinutesFormatter(previousCount));
else if (previous.equals(SECOND))
formatter.add(new SecondsFormatter(previousCount));
else if (previous.equals(TIME))
formatter.add(new SecondsFormatter(-1));
else
formatter.add(new TextFormatter(previous));
}
// Debug
/*
for (int i = 0; i < formatter.size(); i++) {
System.out.println("Formatter " + formatter.get(i).getClass()
+ ": length=" + ((TimeFormatter) formatter.get(i)).mDigits);
}
*/
mFormatter = (TimeFormatter[])
formatter.toArray(new TimeFormatter[formatter.size()]);
}
/**
* DUMMY IMPLEMENTATION!!
* Not locale specific.
*/
public static TimeFormat getInstance() {
return DEFAULT_FORMAT;
}
/** DUMMY IMPLEMENTATION!! */
/* Not locale specific
public static TimeFormat getInstance(Locale pLocale) {
return DEFAULT_FORMAT;
}
*/
/** DUMMY IMPLEMENTATION!! */
/* Not locale specific
public static Locale[] getAvailableLocales() {
return new Locale[] {Locale.getDefault()};
}
*/
/** Gets the format string. */
public String getFormatString() {
return mFormatString;
}
/** DUMMY IMPLEMENTATION!! */
public StringBuffer format(Object pObj, StringBuffer pToAppendTo,
FieldPosition pPos) {
if (!(pObj instanceof Time)) {
throw new IllegalArgumentException("Must be instance of " + Time.class);
}
return pToAppendTo.append(format(pObj));
}
/**
* Formats the the given time, using this format.
*/
public String format(Time pTime) {
StringBuilder buf = new StringBuilder();
for (int i = 0; i < mFormatter.length; i++) {
buf.append(mFormatter[i].format(pTime));
}
return buf.toString();
}
/** DUMMY IMPLEMENTATION!! */
public Object parseObject(String pStr, ParsePosition pStatus) {
Time t = parse(pStr);
pStatus.setIndex(pStr.length()); // Not 100%
return t;
}
/**
* Parses a Time, according to this format.
* <p>
* Will bug on some formats. It's safest to always use delimiters between
* the minutes (m) and seconds (s) part.
*
*/
public Time parse(String pStr) {
Time time = new Time();
int sec = 0;
int min = 0;
int pos = 0;
int skip = 0;
boolean onlyUseSeconds = false;
for (int i = 0; (i < mFormatter.length)
&& (pos + skip < pStr.length()) ; i++) {
// Go to next offset
pos += skip;
if (mFormatter[i] instanceof MinutesFormatter) {
// Parse MINUTES
if ((i + 1) < mFormatter.length
&& mFormatter[i + 1] instanceof TextFormatter) {
// Skip until next format element
skip = pStr.indexOf(((TextFormatter) mFormatter[i + 1]).mText, pos);
// Error in format, try parsing to end
if (skip < 0)
skip = pStr.length();
}
else if ((i + 1) >= mFormatter.length) {
// Skip until end of string
skip = pStr.length();
}
else {
// Hope this is correct...
skip = mFormatter[i].mDigits;
}
// May be first char
if (skip > pos)
min = Integer.parseInt(pStr.substring(pos, skip));
}
else if (mFormatter[i] instanceof SecondsFormatter) {
// Parse SECONDS
if (mFormatter[i].mDigits == -1) {
// Only seconds (or full TIME)
if ((i + 1) < mFormatter.length
&& mFormatter[i + 1] instanceof TextFormatter) {
// Skip until next format element
skip = pStr.indexOf(((TextFormatter) mFormatter[i + 1]).mText, pos);
}
else if ((i + 1) >= mFormatter.length) {
// Skip until end of string
skip = pStr.length();
}
else {
// Cannot possibly know how long?
skip = 0;
continue;
}
// Get seconds
sec = Integer.parseInt(pStr.substring(pos, skip));
// System.out.println("Only seconds: " + sec);
onlyUseSeconds = true;
break;
}
else {
// Normal SECONDS
if ((i + 1) < mFormatter.length
&& mFormatter[i + 1] instanceof TextFormatter) {
// Skip until next format element
skip = pStr.indexOf(((TextFormatter) mFormatter[i + 1]).mText, pos);
}
else if ((i + 1) >= mFormatter.length) {
// Skip until end of string
skip = pStr.length();
}
else {
skip = mFormatter[i].mDigits;
}
// Get seconds
sec = Integer.parseInt(pStr.substring(pos, skip));
}
}
else if (mFormatter[i] instanceof TextFormatter) {
skip = mFormatter[i].mDigits;
}
}
// Set the minutes part if we should
if (!onlyUseSeconds)
time.setMinutes(min);
// Set the seconds part
time.setSeconds(sec);
return time;
}
}
/**
* The base class of TimeFormatters
*/
abstract class TimeFormatter {
int mDigits = 0;
abstract String format(Time t);
}
/**
* Formats the seconds part of the Time
*/
class SecondsFormatter extends TimeFormatter {
SecondsFormatter(int pDigits) {
mDigits = pDigits;
}
String format(Time t) {
// Negative number of digits, means all seconds, no padding
if (mDigits < 0) {
return Integer.toString(t.getTime());
}
// If seconds is more than mDigits long, simply return it
if (t.getSeconds() >= Math.pow(10, mDigits)) {
return Integer.toString(t.getSeconds());
}
// Else return it with leading 0's
//return StringUtil.formatNumber(t.getSeconds(), mDigits);
return com.twelvemonkeys.lang.StringUtil.pad("" + t.getSeconds(), mDigits, "0", true);
}
}
/**
* Formats the minutes part of the Time
*/
class MinutesFormatter extends TimeFormatter {
MinutesFormatter(int pDigits) {
mDigits = pDigits;
}
String format(Time t) {
// If minutes is more than mDigits long, simply return it
if (t.getMinutes() >= Math.pow(10, mDigits)) {
return Integer.toString(t.getMinutes());
}
// Else return it with leading 0's
//return StringUtil.formatNumber(t.getMinutes(), mDigits);
return com.twelvemonkeys.lang.StringUtil.pad("" + t.getMinutes(), mDigits, "0", true);
}
}
/**
* Formats text constant part of the Time
*/
class TextFormatter extends TimeFormatter {
String mText = null;
TextFormatter(String pText) {
mText = pText;
// Just to be able to skip over
if (pText != null) {
mDigits = pText.length();
}
}
String format(Time t) {
// Simply return the text
return mText;
}
}

View File

@@ -0,0 +1,449 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.io.Serializable;
import java.util.*;
/**
* A {@code Map} implementation that removes (exipres) its elements after
* a given period. The map is by default backed by a {@link java.util.HashMap},
* or can be instantiated with any given {@code Map} as backing.
* <P/>
* Notes to consider when using this map:
* <ul>
* <li>Elements may not expire on the exact millisecond as expected.</li>
* <li>The value returned by the {@code size()} method of the map, or any of
* its collection views, may not represent
* the exact number of entries in the map at any given time.</li>
* <li>Elements in this map may expire at any time
* (but never between invocations of {@code Iterator.hasNext()}
* and {@code Iterator.next()} or {@code Iterator.remove()},
* when iterating the collection views).</li>
* </ul>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/TimeoutMap.java#2 $
*
* @todo Consider have this Map extend LinkedMap.. That way the removeExpired
* method only have to run from the first element, until it finds an element
* that should not expire, as elements are in insertion order.
* and next expiry time would be the time of the first element.
* @todo Consider running the removeExpiredEntries method in a separate (deamon) thread
* @todo - or document why it is not such a good idea.
*/
public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements ExpiringMap<K, V>, Serializable, Cloneable {
/**
* Expiry time
*/
protected long mExpiryTime = 60000L; // 1 minute
//////////////////////
private volatile long mNextExpiryTime;
//////////////////////
/**
* Creates a {@code TimeoutMap} with the default expiry time of 1 minute.
* This {@code TimeoutMap} will be backed by a new {@code HashMap} instance.
* <p/>
* <small>This is constructor is here to comply with the reccomendations for
* "standard" constructors in the {@code Map} interface.</small>
*
* @see #TimeoutMap(long)
*/
public TimeoutMap() {
super();
}
/**
* Creates a {@code TimeoutMap} containing the same elements as the given map
* with the default expiry time of 1 minute.
* This {@code TimeoutMap} will be backed by a new {@code HashMap} instance,
* and <em>not</em> the map passed in as a paramter.
* <p/>
* <small>This is constructor is here to comply with the reccomendations for
* "standard" constructors in the {@code Map} interface.</small>
*
* @param pContents the map whose mappings are to be placed in this map.
* May be {@code null}.
* @see #TimeoutMap(java.util.Map, Map, long)
* @see java.util.Map
*/
public TimeoutMap(Map<? extends K, ? extends V> pContents) {
super(pContents);
}
/**
* Creates a {@code TimeoutMap} with the given expiry time (milliseconds).
* This {@code TimeoutMap} will be backed by a new {@code HashMap} instance.
*
* @param pExpiryTime the expiry time (time to live) for elements in this map
*/
public TimeoutMap(long pExpiryTime) {
this();
mExpiryTime = pExpiryTime;
}
/**
* Creates a {@code TimeoutMap} with the given expiry time (milliseconds).
* This {@code TimeoutMap} will be backed by the given {@code Map}.
* <P/>
* <EM>Note that structurally modifying the backing map directly (not
* through this map or its collection views), is not allowed, and will
* produce undeterministic exceptions.</EM>
*
* @param pBacking the map that will be used as backing.
* @param pContents the map whose mappings are to be placed in this map.
* May be {@code null}.
* @param pExpiryTime the expiry time (time to live) for elements in this map
*/
public TimeoutMap(Map<K, Map.Entry<K, V>> pBacking, Map<? extends K, ? extends V> pContents, long pExpiryTime) {
super(pBacking, pContents);
mExpiryTime = pExpiryTime;
}
/**
* Gets the maximum time any value will be kept in the map, before it expires.
*
* @return the expiry time
*/
public long getExpiryTime() {
return mExpiryTime;
}
/**
* Sets the maximum time any value will be kept in the map, before it expires.
* Removes any items that are older than the specified time.
*
* @param pExpiryTime the expiry time (time to live) for elements in this map
*/
public void setExpiryTime(long pExpiryTime) {
long oldEexpiryTime = mExpiryTime;
mExpiryTime = pExpiryTime;
if (mExpiryTime < oldEexpiryTime) {
// Expire now
mNextExpiryTime = 0;
removeExpiredEntries();
}
}
/**
* Returns the number of key-value mappings in this map. If the
* map contains more than {@code Integer.MAX_VALUE} elements, returns
* {@code Integer.MAX_VALUE}.
*
* @return the number of key-value mappings in this map.
*/
public int size() {
removeExpiredEntries();
return mEntries.size();
}
/**
* Returns {@code true} if this map contains no key-value mappings.
*
* @return {@code true} if this map contains no key-value mappings.
*/
public boolean isEmpty() {
return (size() <= 0);
}
/**
* Returns {@code true} if this map contains a mapping for the specified
* pKey.
*
* @param pKey pKey whose presence in this map is to be tested.
* @return {@code true} if this map contains a mapping for the specified
* pKey.
*/
public boolean containsKey(Object pKey) {
removeExpiredEntries();
return mEntries.containsKey(pKey);
}
/**
* Returns the value to which this map maps the specified pKey. Returns
* {@code null} if the map contains no mapping for this pKey. A return
* value of {@code null} does not <i>necessarily</i> indicate that the
* map contains no mapping for the pKey; it's also possible that the map
* explicitly maps the pKey to {@code null}. The {@code containsKey}
* operation may be used to distinguish these two cases.
*
* @param pKey pKey whose associated value is to be returned.
* @return the value to which this map maps the specified pKey, or
* {@code null} if the map contains no mapping for this pKey.
* @see #containsKey(java.lang.Object)
*/
public V get(Object pKey) {
TimedEntry<K, V> entry = (TimedEntry<K, V>) mEntries.get(pKey);
if (entry == null) {
return null;
}
else if (entry.isExpired()) {
//noinspection SuspiciousMethodCalls
mEntries.remove(pKey);
processRemoved(entry);
return null;
}
return entry.getValue();
}
/**
* Associates the specified pValue with the specified pKey in this map
* (optional operation). If the map previously contained a mapping for
* this pKey, the old pValue is replaced.
*
* @param pKey pKey with which the specified pValue is to be associated.
* @param pValue pValue to be associated with the specified pKey.
* @return previous pValue associated with specified pKey, or {@code null}
* if there was no mapping for pKey. A {@code null} return can
* also indicate that the map previously associated {@code null}
* with the specified pKey, if the implementation supports
* {@code null} values.
*/
public V put(K pKey, V pValue) {
TimedEntry<K, V> entry = (TimedEntry<K, V>) mEntries.get(pKey);
V oldValue;
if (entry == null) {
oldValue = null;
entry = createEntry(pKey, pValue);
mEntries.put(pKey, entry);
}
else {
oldValue = entry.mValue;
entry.setValue(pValue);
entry.recordAccess(this);
}
// Need to remove expired objects every now and then
// We do it in the put method, to avoid resource leaks over time.
removeExpiredEntries();
mModCount++;
return oldValue;
}
/**
* Removes the mapping for this pKey from this map if present (optional
* operation).
*
* @param pKey pKey whose mapping is to be removed from the map.
* @return previous value associated with specified pKey, or {@code null}
* if there was no mapping for pKey. A {@code null} return can
* also indicate that the map previously associated {@code null}
* with the specified pKey, if the implementation supports
* {@code null} values.
*/
public V remove(Object pKey) {
TimedEntry<K, V> entry = (TimedEntry<K, V>) mEntries.remove(pKey);
return (entry != null) ? entry.getValue() : null;
}
/**
* Removes all mappings from this map.
*/
public void clear() {
mEntries.clear(); // Finally something straightforward.. :-)
init();
}
/*protected*/ TimedEntry<K, V> createEntry(K pKey, V pValue) {
return new TimedEntry<K, V>(pKey, pValue);
}
/**
* Removes any expired mappings.
*
*/
protected void removeExpiredEntries() {
// Remove any expired elements
long now = System.currentTimeMillis();
if (now > mNextExpiryTime) {
removeExpiredEntriesSynced(now);
}
}
/**
* Okay, I guess this do resemble DCL...
*
* @todo Write some exhausting multi-threaded unit-tests.
*
* @param pTime now
*/
private synchronized void removeExpiredEntriesSynced(long pTime) {
if (pTime > mNextExpiryTime) {
////
long next = Long.MAX_VALUE;
mNextExpiryTime = next; // Avoid multiple runs...
for (Iterator<Entry<K, V>> iterator = new EntryIterator(); iterator.hasNext();) {
TimedEntry<K, V> entry = (TimedEntry<K, V>) iterator.next();
////
long expires = entry.expires();
if (expires < next) {
next = expires;
}
////
}
////
mNextExpiryTime = next;
}
}
public Collection<V> values() {
removeExpiredEntries();
return super.values();
}
public Set<Entry<K, V>> entrySet() {
removeExpiredEntries();
return super.entrySet();
}
public Set<K> keySet() {
removeExpiredEntries();
return super.keySet();
}
// Subclass overrides these to alter behavior of views' iterator() method
protected Iterator<K> newKeyIterator() {
return new KeyIterator();
}
protected Iterator<V> newValueIterator() {
return new ValueIterator();
}
protected Iterator<Entry<K, V>> newEntryIterator() {
return new EntryIterator();
}
public void processRemoved(Entry pRemoved) {
}
/**
* Note: Iterating through this iterator will remove any expired values.
*/
private abstract class TimeoutMapIterator<E> implements Iterator<E> {
Iterator<Entry<K, Entry<K, V>>> mIterator = mEntries.entrySet().iterator();
BasicEntry<K, V> mNext;
long mNow = System.currentTimeMillis();
public void remove() {
mNext = null; // advance
mIterator.remove();
}
public boolean hasNext() {
if (mNext != null) {
return true; // Never expires between hasNext and next/remove!
}
while (mNext == null && mIterator.hasNext()) {
Entry<K, Entry<K, V>> entry = mIterator.next();
TimedEntry<K, V> timed = (TimedEntry<K, V>) entry.getValue();
if (timed.isExpiredBy(mNow)) {
// Remove from map, and continue
mIterator.remove();
processRemoved(timed);
}
else {
// Go with this entry
mNext = timed;
return true;
}
}
return false;
}
BasicEntry<K, V> nextEntry() {
if (!hasNext()) {
throw new NoSuchElementException();
}
BasicEntry<K, V> entry = mNext;
mNext = null; // advance
return entry;
}
}
private class KeyIterator extends TimeoutMapIterator<K> {
public K next() {
return nextEntry().mKey;
}
}
private class ValueIterator extends TimeoutMapIterator<V> {
public V next() {
return nextEntry().mValue;
}
}
private class EntryIterator extends TimeoutMapIterator<Entry<K, V>> {
public Entry<K, V> next() {
return nextEntry();
}
}
/**
* Keeps track of timed objects
*/
private class TimedEntry<K, V> extends BasicEntry<K, V> {
private long mTimestamp;
TimedEntry(K pKey, V pValue) {
super(pKey, pValue);
mTimestamp = System.currentTimeMillis();
}
public V setValue(V pValue) {
mTimestamp = System.currentTimeMillis();
return super.setValue(pValue);
}
final boolean isExpired() {
return isExpiredBy(System.currentTimeMillis());
}
final boolean isExpiredBy(final long pTime) {
return pTime > expires();
}
final long expires() {
return mTimestamp + mExpiryTime;
}
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.util.Iterator;
import java.util.Enumeration;
/**
* TokenIterator, Iterator-based replacement for StringTokenizer.
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/TokenIterator.java#1 $
*/
public interface TokenIterator extends Iterator<String>, Enumeration<String> {
boolean hasMoreTokens();
/**
* Returns the next element in the iteration as a {@code String}.
*
* @return the next element in the iteration.
* @exception java.util.NoSuchElementException iteration has no more elements.
*/
String nextToken();
/**
* Resets this iterator.
*
*/
void reset();
}

View File

@@ -0,0 +1,320 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.util.Map;
import java.util.HashMap;
import java.util.Collection;
import java.util.Set;
import java.io.Serializable;
/**
* This {@code Map} implementation guarantees that the values have a type that
* are compatible with it's key. Different keys may have different types.
*
* @see TypedMap.Key
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/TypedMap.java#1 $
*/
public class TypedMap<K extends TypedMap.Key, V> implements Map<K, V>, Serializable {
/**
* The wrapped map
*/
protected Map<K, V> mEntries;
/**
* Creates a {@code TypedMap}.
* This {@code TypedMap} will be backed by a new {@code HashMap} instance.
*/
public TypedMap() {
mEntries = new HashMap<K, V>();
}
/**
* Creates a {@code TypedMap} containing the same elements as the given
* map.
* This {@code TypedMap} will be backed by a new {@code HashMap} instance,
* and <em>not</em> the map passed in as a paramter.
* <p/>
* <small>This is constructor is here to comply with the reccomendations for
* "standard" constructors in the {@code Map} interface.</small>
*
* @param pMap the map used to populate this map
* @throws ClassCastException if all keys in the map are not instances of
* {@code TypedMap.Key}.
* @see java.util.Map
* @see #TypedMap(java.util.Map, boolean)
*/
public TypedMap(Map<? extends K, ? extends V> pMap) {
this();
if (pMap != null) {
putAll(pMap);
}
}
/**
* Creates a {@code TypedMap}.
* This {@code TypedMap} will be backed by the given {@code Map}.
* <P/>
* Note that structurally modifying the backing map directly (not through
* this map or its collection views), is not allowed, and will produce
* undeterministic exceptions.
*
* @param pBacking the map that will be used as backing.
* @param pUseElements if {@code true}, the elements in the map are
* retained. Otherwise, the map is cleared. For an empty {@code Map} the
* parameter has no effect.
* @throws ClassCastException if {@code pUseElements} is {@code true}
* all keys in the map are not instances of {@code TypedMap.Key}.
*/
public TypedMap(Map<? extends K, ? extends V> pBacking, boolean pUseElements) {
if (pBacking == null) {
throw new IllegalArgumentException("backing == null");
}
// This is safe, as we re-insert all values later
//noinspection unchecked
mEntries = (Map<K, V>) pBacking;
// Re-insert all elements to avoid undeterministic ClassCastExceptions
if (pUseElements) {
putAll(pBacking);
}
else if (mEntries.size() > 0) {
mEntries.clear();
}
}
/**
* Returns the number of key-value mappings in this map. If the
* map contains more than {@code Integer.MAX_VALUE} elements, returns
* {@code Integer.MAX_VALUE}.
*
* @return the number of key-value mappings in this map.
*/
public int size() {
return mEntries.size();
}
/**
* Returns {@code true} if this map contains no key-value mappings.
*
* @return {@code true} if this map contains no key-value mappings.
*/
public boolean isEmpty() {
return mEntries.isEmpty();
}
/**
* Returns {@code true} if this map contains a mapping for the specified
* key.
*
* @param pKey key whose presence in this map is to be tested.
* @return {@code true} if this map contains a mapping for the specified
* key.
*/
public boolean containsKey(Object pKey) {
return mEntries.containsKey(pKey);
}
/**
* Returns {@code true} if this map maps one or more keys to the
* specified value. More formally, returns {@code true} if and only if
* this map contains at least one mapping to a value {@code v} such that
* {@code (pValue==null ? v==null : pValue.equals(v))}. This operation
* will probably require time linear in the map size for most
* implementations of the {@code Map} interface.
*
* @param pValue value whose presence in this map is to be tested.
* @return {@code true} if this map maps one or more keys to the
* specified value.
*/
public boolean containsValue(Object pValue) {
return mEntries.containsValue(pValue);
}
/**
* Returns the value to which this map maps the specified key. Returns
* {@code null} if the map contains no mapping for this key. A return
* value of {@code null} does not <i>necessarily</i> indicate that the
* map contains no mapping for the key; it's also possible that the map
* explicitly maps the key to {@code null}. The {@code containsKey}
* operation may be used to distinguish these two cases.
*
* @param pKey key whose associated value is to be returned.
* @return the value to which this map maps the specified key, or
* {@code null} if the map contains no mapping for this key.
* @see #containsKey(java.lang.Object)
*/
public V get(Object pKey) {
return mEntries.get(pKey);
}
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for
* the key, the old value is replaced.
*
* @param pKey key with which the specified value is to be associated.
* @param pValue value to be associated with the specified key.
*
* @return previous value associated with specified key, or {@code null}
* if there was no mapping for key. A {@code null} return can
* also indicate that the map previously associated {@code null}
* with the specified pKey, if the implementation supports
* {@code null} values.
*
* @throws IllegalArgumentException if the value is not compatible with the
* key.
*
* @see TypedMap.Key
*/
public V put(K pKey, V pValue) {
if (!pKey.isCompatibleValue(pValue)) {
throw new IllegalArgumentException("incompatible value for key");
}
return mEntries.put(pKey, pValue);
}
/**
* Removes the mapping for this key from this map if present (optional
* operation).
*
* @param pKey key whose mapping is to be removed from the map.
* @return previous value associated with specified key, or {@code null}
* if there was no mapping for key. A {@code null} return can
* also indicate that the map previously associated {@code null}
* with the specified key, if the implementation supports
* {@code null} values.
*/
public V remove(Object pKey) {
return mEntries.remove(pKey);
}
/**
* Copies all of the mappings from the specified map to this map
* (optional operation). These mappings will replace any mappings that
* this map had for any of the keys currently in the specified map.
* <P/>
* Note: If you override this method, make sure you add each element through
* the put method, to avoid resource leaks and undeterministic class casts.
*
* @param pMap Mappings to be stored in this map.
*/
public void putAll(Map<? extends K, ? extends V> pMap) {
for (final Entry<? extends K, ? extends V> e : pMap.entrySet()) {
put(e.getKey(), e.getValue());
}
}
/**
* Removes all mappings from this map (optional operation).
*/
public void clear() {
mEntries.clear();
}
public Collection<V> values() {
return mEntries.values();
}
public Set<Entry<K, V>> entrySet() {
return mEntries.entrySet();
}
public Set<K> keySet() {
return mEntries.keySet();
}
/**
* Keys for use with {@code TypedMap} must implement this interface.
*
* @see #isCompatibleValue(Object)
*/
public static interface Key {
/**
* Tests if the given value is compatible with this {@code Key}.
* Only compatible values may be passed to the
* {@code TypedMap.put} method.
*
* @param pValue the value to test for compatibility
* @return {@code true} if compatible, otherwise {@code false}
*/
boolean isCompatibleValue(Object pValue);
}
/**
* An abstract {@code Key} implementation that allows keys to have
* meaningful names.
*/
public static abstract class AbstractKey implements Key, Serializable {
private final String mStringRep;
/**
* Creates a {@code Key} with the given name.
*
* @param pName name of this key
*/
public AbstractKey(String pName) {
if (pName == null) {
throw new IllegalArgumentException("name == null");
}
mStringRep = getClass().getName() + '[' + pName + ']';
}
/**
* Creates a {@code Key} with no name.
*/
public AbstractKey() {
this("null");
}
@Override
public String toString() {
return mStringRep;
}
@Override
public boolean equals(Object obj) {
return obj == this ||
(obj != null && obj.getClass() == getClass() &&
mStringRep.equals(((AbstractKey) obj).mStringRep));
}
@Override
public int hashCode() {
return mStringRep.hashCode();
}
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
/**
* URLResource class description.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/URLResource.java#1 $
*/
final class URLResource extends AbstractResource {
// NOTE: For the time being, we rely on the URL class (and helpers) to do
// some smart caching and reuse of connections...
// TODO: Change the implementation if this is a problem
private long mLastModified = -1;
/**
* Creates a {@code URLResource}.
*
* @param pResourceId the resource id
* @param pURL the URL resource
*/
public URLResource(Object pResourceId, URL pURL) {
super(pResourceId, pURL);
}
private URL getURL() {
return (URL) mWrappedResource;
}
public URL asURL() {
return getURL();
}
public InputStream asStream() throws IOException {
URLConnection connection = getURL().openConnection();
connection.setAllowUserInteraction(false);
connection.setUseCaches(true);
return connection.getInputStream();
}
public long lastModified() {
try {
URLConnection connection = getURL().openConnection();
connection.setAllowUserInteraction(false);
connection.setUseCaches(true);
connection.setIfModifiedSince(mLastModified);
mLastModified = connection.getLastModified();
}
catch (IOException ignore) {
}
return mLastModified;
}
}

View File

@@ -0,0 +1,19 @@
package com.twelvemonkeys.util;
/**
* A generic visitor.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/Visitor.java#1 $
*
* @see <a href="http://en.wikipedia.org/wiki/Visitor_pattern">Visitor Patter</a>
*/
public interface Visitor<T> {
/**
* Visits an element.
*
* @param pElement the element to visit
*/
void visit(T pElement);
}

View File

@@ -0,0 +1,216 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import java.util.*;
import java.lang.ref.WeakReference;
/**
* Special-purpose map implementation with weak keys and weak values. This is
* useful for mapping between keys and values that refer to (for example by
* wrapping) their keys.
* For more info, see {@link WeakHashMap} on why the
* values in a {@code WeakHashMap} must never refer strongly to their keys.
*
* @see WeakHashMap
* @see WeakReference
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/WeakWeakMap.java#1 $
*/
@SuppressWarnings({"unchecked"})
public class WeakWeakMap<K, V> extends WeakHashMap<K, V> {
// TODO: Consider using a backing map and delegate, instead of extending...
/**
* Creates a {@code WeakWeakMap} with default initial capacity and load
* factor.
*
* @see java.util.WeakHashMap#WeakHashMap()
*/
public WeakWeakMap() {
super();
}
/**
* Creates a {@code WeakWeakMap} with the given initial capacity and
* default load factor.
*
* @param pInitialCapacity the initial capacity
*
* @see java.util.WeakHashMap#WeakHashMap(int)
*/
public WeakWeakMap(int pInitialCapacity) {
super(pInitialCapacity);
}
/**
* Creates a {@code WeakWeakMap} with the given initial capacity and
* load factor.
*
* @param pInitialCapacity the initial capacity
* @param pLoadFactor the load factor
*
* @see WeakHashMap#WeakHashMap(int, float)
*/
public WeakWeakMap(int pInitialCapacity, float pLoadFactor) {
super(pInitialCapacity, pLoadFactor);
}
/**
* Creates a {@code WeakWeakMap} containing the mappings in the given map.
*
* @param pMap the map whose mappings are to be placed in this map.
*
* @see WeakHashMap#WeakHashMap(java.util.Map)
*/
public WeakWeakMap(Map<? extends K, ? extends V> pMap) {
super(pMap);
}
@Override
public V put(K pKey, V pValue) {
// NOTE: This is wrong, but we don't really care..
return super.put(pKey, (V) new WeakReference(pValue));
}
@Override
public V get(Object pKey) {
WeakReference<V> ref = (WeakReference) super.get(pKey);
return ref != null ? ref.get() : null;
}
@Override
public V remove(Object pKey) {
WeakReference<V> ref = (WeakReference) super.remove(pKey);
return ref != null ? ref.get() : null;
}
@Override
public boolean containsValue(Object pValue) {
for (final V value : values()) {
if (pValue == value || (value != null && value.equals(pValue))) {
return true;
}
}
return false;
}
@Override
public void putAll(Map<? extends K, ? extends V> pMap) {
for (final Map.Entry<? extends K, ? extends V> entry : pMap.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
@Override
public Set<Map.Entry<K, V>> entrySet() {
return new AbstractSet<Map.Entry<K, V>>() {
public Iterator<Map.Entry<K, V>> iterator() {
return new Iterator<Map.Entry<K, V>>() {
@SuppressWarnings({"unchecked"})
final Iterator<Map.Entry<K, WeakReference<V>>> mIterator = (Iterator) WeakWeakMap.super.entrySet().iterator();
public boolean hasNext() {
return mIterator.hasNext();
}
public Map.Entry<K, V> next() {
return new Map.Entry<K, V>() {
final Map.Entry<K, WeakReference<V>> mEntry = mIterator.next();
public K getKey() {
return mEntry.getKey();
}
public V getValue() {
WeakReference<V> ref = mEntry.getValue();
return ref.get();
}
public V setValue(V pValue) {
WeakReference<V> ref = mEntry.setValue(new WeakReference<V>(pValue));
return ref != null ? ref.get() : null;
}
public boolean equals(Object obj) {
return mEntry.equals(obj);
}
public int hashCode() {
return mEntry.hashCode();
}
public String toString() {
return mEntry.toString();
}
};
}
public void remove() {
mIterator.remove();
}
};
}
public int size() {
return WeakWeakMap.this.size();
}
};
}
@Override
public Collection<V> values() {
return new AbstractCollection<V>() {
public Iterator<V> iterator() {
return new Iterator<V>() {
@SuppressWarnings({"unchecked"})
Iterator<WeakReference<V>> mIterator = (Iterator<WeakReference<V>>) WeakWeakMap.super.values().iterator();
public boolean hasNext() {
return mIterator.hasNext();
}
public V next() {
WeakReference<V> ref = mIterator.next();
return ref.get();
}
public void remove() {
mIterator.remove();
}
};
}
public int size() {
return WeakWeakMap.this.size();
}
};
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,96 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util.convert;
/**
* This exception may be thrown by PropertyConverters, when an attempted
* conversion fails.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/convert/ConversionException.java#1 $
*/
public class ConversionException extends IllegalArgumentException {
protected Throwable mCause = this;
/**
* Creates a {@code ConversionException} with the given error message.
*
* @param pMessage the error message
*/
public ConversionException(String pMessage) {
super(pMessage);
}
/**
* Creates a {@code ConversionException} with the given cause.
*
* @param pCause The Throwable that caused this exception
*/
public ConversionException(Throwable pCause) {
super(pCause == null ? null : pCause.getMessage());
initCause(pCause);
}
/**
* Returns the cause of this {@code Throwable} or {@code null} if the
* cause is nonexistent or unknown.
*
* @return the cause of this {@code Throwable} or {@code null} if the
* cause is nonexistent or unknown (the cause is the throwable that caused
* this throwable to get thrown).
*/
public Throwable getCause() {
if (mCause == this) {
return null;
}
return mCause;
}
/**
* Initializes this ConversionException with the given cause.
*
* @param pCause The Throwable that caused this exception
*
* @throws IllegalStateException if cause is allready set
* @throws IllegalArgumentException if {@code pCause == this}
*/
public Throwable initCause(Throwable pCause) {
if (mCause != this) {
throw new IllegalStateException("Can't overwrite cause");
}
if (pCause == this) {
throw new IllegalArgumentException("Can't be caused by self");
}
mCause = pCause;
return this;
}
}

View File

@@ -0,0 +1,193 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util.convert;
import com.twelvemonkeys.util.Time;
import java.util.Date;
import java.util.Hashtable;
import java.util.Map;
/**
* The converter (singleton). Converts strings to objects and back.
* This is the entrypoint to the converter framework.
* <p/>
* By default, converters for {@link com.twelvemonkeys.util.Time}, {@link Date}
* and {@link Object}
* (the {@link DefaultConverter}) are registered by this class' static
* initializer. You might remove them using the
* {@code unregisterConverter} method.
*
* @see #registerConverter(Class, PropertyConverter)
* @see #unregisterConverter(Class)
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/convert/Converter.java#1 $
*/
// TODO: Get rid of singleton stuff
// Can probably be a pure static class, but is that a good idea?
// Maybe have BeanUtil act as a "proxy", and hide this class alltogheter?
// TODO: ServiceRegistry for registering 3rd party converters
// TODO: URI scheme, for implicit typing? Is that a good idea?
public abstract class Converter implements PropertyConverter {
/** Our singleton instance */
protected static Converter sInstance = new ConverterImpl(); // Thread safe & EASY
/** The conveters Map */
protected Map mConverters = new Hashtable();
// Register our predefined converters
static {
PropertyConverter defaultConverter = new DefaultConverter();
registerConverter(Object.class, defaultConverter);
registerConverter(Boolean.TYPE, defaultConverter);
PropertyConverter numberConverter = new NumberConverter();
registerConverter(Number.class, numberConverter);
registerConverter(Byte.TYPE, numberConverter);
registerConverter(Double.TYPE, numberConverter);
registerConverter(Float.TYPE, numberConverter);
registerConverter(Integer.TYPE, numberConverter);
registerConverter(Long.TYPE, numberConverter);
registerConverter(Short.TYPE, numberConverter);
registerConverter(Date.class, new DateConverter());
registerConverter(Time.class, new TimeConverter());
}
/**
* Creates a Converter.
*/
protected Converter() {
}
/**
* Gets the Converter instance.
*
* @return the converter instance
*/
public static Converter getInstance() {
return sInstance;
}
/**
* Registers a converter for a given type.
* This converter will also be used for all subclasses, unless a more
* specific version is registered.
* </p>
* By default, converters for {@link com.twelvemonkeys.util.Time}, {@link Date}
* and {@link Object}
* (the {@link DefaultConverter}) are registered by this class' static
* initializer. You might remove them using the
* {@code unregisterConverter} method.
*
* @param pType the (super) type to register a converter for
* @param pConverter the converter
*
* @see #unregisterConverter(Class)
*/
public static void registerConverter(Class pType, PropertyConverter pConverter) {
getInstance().mConverters.put(pType, pConverter);
}
/**
* Unregisters a converter for a given type. That is, making it unavailable
* for the converter framework, and making it (potentially) available for
* garbabe collection.
*
* @param pType the (super) type to remove converter for
*
* @see #registerConverter(Class,PropertyConverter)
*/
public static void unregisterConverter(Class pType) {
getInstance().mConverters.remove(pType);
}
/**
* Converts the string to an object of the given type.
*
* @param pString the string to convert
* @param pType the type to convert to
*
* @return the object created from the given string.
*
* @throws ConversionException if the string cannot be converted for any
* reason.
*/
public Object toObject(String pString, Class pType)
throws ConversionException {
return toObject(pString, pType, null);
}
/**
* Converts the string to an object of the given type, parsing after the
* given format.
*
* @param pString the string to convert
* @param pType the type to convert to
* @param pFormat the (optional) conversion format
*
* @return the object created from the given string.
*
* @throws ConversionException if the string cannot be converted for any
* reason.
*/
public abstract Object toObject(String pString, Class pType, String pFormat)
throws ConversionException;
/**
* Converts the object to a string, using {@code object.toString()}
*
* @param pObject the object to convert.
*
* @return the string representation of the object, on the correct format.
*
* @throws ConversionException if the object cannot be converted to a
* string for any reason.
*/
public String toString(Object pObject) throws ConversionException {
return toString(pObject, null);
}
/**
* Converts the object to a string, using {@code object.toString()}
*
* @param pObject the object to convert.
* @param pFormat the (optional) conversion format
*
* @return the string representation of the object, on the correct format.
*
* @throws ConversionException if the object cannot be converted to a
* string for any reason.
*/
public abstract String toString(Object pObject, String pFormat)
throws ConversionException;
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util.convert;
/**
* The converter (singleton). Converts strings to objects and back.
* This is the entrypoint to the converter framework.
*
* @see #registerConverter(Class, PropertyConverter)
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/convert/ConverterImpl.java#1 $
*/
class ConverterImpl extends Converter {
/**
* Creates a Converter.
*/
ConverterImpl() {
}
/**
* Gets the registered converter for the given type.
*
* @param pType the type to convert to
* @return an instance of a {@code PropertyConverter} or {@code null}
*/
private PropertyConverter getConverterForType(Class pType) {
Object converter;
Class cl = pType;
// Loop until we find a suitable converter
do {
// Have a match, return converter
if ((converter = getInstance().mConverters.get(cl)) != null) {
return (PropertyConverter) converter;
}
}
while ((cl = cl.getSuperclass()) != null);
// No converter found, return null
return null;
}
/**
* Converts the string to an object of the given type, parsing after the
* given format.
*
* @param pString the string to convert
* @param pType the type to convert to
* @param pFormat the vonversion format
*
* @return the object created from the given string.
*
* @throws ConversionException if the string cannot be converted for any
* reason.
*/
public Object toObject(String pString, Class pType, String pFormat)
throws ConversionException {
if (pString == null) {
return null;
}
if (pType == null) {
throw new MissingTypeException();
}
// Get converter
PropertyConverter converter = getConverterForType(pType);
if (converter == null) {
throw new NoAvailableConverterException("Cannot convert to object, no converter available for type \"" + pType.getName() + "\"");
}
// Convert and return
return converter.toObject(pString, pType, pFormat);
}
/**
* Converts the object to a string, using {@code object.toString()}
*
* @param pBean the object to convert
* @param pFormat the conversion format
*
* @return the string representation of the object, on the correct format.
*
* @throws ConversionException if the object cannot be converted to a
* string for any reason.
*/
public String toString(Object pBean, String pFormat)
throws ConversionException {
if (pBean == null) {
return null;
}
// Get converter
PropertyConverter converter = getConverterForType(pBean.getClass());
if (converter == null) {
throw new NoAvailableConverterException("Cannot object to string, no converter available for type \"" + pBean.getClass().getName() + "\"");
}
// Convert and return string
return converter.toString(pBean, pFormat);
}
}

View File

@@ -0,0 +1,152 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util.convert;
import com.twelvemonkeys.lang.*;
import java.util.*;
import java.text.*;
import java.lang.reflect.InvocationTargetException;
/**
* Converts strings to dates and back.
* <p/>
* <small>This class has a static cache of {@code DateFormats}, to avoid
* creation and parsing of dateformats every time one is used.</small>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/convert/DateConverter.java#2 $
*/
public class DateConverter extends NumberConverter {
/** Creates a {@code DateConverter} */
public DateConverter() {
}
/**
* Converts the string to a date, using the given format for parsing.
*
* @param pString the string to convert.
* @param pType the type to convert to. {@code java.util.Date} and
* subclasses allowed.
* @param pFormat the format used for parsing. Must be a legal
* {@code SimpleDateFormat} format, or {@code null} which will use the
* default format.
*
* @return the object created from the given string. May safely be typecast
* to {@code java.util.Date}
*
* @see Date
* @see java.text.DateFormat
*
* @throws ConversionException
*/
public Object toObject(String pString, Class pType, String pFormat)
throws ConversionException {
if (StringUtil.isEmpty(pString))
return null;
try {
DateFormat format;
if (pFormat == null) {
// Use system default format, using default locale
format = DateFormat.getDateTimeInstance();
}
else {
// Get format from cache
format = getDateFormat(pFormat);
}
Date date = StringUtil.toDate(pString, format);
// Allow for conversion to Date subclasses (ie. java.sql.*)
if (pType != Date.class) {
try {
date = (Date) BeanUtil.createInstance(pType, new Long(date.getTime()));
}
catch (ClassCastException e) {
throw new TypeMismathException(pType);
}
catch (InvocationTargetException e) {
throw new ConversionException(e);
}
}
return date;
}
catch (RuntimeException rte) {
throw new ConversionException(rte);
}
}
/**
* Converts the object to a string, using the given format
*
* @param pObject the object to convert.
* @param pFormat the format used for conversion. Must be a legal
* {@code SimpleDateFormat} format, or {@code null} which will use the
* default format.
*
* @return the string representation of the object, on the correct format.
*
* @throws ConversionException if the object is not a subclass of
* {@code java.util.Date}
*
* @see Date
* @see java.text.DateFormat
*/
public String toString(Object pObject, String pFormat)
throws ConversionException {
if (pObject == null)
return null;
if (!(pObject instanceof Date))
throw new TypeMismathException(pObject.getClass());
try {
// Convert to string, default way
if (StringUtil.isEmpty(pFormat)) {
return DateFormat.getDateTimeInstance().format(pObject);
}
// Convert to string, using format
DateFormat format = getDateFormat(pFormat);
return format.format(pObject);
}
catch (RuntimeException rte) {
throw new ConversionException(rte);
}
}
private DateFormat getDateFormat(String pFormat) {
return (DateFormat) getFormat(SimpleDateFormat.class, pFormat);
}
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util.convert;
import com.twelvemonkeys.lang.*;
import java.lang.reflect.*;
/**
* Converts strings to objects and back.
* <p/>
* This converter first tries to create an object, using the class' single
* string argument constructor ({@code &lt;type&gt;(String)}) if found,
* otherwise, an attempt to call
* the class' static {@code valueOf(String)} method. If both fails, a
* {@link ConversionException} is thrown.
*
* @author <A href="haraldk@iconmedialab.no">Harald Kuhr</A>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/convert/DefaultConverter.java#2 $
*
*/
public final class DefaultConverter implements PropertyConverter {
/**
* Creates a {@code DefaultConverter}.
*/
public DefaultConverter() {
}
/**
* Converts the string to an object of the given type.
*
* @param pString the string to convert
* @param pType the type to convert to
* @param pFormat ignored.
*
* @return the object created from the given string.
*
* @throws ConversionException if the type is null, or if the string cannot
* be converted into the given type, using a string constructor or static
* {@code valueof} method.
*/
public Object toObject(String pString, final Class pType, String pFormat)
throws ConversionException {
if (pString == null) {
return null;
}
if (pType == null) {
throw new MissingTypeException();
}
// Primitive -> wrapper
Class type;
if (pType == Boolean.TYPE) {
type = Boolean.class;
}
else {
type = pType;
}
try {
// Try to create instance from <Constructor>(String)
Object value = BeanUtil.createInstance(type, pString);
if (value == null) {
// createInstance failed for some reason
// Try to invoke the static method valueof(String)
value = BeanUtil.invokeStaticMethod(type, "valueOf", pString);
if (value == null) {
// If the value is still null, well, then I cannot help...
throw new ConversionException("Could not convert String to " + pType.getName() + ": No constructor " + type.getName() + "(String) or static " + type.getName() + ".valueof(String) method found!");
}
}
return value;
}
catch (InvocationTargetException ite) {
throw new ConversionException(ite.getTargetException());
}
catch (RuntimeException rte) {
throw new ConversionException(rte);
}
}
/**
* Converts the object to a string, using {@code pObject.toString()}.
*
* @param pObject the object to convert.
* @param pFormat ignored.
*
* @return the string representation of the object, or {@code null} if
* {@code pObject == null}
*/
public String toString(Object pObject, String pFormat)
throws ConversionException {
try {
return (pObject != null ? pObject.toString() : null);
}
catch (RuntimeException rte) {
throw new ConversionException(rte);
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util.convert;
/**
* This exception may be thrown by {@code PropertyConverter}s, if a conversion is
* attempted without type (class).
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/convert/MissingTypeException.java#2 $
*/
public class MissingTypeException extends ConversionException {
/**
* Creates a {@code MissingTypeException} with no error message.
*/
public MissingTypeException() {
super("Cannot convert, missing type");
}
/**
* Creates a {@code MissingTypeException} with the given error message.
*
* @param pMessage the exception message
*/
public MissingTypeException(String pMessage) {
super(pMessage);
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util.convert;
/**
* This exception may be thrown by {@code PropertyConverter}s, if a conversion
* is attempted for a type (class), that has no registered {@code PropertyConverter}.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/convert/NoAvailableConverterException.java#2 $
*/
public class NoAvailableConverterException extends ConversionException {
/**
* Creates a {@code NoAvailableConverterException} with no error message.
*/
public NoAvailableConverterException() {
super("Cannot convert, no converter available for given type");
}
/**
* Creates a {@code NoAvailableConverterException} with the given error message.
*
* @param pMessage the exception message
*/
public NoAvailableConverterException(String pMessage) {
super(pMessage);
}
}

View File

@@ -0,0 +1,208 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util.convert;
import com.twelvemonkeys.lang.*;
import com.twelvemonkeys.util.LRUHashMap;
import java.util.*;
import java.math.*;
import java.text.*;
/**
* Converts strings to numbers and back.
* <p/>
* <small>This class has a static cache of {@code NumberFormats}, to avoid
* creation and parsing of numberformats every time one is used.</small>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/convert/NumberConverter.java#2 $
*/
public class NumberConverter implements PropertyConverter {
// TODO: Need to either make this non-local aware, or document that it is...
private static final DecimalFormatSymbols SYMBOLS = new DecimalFormatSymbols(Locale.US);
private static final NumberFormat sDefaultFormat = new DecimalFormat("#0.#", SYMBOLS);
private static final Map<String, Format> sFormats = new LRUHashMap<String, Format>(50);
public NumberConverter() {
}
/**
* Converts the string to a number, using the given format for parsing.
*
* @param pString the string to convert.
* @param pType the type to convert to. PropertyConverter
* implementations may choose to ignore this parameter.
* @param pFormat the format used for parsing. PropertyConverter
* implementations may choose to ignore this parameter. Also,
* implementations that require a parser format, should provide a default
* format, and allow {@code null} as the format argument.
*
* @return the object created from the given string. May safely be typecast
* to {@code java.lang.Number} or the class of the {@code type} parameter.
*
* @see Number
* @see java.text.NumberFormat
*
* @throws ConversionException
*/
public Object toObject(final String pString, final Class pType, final String pFormat) throws ConversionException {
if (StringUtil.isEmpty(pString)) {
return null;
}
try {
if (pType.equals(BigInteger.class)) {
return new BigInteger(pString); // No format?
}
if (pType.equals(BigDecimal.class)) {
return new BigDecimal(pString); // No format?
}
NumberFormat format;
if (pFormat == null) {
// Use system default format, using default locale
// format = NumberFormat.getNumberInstance();
format = sDefaultFormat;
}
else {
// Get format from cache
format = getNumberFormat(pFormat);
}
Number num;
synchronized (format) {
num = format.parse(pString);
}
if (pType == Integer.TYPE || pType == Integer.class) {
return num.intValue();
}
else if (pType == Long.TYPE || pType == Long.class) {
return num.longValue();
}
else if (pType == Double.TYPE || pType == Double.class) {
return num.doubleValue();
}
else if (pType == Float.TYPE || pType == Float.class) {
return num.floatValue();
}
else if (pType == Byte.TYPE || pType == Byte.class) {
return num.byteValue();
}
else if (pType == Short.TYPE || pType == Short.class) {
return num.shortValue();
}
return num;
}
catch (ParseException pe) {
throw new ConversionException(pe);
}
catch (RuntimeException rte) {
throw new ConversionException(rte);
}
}
/**
* Converts the object to a string, using the given format
*
* @param pObject the object to convert.
* @param pFormat the format used for parsing. PropertyConverter
* implementations may choose to ignore this parameter. Also,
* implementations that require a parser format, should provide a default
* format, and allow {@code null} as the format argument.
*
* @return the string representation of the object, on the correct format.
*
* @throws ConversionException if the object is not a subclass of {@link java.lang.Number}
*/
public String toString(final Object pObject, final String pFormat)
throws ConversionException {
if (pObject == null) {
return null;
}
if (!(pObject instanceof Number)) {
throw new TypeMismathException(pObject.getClass());
}
try {
// Convert to string, default way
if (StringUtil.isEmpty(pFormat)) {
// return NumberFormat.getNumberInstance().format(pObject);
return sDefaultFormat.format(pObject);
}
// Convert to string, using format
NumberFormat format = getNumberFormat(pFormat);
synchronized (format) {
return format.format(pObject);
}
}
catch (RuntimeException rte) {
throw new ConversionException(rte);
}
}
private NumberFormat getNumberFormat(String pFormat) {
return (NumberFormat) getFormat(DecimalFormat.class, pFormat, SYMBOLS);
}
protected final Format getFormat(Class pFormatterClass, Object... pFormat) {
// Try to get format from cache
synchronized (sFormats) {
String key = pFormatterClass.getName() + ":" + Arrays.toString(pFormat);
Format format = sFormats.get(key);
if (format == null) {
// If not found, create...
try {
format = (Format) BeanUtil.createInstance(pFormatterClass, pFormat);
}
catch (Exception e) {
e.printStackTrace();
return null;
}
// ...and store in cache
sFormats.put(key, format);
}
return format;
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util.convert;
/**
* Converts strings to objects and back.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/convert/PropertyConverter.java#1 $
*/
public interface PropertyConverter {
/**
* Converts the string to an object, using the given format for parsing.
*
* @param pString the string to convert
* @param pType the type to convert to. {@code PropertyConverter}
* implementations may choose to ignore this parameter.
* @param pFormat the format used for parsing. {@code PropertyConverter}
* implementations may choose to ignore this parameter. Also,
* implementations that require a parser format, should provide a default
* format, and allow {@code null} as the format argument.
*
* @return the object created from the given string.
*
* @throws ConversionException if the string could not be converted to the
* specified type and format.
*/
public Object toObject(String pString, Class pType, String pFormat)
throws ConversionException;
/**
* Converts the object to a string, using the given format
*
* @param pObject the object to convert
* @param pFormat the format used for parsing. {@code PropertyConverter}
* implementations may choose to ignore this parameter. Also,
* implementations that require a parser format, should provide a default
* format, and allow {@code null} as the format argument.
*
* @return the string representation of the object, on the correct format.
*
* @throws ConversionException if the string could not be converted to the
* specified type and format.
*/
public String toString(Object pObject, String pFormat)
throws ConversionException;
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util.convert;
import com.twelvemonkeys.lang.*;
import com.twelvemonkeys.util.Time;
import com.twelvemonkeys.util.TimeFormat;
/**
* Converts strings to times and back.
* <p/>
* <small>This class has a static cache of {@code TimeFormats}, to avoid creation and
* parsing of timeformats every time one is used.</small>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/convert/TimeConverter.java#1 $
*/
public class TimeConverter extends NumberConverter {
public TimeConverter() {
}
/**
* Converts the string to a time, using the given format for parsing.
*
* @param pString the string to convert.
* @param pType the type to convert to. PropertyConverter
* implementations may choose to ignore this parameter.
* @param pFormat the format used for parsing. PropertyConverter
* implementations may choose to ignore this parameter. Also,
* implementations that require a parser format, should provide a default
* format, and allow {@code null} as the format argument.
*
* @return the object created from the given string. May safely be typecast
* to {@code com.twelvemonkeys.util.Time}
*
* @see com.twelvemonkeys.util.Time
* @see com.twelvemonkeys.util.TimeFormat
*
* @throws ConversionException
*/
public Object toObject(String pString, Class pType, String pFormat)
throws ConversionException {
if (StringUtil.isEmpty(pString))
return null;
TimeFormat format;
try {
if (pFormat == null) {
// Use system default format
format = TimeFormat.getInstance();
}
else {
// Get format from cache
format = getTimeFormat(pFormat);
}
return format.parse(pString);
}
catch (RuntimeException rte) {
throw new ConversionException(rte);
}
}
/**
* Converts the object to a string, using the given format
*
* @param pObject the object to convert.
* @param pFormat the format used for parsing. PropertyConverter
* implementations may choose to ignore this parameter. Also,
* implementations that require a parser format, should provide a default
* format, and allow {@code null} as the format argument.
*
* @return the string representation of the object, on the correct format.
*
* @throws ConversionException if the object is not a subclass of
* {@code com.twelvemonkeys.util.Time}
*
* @see com.twelvemonkeys.util.Time
* @see com.twelvemonkeys.util.TimeFormat
*/
public String toString(Object pObject, String pFormat)
throws ConversionException {
if (pObject == null)
return null;
if (!(pObject instanceof com.twelvemonkeys.util.Time))
throw new TypeMismathException(pObject.getClass());
try {
// Convert to string, default way
if (StringUtil.isEmpty(pFormat))
return pObject.toString();
// Convert to string, using format
TimeFormat format = getTimeFormat(pFormat);
return format.format((Time) pObject);
}
catch (RuntimeException rte) {
throw new ConversionException(rte);
}
}
private TimeFormat getTimeFormat(String pFormat) {
return (TimeFormat) getFormat(TimeFormat.class, pFormat);
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util.convert;
/**
* This exception may be thrown by {@code PropertyConverter}s, if a conversion
* is attempted on the wrong type (class).
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/convert/TypeMismathException.java#1 $
*/
public class TypeMismathException extends ConversionException {
/**
* Creates a {@code TypeMismathException}.
*
* @param pWrongType the type we couldn't convert
*/
public TypeMismathException(Class pWrongType) {
super("Wrong type for conversion: " + pWrongType.getName());
}
}

View File

@@ -0,0 +1,5 @@
/**
* Provides a general purpose conversion framework, for conversion of values
* between string representations and objects.
*/
package com.twelvemonkeys.util.convert;

View File

@@ -0,0 +1,4 @@
/**
* Provides miscellaneous utility classes.
*/
package com.twelvemonkeys.util;

View File

@@ -0,0 +1,105 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util.regex;
import com.twelvemonkeys.util.AbstractTokenIterator;
import java.util.NoSuchElementException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
/**
* {@code StringTokenizer} replacement, that uses regular expressions to split
* strings into tokens.
* <p/>
* @see java.util.regex.Pattern for pattern syntax.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/regex/RegExTokenIterator.java#1 $
*/
public class RegExTokenIterator extends AbstractTokenIterator {
private final Matcher mMatcher;
private boolean mNext = false;
/**
* Creates a {@code RegExTokenIterator}.
* Default pettern is {@code "\S+"}.
*
* @param pString the string to be parsed.
*
* @throws IllegalArgumentException if {@code pString} is {@code null}
*/
public RegExTokenIterator(String pString) {
this(pString, "\\S+");
}
/**
* Creates a {@code RegExTokenIterator}.
*
* @see Pattern for pattern syntax.
*
* @param pString the string to be parsed.
* @param pPattern the pattern
*
* @throws PatternSyntaxException if {@code pPattern} is not a valid pattern
* @throws IllegalArgumentException if any of the arguments are {@code null}
*/
public RegExTokenIterator(String pString, String pPattern) {
if (pString == null) {
throw new IllegalArgumentException("string == null");
}
if (pPattern == null) {
throw new IllegalArgumentException("pattern == null");
}
mMatcher = Pattern.compile(pPattern).matcher(pString);
}
/**
* Resets this iterator.
*
*/
public void reset() {
mMatcher.reset();
}
public boolean hasNext() {
return mNext || (mNext = mMatcher.find());
}
public String next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
mNext = false;
return mMatcher.group();
}
}

View File

@@ -0,0 +1,783 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util.regex;
import java.io.PrintStream;
/**
* This class parses arbitrary strings against a wildcard string mask provided.
* The wildcard characters are '*' and '?'.
* <p/>
* The string masks provided are treated as case sensitive.<br>
* Null-valued string masks as well as null valued strings to be parsed, will lead to rejection.
* <p/>
* <p/>
* <p/>
* <i>This class is custom designed for wildcard string parsing and is several times faster than the implementation based on the Jakarta Regexp package.</i>
* <p/>
* <p><hr style="height=1"><p>
* <p/>
* This task is performed based on regular expression techniques.
* The possibilities of string generation with the well-known wildcard characters stated above,
* represent a subset of the possibilities of string generation with regular expressions.<br>
* The '*' corresponds to ([Union of all characters in the alphabet])*<br>
* The '?' corresponds to ([Union of all characters in the alphabet])<br>
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<small>These expressions are not suited for textual representation at all, I must say. Is there any math tags included in HTML?</small>
* <p/>
* <p/>
* <p/>
* The complete meta-language for regular expressions are much larger.
* This fact makes it fairly straightforward to build data structures for parsing because the amount of rules of building these structures are quite limited, as stated below.
* <p/>
* <p/>
* <p/>
* To bring this over to mathematical terms:
* The parser ia a <b>nondeterministic finite automaton</b> (latin) representing the <b>grammar</b> which is stated by the string mask.
* The <b>language</b> accepted by this automaton is the set of all strings accepted by this automaton.<br>
* The formal automaton quintuple consists of:
* <ol>
* <li>A finite set of <b>states</b>, depending on the wildcard string mask.
* For each character in the mask a state representing that character is created.
* The number of states therefore coincides with the length of the mask.
* <li>An <b>alphabet</b> consisting of all legal filename characters - included the two wildcard characters '*' and '?'.
* This alphabet is hard-coded in this class. It contains {a .. <20>}, {A .. <20>}, {0 .. 9}, {.}, {_}, {-}, {*} and {?}.
* <li>A finite set of <b>initial states</b>, here only consisting of the state corresponding to the first character in the mask.
* <li>A finite set of <b>final states</b>, here only consisting of the state corresponding to the last character in the mask.
* <li>A <b>transition relation</b> that is a finite set of transitions satisfying some formal rules.<br>
* This implementation on the other hand, only uses ad-hoc rules which start with an initial setup of the states as a sequence according to the string mask.<br>
* Additionally, the following rules completes the building of the automaton:
* <ol>
* <li>If the next state represents the same character as the next character in the string to test - go to this next state.
* <li>If the next state represents '*' - go to this next state.
* <li>If the next state represents '?' - go to this next state.
* <li>If a '*' is followed by one or more '?', the last of these '?' state counts as a '*' state. Some extra checks regarding the number of characters read must be imposed if this is the case...
* <li>If the next character in the string to test does not coincide with the next state - go to the last state representing '*'. If there are none - rejection.
* <li>If there are no subsequent state (final state) and the state represents '*' - acceptance.
* <li>If there are no subsequent state (final state) and the end of the string to test is reached - acceptance.
* </ol>
* <br>
* <small>
* Disclaimer: This class does not build a finite automaton according to formal mathematical rules.
* The proper way of implementation should be finding the complete set of transition relations, decomposing these into rules accepted by a <i>deterministic</i> finite automaton and finally build this automaton to be used for string parsing.
* Instead, this class is ad-hoc implemented based on the informal transition rules stated above.
* Therefore the correctness cannot be guaranteed before extensive testing has been imposed on this class... anyway, I think I have succeeded.
* Parsing faults must be reported to the author.
* </small>
* </ol>
* <p/>
* <p><hr style="height=1"><p>
* <p/>
* Examples of usage:<br>
* This example will return "Accepted!".
* <pre>
* WildcardStringParser parser = new WildcardStringParser("*_28????.jp*");
* if (parser.parseString("gupu_280915.jpg")) {
* System.out.println("Accepted!");
* } else {
* System.out.println("Not accepted!");
* }
* </pre>
* <p/>
* <p><hr style="height=1"><p>
* <p/>
* Theories and concepts are based on the book <i>Elements of the Theory of Computation</i>, by Harry l. Lewis and Christos H. Papadimitriou, (c) 1981 by Prentice Hall.
* <p/>
* <p/>
*
* @author <a href="mailto:eirik.torske@iconmedialab.no">Eirik Torske</a>
* @deprecated Will probably be removed in the near future
*/
public class WildcardStringParser {
// Constants
/** Field ALPHABET */
public static final char[] ALPHABET = {
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '<27>',
'<27>', '<27>', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'N', 'M', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
'Z', '<27>', '<27>', '<27>', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '_', '-'
};
/** Field FREE_RANGE_CHARACTER */
public static final char FREE_RANGE_CHARACTER = '*';
/** Field FREE_PASS_CHARACTER */
public static final char FREE_PASS_CHARACTER = '?';
// Members
boolean mInitialized;
String mStringMask;
WildcardStringParserState mInitialState;
int mTotalNumberOfStringsParsed;
boolean mDebugging;
PrintStream out;
// Properties
// Constructors
/**
* Creates a wildcard string parser.
* <p/>
*
* @param pStringMask the wildcard string mask.
*/
public WildcardStringParser(final String pStringMask) {
this(pStringMask, false);
}
/**
* Creates a wildcard string parser.
* <p/>
*
* @param pStringMask the wildcard string mask.
* @param pDebugging {@code true} will cause debug messages to be emitted to {@code System.out}.
*/
public WildcardStringParser(final String pStringMask, final boolean pDebugging) {
this(pStringMask, pDebugging, System.out);
}
/**
* Creates a wildcard string parser.
* <p/>
*
* @param pStringMask the wildcard string mask.
* @param pDebugging {@code true} will cause debug messages to be emitted.
* @param pDebuggingPrintStream the {@code java.io.PrintStream} to which the debug messages will be emitted.
*/
public WildcardStringParser(final String pStringMask, final boolean pDebugging, final PrintStream pDebuggingPrintStream) {
this.mStringMask = pStringMask;
this.mDebugging = pDebugging;
this.out = pDebuggingPrintStream;
mInitialized = buildAutomaton();
}
// Methods
private boolean checkIfStateInWildcardRange(WildcardStringParserState pState) {
WildcardStringParserState runnerState = pState;
while (runnerState.mPreviousState != null) {
runnerState = runnerState.mPreviousState;
if (isFreeRangeCharacter(runnerState.mChar)) {
return true;
}
if (!isFreePassCharacter(runnerState.mChar)) {
return false;
} // If free-pass char '?' - move on
}
return false;
}
private boolean checkIfLastFreeRangeState(WildcardStringParserState pState) {
if (isFreeRangeCharacter(pState.mChar)) {
return true;
}
if (isFreePassCharacter(pState.mChar)) {
if (checkIfStateInWildcardRange(pState)) {
return true;
}
}
return false;
}
/** @return {@code true} if and only if the string mask only consists of free-range wildcard character(s). */
private boolean isTrivialAutomaton() {
for (int i = 0; i < mStringMask.length(); i++) {
if (!isFreeRangeCharacter(mStringMask.charAt(i))) {
return false;
}
}
return true;
}
private boolean buildAutomaton() {
char activeChar;
WildcardStringParserState runnerState = null;
WildcardStringParserState newState = null;
WildcardStringParserState lastFreeRangeState = null;
// Create the initial state of the automaton
if ((mStringMask != null) && (mStringMask.length() > 0)) {
newState = new WildcardStringParserState(mStringMask.charAt(0));
newState.mAutomatonStateNumber = 0;
newState.mPreviousState = null;
if (checkIfLastFreeRangeState(newState)) {
lastFreeRangeState = newState;
}
runnerState = newState;
mInitialState = runnerState;
mInitialState.mAutomatonStateNumber = 0;
}
else {
System.err.println("string mask provided are null or empty - aborting!");
return false;
}
// Create the rest of the automaton
for (int i = 1; i < mStringMask.length(); i++) {
activeChar = mStringMask.charAt(i);
// Check if the char is an element in the alphabet or is a wildcard character
if (!((isInAlphabet(activeChar)) || (isWildcardCharacter(activeChar)))) {
System.err.println("one or more characters in string mask are not legal characters - aborting!");
return false;
}
// Set last free-range state before creating/checking the next state
runnerState.mLastFreeRangeState = lastFreeRangeState;
// Create next state, check if free-range state, set the state number and preceeding state
newState = new WildcardStringParserState(activeChar);
newState.mAutomatonStateNumber = i;
newState.mPreviousState = runnerState;
// Special check if the state represents an '*' or '?' with only preceeding states representing '?' and '*'
if (checkIfLastFreeRangeState(newState)) {
lastFreeRangeState = newState;
}
// Set the succeding state before moving to the next state
runnerState.mNextState = newState;
// Move to the next state
runnerState = newState;
// Special setting of the last free-range state for the last element
if (runnerState.mAutomatonStateNumber == mStringMask.length() - 1) {
runnerState.mLastFreeRangeState = lastFreeRangeState;
}
}
// Initiate some statistics
mTotalNumberOfStringsParsed = 0;
return true;
}
/** Tests if a certain character is a valid character in the alphabet that is applying for this automaton. */
public static boolean isInAlphabet(final char pCharToCheck) {
for (int i = 0; i < ALPHABET.length; i++) {
if (pCharToCheck == ALPHABET[i]) {
return true;
}
}
return false;
}
/** Tests if a certain character is the designated "free-range" character ('*'). */
public static boolean isFreeRangeCharacter(final char pCharToCheck) {
return pCharToCheck == FREE_RANGE_CHARACTER;
}
/** Tests if a certain character is the designated "free-pass" character ('?'). */
public static boolean isFreePassCharacter(final char pCharToCheck) {
return pCharToCheck == FREE_PASS_CHARACTER;
}
/** Tests if a certain character is a wildcard character ('*' or '?'). */
public static boolean isWildcardCharacter(final char pCharToCheck) {
return ((isFreeRangeCharacter(pCharToCheck)) || (isFreePassCharacter(pCharToCheck)));
}
/**
* Gets the string mask that was used when building the parser atomaton.
* <p/>
*
* @return the string mask used for building the parser automaton.
*/
public String getStringMask() {
return mStringMask;
}
/**
* Parses a string according to the rules stated above.
* <p/>
*
* @param pStringToParse the string to parse.
* @return {@code true} if and only if the string are accepted by the automaton.
*/
public boolean parseString(final String pStringToParse) {
if (mDebugging) {
out.println("parsing \"" + pStringToParse + "\"...");
}
// Update statistics
mTotalNumberOfStringsParsed++;
// Check string to be parsed for nullness
if (pStringToParse == null) {
if (mDebugging) {
out.println("string to be parsed is null - rejection!");
}
return false;
}
// Create parsable string
ParsableString parsableString = new ParsableString(pStringToParse);
// Check string to be parsed
if (!parsableString.checkString()) {
if (mDebugging) {
out.println("one or more characters in string to be parsed are not legal characters - rejection!");
}
return false;
}
// Check if automaton is correctly initialized
if (!mInitialized) {
System.err.println("automaton is not initialized - rejection!");
return false;
}
// Check if automaton is trivial (accepts all strings)
if (isTrivialAutomaton()) {
if (mDebugging) {
out.println("automaton represents a trivial string mask (accepts all strings) - acceptance!");
}
return true;
}
// Check if string to be parsed is empty
if (parsableString.isEmpty()) {
if (mDebugging) {
out.println("string to be parsed is empty and not trivial automaton - rejection!");
}
return false;
}
// Flag and more to indicate that state skipping due to sequence of '?' succeeding a '*' has been performed
boolean hasPerformedFreeRangeMovement = false;
int numberOfFreePassCharactersRead_SinceLastFreePassState = 0;
int numberOfParsedCharactersRead_SinceLastFreePassState = 0;
WildcardStringParserState runnerState = null;
// Accepted by the first state?
if ((parsableString.mCharArray[0] == mInitialState.mChar) || isWildcardCharacter(mInitialState.mChar)) {
runnerState = mInitialState;
parsableString.mIndex = 0;
}
else {
if (mDebugging) {
out.println("cannot enter first automaton state - rejection!");
}
return false;
}
// Initialize the free-pass character state visited count
if (isFreePassCharacter(runnerState.mChar)) {
numberOfFreePassCharactersRead_SinceLastFreePassState++;
}
// Perform parsing according to the rules above
for (int i = 0; i < parsableString.length(); i++) {
if (mDebugging) {
out.println();
}
if (mDebugging) {
out.println("parsing - index number " + i + ", active char: '"
+ parsableString.getActiveChar() + "' char string index: " + parsableString.mIndex
+ " number of chars since last free-range state: " + numberOfParsedCharactersRead_SinceLastFreePassState);
}
if (mDebugging) {
out.println("parsing - state: " + runnerState.mAutomatonStateNumber + " '"
+ runnerState.mChar + "' - no of free-pass chars read: " + numberOfFreePassCharactersRead_SinceLastFreePassState);
}
if (mDebugging) {
out.println("parsing - hasPerformedFreeRangeMovement: " + hasPerformedFreeRangeMovement);
}
if (runnerState.mNextState == null) {
if (mDebugging) {
out.println("parsing - runnerState.mNextState == null");
}
// If there are no subsequent state (final state) and the state represents '*' - acceptance!
if (isFreeRangeCharacter(runnerState.mChar)) {
// Special free-range skipping check
if (hasPerformedFreeRangeMovement) {
if (parsableString.reachedEndOfString()) {
if (numberOfFreePassCharactersRead_SinceLastFreePassState > numberOfParsedCharactersRead_SinceLastFreePassState) {
if (mDebugging) {
out.println(
"no subsequent state (final state) and the state represents '*' - end of parsing string, but not enough characters read - rejection!");
}
return false;
}
else {
if (mDebugging) {
out.println(
"no subsequent state (final state) and the state represents '*' - end of parsing string and enough characters read - acceptance!");
}
return true;
}
}
else {
if (numberOfFreePassCharactersRead_SinceLastFreePassState > numberOfParsedCharactersRead_SinceLastFreePassState) {
if (mDebugging) {
out.println(
"no subsequent state (final state) and the state represents '*' - not the end of parsing string and not enough characters read - read next character");
}
parsableString.mIndex++;
numberOfParsedCharactersRead_SinceLastFreePassState++;
}
else {
if (mDebugging) {
out.println(
"no subsequent state (final state) and the state represents '*' - not the end of parsing string, but enough characters read - acceptance!");
}
return true;
}
}
}
else {
if (mDebugging) {
out.println("no subsequent state (final state) and the state represents '*' - no skipping performed - acceptance!");
}
return true;
}
}
// If there are no subsequent state (final state) and no skipping has been performed and the end of the string to test is reached - acceptance!
else if (parsableString.reachedEndOfString()) {
// Special free-range skipping check
if ((hasPerformedFreeRangeMovement)
&& (numberOfFreePassCharactersRead_SinceLastFreePassState > numberOfParsedCharactersRead_SinceLastFreePassState)) {
if (mDebugging) {
out.println(
"no subsequent state (final state) and skipping has been performed and end of parsing string, but not enough characters read - rejection!");
}
return false;
}
if (mDebugging) {
out.println("no subsequent state (final state) and the end of the string to test is reached - acceptance!");
}
return true;
}
else {
if (mDebugging) {
out.println("parsing - escaping process...");
}
}
}
else {
if (mDebugging) {
out.println("parsing - runnerState.mNextState != null");
}
// Special Case:
// If this state represents '*' - go to the rightmost state representing '?'.
// This state will act as an '*' - except that you only can go to the next state or accept the string, if and only if the number of '?' read are equal or less than the number of character read from the parsing string.
if (isFreeRangeCharacter(runnerState.mChar)) {
numberOfFreePassCharactersRead_SinceLastFreePassState = 0;
numberOfParsedCharactersRead_SinceLastFreePassState = 0;
WildcardStringParserState freeRangeRunnerState = runnerState.mNextState;
while ((freeRangeRunnerState != null) && (isFreePassCharacter(freeRangeRunnerState.mChar))) {
runnerState = freeRangeRunnerState;
hasPerformedFreeRangeMovement = true;
numberOfFreePassCharactersRead_SinceLastFreePassState++;
freeRangeRunnerState = freeRangeRunnerState.mNextState;
}
// Special Case: if the mask is at the end
if (runnerState.mNextState == null) {
if (mDebugging) {
out.println();
}
if (mDebugging) {
out.println("parsing - index number " + i + ", active char: '"
+ parsableString.getActiveChar() + "' char string index: " + parsableString.mIndex
+ " number of chars since last free-range state: " + numberOfParsedCharactersRead_SinceLastFreePassState);
}
if (mDebugging) {
out.println("parsing - state: " + runnerState.mAutomatonStateNumber + " '"
+ runnerState.mChar + "' - no of free-pass chars read: " + numberOfFreePassCharactersRead_SinceLastFreePassState);
}
if (mDebugging) {
out.println("parsing - hasPerformedFreeRangeMovement: "
+ hasPerformedFreeRangeMovement);
}
if ((hasPerformedFreeRangeMovement)
&& (numberOfFreePassCharactersRead_SinceLastFreePassState >= numberOfParsedCharactersRead_SinceLastFreePassState)) {
return true;
}
else {
return false;
}
}
}
// If the next state represents '*' - go to this next state
if (isFreeRangeCharacter(runnerState.mNextState.mChar)) {
runnerState = runnerState.mNextState;
parsableString.mIndex++;
numberOfParsedCharactersRead_SinceLastFreePassState++;
}
// If the next state represents '?' - go to this next state
else if (isFreePassCharacter(runnerState.mNextState.mChar)) {
runnerState = runnerState.mNextState;
parsableString.mIndex++;
numberOfFreePassCharactersRead_SinceLastFreePassState++;
numberOfParsedCharactersRead_SinceLastFreePassState++;
}
// If the next state represents the same character as the next character in the string to test - go to this next state
else if ((!parsableString.reachedEndOfString()) && (runnerState.mNextState.mChar == parsableString.getSubsequentChar())) {
runnerState = runnerState.mNextState;
parsableString.mIndex++;
numberOfParsedCharactersRead_SinceLastFreePassState++;
}
// If the next character in the string to test does not coincide with the next state - go to the last state representing '*'. If there are none - rejection!
else if (runnerState.mLastFreeRangeState != null) {
runnerState = runnerState.mLastFreeRangeState;
parsableString.mIndex++;
numberOfParsedCharactersRead_SinceLastFreePassState++;
}
else {
if (mDebugging) {
out.println("the next state does not represent the same character as the next character in the string to test, and there are no last-free-range-state - rejection!");
}
return false;
}
}
}
if (mDebugging) {
out.println("finished reading parsing string and not at any final state - rejection!");
}
return false;
}
/*
* Overriding mandatory methods from EntityObject's.
*/
/**
* Method toString
*
* @return
*/
public String toString() {
StringBuilder buffer = new StringBuilder();
if (!mInitialized) {
buffer.append(getClass().getName());
buffer.append(": Not initialized properly!");
buffer.append("\n");
buffer.append("\n");
}
else {
WildcardStringParserState runnerState = mInitialState;
buffer.append(getClass().getName());
buffer.append(": String mask ");
buffer.append(mStringMask);
buffer.append("\n");
buffer.append("\n");
buffer.append(" Automaton: ");
while (runnerState != null) {
buffer.append(runnerState.mAutomatonStateNumber);
buffer.append(": ");
buffer.append(runnerState.mChar);
buffer.append(" (");
if (runnerState.mLastFreeRangeState != null) {
buffer.append(runnerState.mLastFreeRangeState.mAutomatonStateNumber);
}
else {
buffer.append("-");
}
buffer.append(")");
if (runnerState.mNextState != null) {
buffer.append(" --> ");
}
runnerState = runnerState.mNextState;
}
buffer.append("\n");
buffer.append(" Format: <state index>: <character> (<last free state>)");
buffer.append("\n");
buffer.append(" Number of strings parsed: " + mTotalNumberOfStringsParsed);
buffer.append("\n");
}
return buffer.toString();
}
/**
* Method equals
*
* @param pObject
* @return
*/
public boolean equals(Object pObject) {
if (pObject instanceof WildcardStringParser) {
WildcardStringParser externalParser = (WildcardStringParser) pObject;
return ((externalParser.mInitialized == this.mInitialized) && (externalParser.mStringMask == this.mStringMask));
}
return super.equals(pObject);
}
// Just taking the lazy, easy and dangerous way out
/**
* Method hashCode
*
* @return
*/
public int hashCode() {
return super.hashCode();
}
protected Object clone() throws CloneNotSupportedException {
if (mInitialized) {
return new WildcardStringParser(mStringMask);
}
return null;
}
// Just taking the lazy, easy and dangerous way out
protected void finalize() throws Throwable {
}
/** A simple holder class for an automaton state. */
class WildcardStringParserState {
// Constants
// Members
int mAutomatonStateNumber;
char mChar;
WildcardStringParserState mPreviousState;
WildcardStringParserState mNextState;
WildcardStringParserState mLastFreeRangeState;
// Constructors
/**
* Constructor WildcardStringParserState
*
* @param pChar
*/
public WildcardStringParserState(final char pChar) {
this.mChar = pChar;
}
// Methods
// Debug
}
/** A simple holder class for a string to be parsed. */
class ParsableString {
// Constants
// Members
char[] mCharArray;
int mIndex;
// Constructors
ParsableString(final String pStringToParse) {
if (pStringToParse != null) {
mCharArray = pStringToParse.toCharArray();
}
mIndex = -1;
}
// Methods
boolean reachedEndOfString() {
//System.out.println(DebugUtil.DEBUG + DebugUtil.getClassName(this) + ": mIndex :" + mIndex);
//System.out.println(DebugUtil.DEBUG + DebugUtil.getClassName(this) + ": mCharArray.length :" + mCharArray.length);
return mIndex == mCharArray.length - 1;
}
int length() {
return mCharArray.length;
}
char getActiveChar() {
if ((mIndex > -1) && (mIndex < mCharArray.length)) {
return mCharArray[mIndex];
}
System.err.println(getClass().getName() + ": trying to access character outside character array!");
return ' ';
}
char getSubsequentChar() {
if ((mIndex > -1) && (mIndex + 1 < mCharArray.length)) {
return mCharArray[mIndex + 1];
}
System.err.println(getClass().getName() + ": trying to access character outside character array!");
return ' ';
}
boolean checkString() {
if (!isEmpty()) {
// Check if the string only contains chars that are elements in the alphabet
for (int i = 0; i < mCharArray.length; i++) {
if (!WildcardStringParser.isInAlphabet(mCharArray[i])) {
return false;
}
}
}
return true;
}
boolean isEmpty() {
return ((mCharArray == null) || (mCharArray.length == 0));
}
/**
* Method toString
*
* @return
*/
public String toString() {
return new String(mCharArray);
}
}
}
/*--- Formatted in Sun Java Convention Style on ma, des 1, '03 ---*/
/*------ Formatted by Jindent 3.23 Basic 1.0 --- http://www.jindent.de ------*/

View File

@@ -0,0 +1,4 @@
/**
* Provides functionality for regular expressions.
*/
package com.twelvemonkeys.util.regex;

View File

@@ -0,0 +1,61 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util.service;
/**
* An optional interface that may be implemented by service provider objects.
* <p/>
* If this interface is implemented, the service provider objects will receive
* notification of registration and deregistration from the
* {@code ServiceRegistry}.
*
* @see ServiceRegistry
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/service/RegisterableService.java#1 $
*/
public interface RegisterableService {
/**
* Called right after this service provider object is added to
* the given category of the given {@code ServiceRegistry}.
*
* @param pRegistry the {@code ServiceRegistry} {@code this} was added to
* @param pCategory the category {@code this} was added to
*/
void onRegistration(ServiceRegistry pRegistry, Class pCategory);
/**
* Called right after this service provider object is removed
* from the given category of the given {@code ServiceRegistry}.
*
* @param pRegistry the {@code ServiceRegistry} {@code this} was added to
* @param pCategory the category {@code this} was added to
*/
void onDeregistration(ServiceRegistry pRegistry, Class pCategory);
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util.service;
/**
* Error thrown by the {@code ServiceRegistry} in case of a configuration
* error.
* <p/>
* @see ServiceRegistry
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/service/ServiceConfigurationError.java#1 $
*/
public class ServiceConfigurationError extends Error {
ServiceConfigurationError(Throwable pCause) {
super(pCause);
}
ServiceConfigurationError(String pMessage) {
super(pMessage);
}
ServiceConfigurationError(String pMessage, Throwable pCause) {
super(pMessage, pCause);
}
}

View File

@@ -0,0 +1,546 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util.service;
import com.twelvemonkeys.lang.Validate;
import com.twelvemonkeys.util.FilterIterator;
import java.io.IOException;
import java.net.URL;
import java.util.*;
/**
* A registry for service provider objects.
* <p/>
* Service providers are looked up from the classpath, under the path
* {@code META-INF/services/}&lt;full-class-name&gt;.
* <p/>
* For example:<br/>
* {@code META-INF/services/com.company.package.spi.MyService}.
* <p/>
* The file should contain a list of fully-qualified concrete class names,
* one per line.
* <p/>
* The <em>full-class-name</em> represents an interface or (typically) an
* abstract class, and is the same class used as the category for this registry.
* Note that only one instance of a concrete subclass may be registered with a
* specific category at a time.
* <p/>
* <small>Implementation detail: This class is a clean room implementation of
* a service registry and does not use the proprietary {@code sun.misc.Service}
* class that is referred to in the <em>JAR File specification</em>.
* This class should work on any Java platform.
* </small>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/service/ServiceRegistry.java#2 $
* @see RegisterableService
* @see <a href="http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html#Service%20Provider">JAR File Specification</a>
*/
public class ServiceRegistry {
// TODO: Security issues?
// TODO: Application contexts?
/**
* "META-INF/services/"
*/
public static final String SERVICES = "META-INF/services/";
// Class to CategoryRegistry mapping
private final Map<Class<?>, CategoryRegistry> mCategoryMap;
/**
* Creates a {@code ServiceRegistry} instance with a set of categories
* taken from the {@code pCategories} argument.
* <p/>
* The categories are constant during the lifetime of the registry, and may
* not be changed after initial creation.
*
* @param pCategories an {@code Iterator} containing
* {@code Class} objects that defines this registry's categories.
* @throws IllegalArgumentException if {@code pCategories} is {@code null}.
* @throws ClassCastException if {@code pCategories} contains anything
* but {@code Class} objects.
*/
public ServiceRegistry(final Iterator<? extends Class<?>> pCategories) {
Validate.notNull(pCategories, "categories");
Map<Class<?>, CategoryRegistry> map = new LinkedHashMap<Class<?>, CategoryRegistry>();
while (pCategories.hasNext()) {
putCategory(map, pCategories.next());
}
// NOTE: Categories are constant for the lifetime of a registry
mCategoryMap = Collections.unmodifiableMap(map);
}
private <T> void putCategory(Map<Class<?>, CategoryRegistry> pMap, Class<T> pCategory) {
CategoryRegistry<T> registry = new CategoryRegistry<T>(pCategory);
pMap.put(pCategory, registry);
}
/**
* Registers all provider implementations for this {@code ServiceRegistry}
* found in the application classpath.
*
* @throws ServiceConfigurationError if an error occured during registration
*/
public void registerApplicationClasspathSPIs() {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Iterator<Class<?>> categories = categories();
while (categories.hasNext()) {
Class<?> category = categories.next();
try {
// Find all META-INF/services/ + name on class path
String name = SERVICES + category.getName();
Enumeration<URL> spiResources = loader.getResources(name);
while (spiResources.hasMoreElements()) {
URL resource = spiResources.nextElement();
registerSPIs(resource, category, loader);
}
}
catch (IOException e) {
throw new ServiceConfigurationError(e);
}
}
}
/**
* Registers all SPIs listed in the given resource.
*
* @param pResource the resource to load SPIs from
* @param pCategory the category class
* @param pLoader the classloader to use
*/
<T> void registerSPIs(final URL pResource, final Class<T> pCategory, final ClassLoader pLoader) {
Properties classNames = new Properties();
try {
classNames.load(pResource.openStream());
}
catch (IOException e) {
throw new ServiceConfigurationError(e);
}
if (!classNames.isEmpty()) {
@SuppressWarnings({"unchecked"})
CategoryRegistry<T> registry = mCategoryMap.get(pCategory);
Set providerClassNames = classNames.keySet();
for (Object providerClassName : providerClassNames) {
String className = (String) providerClassName;
try {
@SuppressWarnings({"unchecked"})
Class<T> providerClass = (Class<T>) Class.forName(className, true, pLoader);
T provider = providerClass.newInstance();
registry.register(provider);
}
catch (ClassNotFoundException e) {
throw new ServiceConfigurationError(e);
}
catch (IllegalAccessException e) {
throw new ServiceConfigurationError(e);
}
catch (InstantiationException e) {
throw new ServiceConfigurationError(e);
}
catch (IllegalArgumentException e) {
throw new ServiceConfigurationError(e);
}
}
}
}
/**
* Returns an {@code Iterator} containing all providers in the given
* category.
* <p/>
* The iterator supports removal.
* <p/>
* <small>
* NOTE: Removing a provider from the iterator, deregisters the current
* provider (as returned by the last invocation of {@code next()}) from
* {@code pCategory}, it does <em>not</em> remove the provider
* from other categories in the registry.
* </small>
*
* @param pCategory the category class
* @return an {@code Iterator} containing all providers in the given
* category.
* @throws IllegalArgumentException if {@code pCategory} is not a valid
* category in this registry
*/
protected <T> Iterator<T> providers(Class<T> pCategory) {
return getRegistry(pCategory).providers();
}
/**
* Returns an {@code Iterator} containing all categories in this registry.
* <p/>
* The iterator does not support removal.
*
* @return an {@code Iterator} containing all categories in this registry.
*/
protected Iterator<Class<?>> categories() {
return mCategoryMap.keySet().iterator();
}
/**
* Returns an {@code Iterator} containing all categories in this registry
* the given {@code pProvider} <em>may be registered with</em>.
* <p/>
* The iterator does not support removal.
*
* @param pProvider the provider instance
* @return an {@code Iterator} containing all categories in this registry
* the given {@code pProvider} may be registered with
*/
protected Iterator<Class<?>> compatibleCategories(final Object pProvider) {
return new FilterIterator<Class<?>>(categories(),
new FilterIterator.Filter<Class<?>>() {
public boolean accept(Class<?> pElement) {
return pElement.isInstance(pProvider);
}
});
}
/**
* Returns an {@code Iterator} containing all categories in this registry
* the given {@code pProvider} <em>is currently registered with</em>.
* <p/>
* The iterator supports removal.
* <p/>
* <small>
* NOTE: Removing a category from the iterator, deregisters
* {@code pProvider} from the current category (as returned by the last
* invocation of {@code next()}), it does <em>not</em> remove the category
* itself from the registry.
* </small>
*
* @param pProvider the provider instance
* @return an {@code Iterator} containing all categories in this registry
* the given {@code pProvider} may be registered with
*/
protected Iterator<Class<?>> containingCategories(final Object pProvider) {
// TODO: Is removal using the iterator really a good idea?
return new FilterIterator<Class<?>>(categories(),
new FilterIterator.Filter<Class<?>>() {
public boolean accept(Class<?> pElement) {
return getRegistry(pElement).contatins(pProvider);
}
}) {
Class<?> mCurrent;
public Class next() {
return (mCurrent = super.next());
}
public void remove() {
if (mCurrent == null) {
throw new IllegalStateException("No current element");
}
getRegistry(mCurrent).deregister(pProvider);
mCurrent = null;
}
};
}
/**
* Gets the category registry for the given category.
*
* @param pCategory the category class
* @return the {@code CategoryRegistry} for the given category
*/
private <T> CategoryRegistry<T> getRegistry(final Class<T> pCategory) {
@SuppressWarnings({"unchecked"})
CategoryRegistry<T> registry = mCategoryMap.get(pCategory);
if (registry == null) {
throw new IllegalArgumentException("No such category: " + pCategory.getName());
}
return registry;
}
/**
* Registers the given provider for all categories it matches.
*
* @param pProvider the provider instance
* @return {@code true} if {@code pProvider} is now registered in
* one or more categories
* @see #compatibleCategories(Object)
*/
public boolean register(final Object pProvider) {
Iterator<Class<?>> categories = compatibleCategories(pProvider);
boolean registered = false;
while (categories.hasNext()) {
Class<?> category = categories.next();
if (registerImpl(pProvider, category) && !registered) {
registered = true;
}
}
return registered;
}
private <T> boolean registerImpl(final Object pProvider, final Class<T> pCategory) {
return getRegistry(pCategory).register(pCategory.cast(pProvider));
}
/**
* Registers the given provider for the given category.
*
* @param pProvider the provider instance
* @param pCategory the category class
* @return {@code true} if {@code pProvider} is now registered in
* the given category
*/
public <T> boolean register(final T pProvider, final Class<? super T> pCategory) {
return registerImpl(pProvider, pCategory);
}
/**
* Deregisters the given provider from all categories it's currently
* registered in.
*
* @param pProvider the provider instance
* @return {@code true} if {@code pProvider} was previously registered in
* any category
* @see #containingCategories(Object)
*/
public boolean deregister(final Object pProvider) {
Iterator<Class<?>> categories = containingCategories(pProvider);
boolean deregistered = false;
while (categories.hasNext()) {
Class<?> category = categories.next();
if (deregister(pProvider, category) && !deregistered) {
deregistered = true;
}
}
return deregistered;
}
/**
* Deregisters the given provider from the given category.
*
* @param pProvider the provider instance
* @param pCategory the category class
* @return {@code true} if {@code pProvider} was previously registered in
* the given category
*/
public boolean deregister(final Object pProvider, final Class<?> pCategory) {
return getRegistry(pCategory).deregister(pProvider);
}
/**
* Keeps track of each individual category.
*/
class CategoryRegistry<T> {
private final Class<T> mCategory;
private final Map<Class, T> mProviders = new LinkedHashMap<Class, T>();
CategoryRegistry(Class<T> pCategory) {
Validate.notNull(pCategory, "category");
mCategory = pCategory;
}
private void checkCategory(final Object pProvider) {
if (!mCategory.isInstance(pProvider)) {
throw new IllegalArgumentException(pProvider + " not instance of category " + mCategory.getName());
}
}
public boolean register(final T pProvider) {
checkCategory(pProvider);
// NOTE: We only register the new instance, if we don't allready
// have an instance of pProvider's class.
if (!contatins(pProvider)) {
mProviders.put(pProvider.getClass(), pProvider);
processRegistration(pProvider);
return true;
}
return false;
}
void processRegistration(final T pProvider) {
if (pProvider instanceof RegisterableService) {
RegisterableService service = (RegisterableService) pProvider;
service.onRegistration(ServiceRegistry.this, mCategory);
}
}
public boolean deregister(final Object pProvider) {
checkCategory(pProvider);
// NOTE: We remove any provider of the same class, this may or may
// not be the same instance as pProvider.
T oldProvider = mProviders.remove(pProvider.getClass());
if (oldProvider != null) {
processDeregistration(oldProvider);
return true;
}
return false;
}
void processDeregistration(final T pOldProvider) {
if (pOldProvider instanceof RegisterableService) {
RegisterableService service = (RegisterableService) pOldProvider;
service.onDeregistration(ServiceRegistry.this, mCategory);
}
}
public boolean contatins(final Object pProvider) {
return mProviders.containsKey(pProvider.getClass());
}
public Iterator<T> providers() {
// NOTE: The iterator must support removal because deregistering
// using the deregister method will result in
// ConcurrentModificationException in the iterator..
// We wrap the iterator to track deregistration right.
final Iterator<T> iterator = mProviders.values().iterator();
return new Iterator<T>() {
T mCurrent;
public boolean hasNext() {
return iterator.hasNext();
}
public T next() {
return (mCurrent = iterator.next());
}
public void remove() {
iterator.remove();
processDeregistration(mCurrent);
}
};
}
}
@SuppressWarnings({"UnnecessaryFullyQualifiedName"})
public static void main(String[] pArgs) {
abstract class Spi {}
class One extends Spi {}
class Two extends Spi {}
ServiceRegistry testRegistry = new ServiceRegistry(
Arrays.<Class<?>>asList(
java.nio.charset.spi.CharsetProvider.class,
java.nio.channels.spi.SelectorProvider.class,
javax.imageio.spi.ImageReaderSpi.class,
javax.imageio.spi.ImageWriterSpi.class,
Spi.class
).iterator()
);
testRegistry.registerApplicationClasspathSPIs();
One one = new One();
Two two = new Two();
testRegistry.register(one, Spi.class);
testRegistry.register(two, Spi.class);
testRegistry.deregister(one);
testRegistry.deregister(one, Spi.class);
testRegistry.deregister(two, Spi.class);
testRegistry.deregister(two);
Iterator<Class<?>> categories = testRegistry.categories();
System.out.println("Categories: ");
while (categories.hasNext()) {
Class<?> category = categories.next();
System.out.println(" " + category.getName() + ":");
Iterator<?> providers = testRegistry.providers(category);
Object provider = null;
while (providers.hasNext()) {
provider = providers.next();
System.out.println(" " + provider);
if (provider instanceof javax.imageio.spi.ImageReaderWriterSpi) {
System.out.println(" - " + ((javax.imageio.spi.ImageReaderWriterSpi) provider).getDescription(null));
}
// javax.imageio.spi.ImageReaderWriterSpi provider = (javax.imageio.spi.ImageReaderWriterSpi) providers.next();
// System.out.println(" " + provider);
// System.out.println(" " + provider.getVendorName());
// System.out.println(" Formats:");
//
// System.out.print(" ");
// String[] formatNames = provider.getFormatNames();
// for (int i = 0; i < formatNames.length; i++) {
// if (i != 0) {
// System.out.print(", ");
// }
// System.out.print(formatNames[i]);
// }
// System.out.println();
// Don't remove last one, it's removed later to exercise more code :-)
if (providers.hasNext()) {
providers.remove();
}
}
// Remove the last item from all categories
if (provider != null) {
Iterator containers = testRegistry.containingCategories(provider);
int count = 0;
while (containers.hasNext()) {
if (category == containers.next()) {
containers.remove();
count++;
}
}
if (count != 1) {
System.err.println("Removed " + provider + " from " + count + " categories");
}
}
// Remove all using providers iterator
providers = testRegistry.providers(category);
if (!providers.hasNext()) {
System.out.println("All providers successfully deregistered");
}
while (providers.hasNext()) {
System.err.println("Not removed: " + providers.next());
}
}
}
//*/
}

View File

@@ -0,0 +1,9 @@
/**
* Provides a service provider registry.
* <p/>
* This package contains a service provider registry, as specified in the
* <a href="http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html#Service%20Provider">JAR File Specification</a>.
*
* @see <a href="http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html#Service%20Provider">JAR File Specification</a>
*/
package com.twelvemonkeys.util.service;

View File

@@ -0,0 +1,228 @@
package com.twelvemonkeys.lang;
import junit.framework.TestCase;
import java.util.Map;
import java.util.HashMap;
import java.lang.reflect.InvocationTargetException;
import java.text.NumberFormat;
/**
* BeanUtilTestCase
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/lang/BeanUtilTestCase.java#1 $
*/
public class BeanUtilTestCase extends TestCase {
public void testConfigureNoMehtod() {
TestBean bean = new TestBean();
Map map = new HashMap();
map.put("noSuchMethod", "jaffa");
try {
BeanUtil.configure(bean, map);
}
catch (InvocationTargetException e) {
fail(e.getMessage());
}
}
public void testConfigureNoMethodArgs() {
TestBean bean = new TestBean();
Map map = new HashMap();
map.put("doubleValue", new Object()); // Should not be able to convert this
try {
BeanUtil.configure(bean, map);
}
catch (InvocationTargetException e) {
fail(e.getMessage());
}
assertNull(bean.getDoubleValue());
}
public void testConfigureNullValue() {
TestBean bean = new TestBean();
Map map = new HashMap();
map.put("stringValue", null);
try {
BeanUtil.configure(bean, map);
}
catch (InvocationTargetException e) {
fail(e.getMessage());
}
assertNull(bean.getStringValue());
}
public void testConfigureSimple() {
TestBean bean = new TestBean();
Map map = new HashMap();
map.put("stringValue", "one");
map.put("intValue", new Integer(2));
map.put("doubleValue", new Double(.3));
try {
BeanUtil.configure(bean, map);
}
catch (InvocationTargetException e) {
fail(e.getMessage());
}
assertEquals("one", bean.getStringValue());
assertEquals(2, bean.getIntValue());
assertEquals(new Double(.3), bean.getDoubleValue());
}
public void testConfigureConvert() {
TestBean bean = new TestBean();
Map map = new HashMap();
map.put("stringValue", new Integer(1));
map.put("intValue", "2");
map.put("doubleValue", NumberFormat.getNumberInstance().format(0.3)); // Note, format is locale specific...
try {
BeanUtil.configure(bean, map);
}
catch (InvocationTargetException e) {
fail(e.getMessage());
}
assertEquals("1", bean.getStringValue());
assertEquals(2, bean.getIntValue());
assertEquals(new Double(.3), bean.getDoubleValue());
}
public void testConfigureAmbigious1() {
TestBean bean = new TestBean();
Map map = new HashMap();
String value = "one";
map.put("ambigious", value);
try {
BeanUtil.configure(bean, map);
}
catch (InvocationTargetException e) {
fail(e.getMessage());
}
assertNotNull(bean.getAmbigious());
assertEquals("String converted rather than invoking setAmbigiouos(String), ordering not predictable",
"one", bean.getAmbigious());
assertSame("String converted rather than invoking setAmbigiouos(String), ordering not predictable",
value, bean.getAmbigious());
}
public void testConfigureAmbigious2() {
TestBean bean = new TestBean();
Map map = new HashMap();
Integer value = new Integer(2);
map.put("ambigious", value);
try {
BeanUtil.configure(bean, map);
}
catch (InvocationTargetException e) {
fail(e.getMessage());
}
assertNotNull(bean.getAmbigious());
assertEquals("Integer converted rather than invoking setAmbigiouos(Integer), ordering not predictable",
new Integer(2), bean.getAmbigious());
assertSame("Integer converted rather than invoking setAmbigiouos(Integer), ordering not predictable",
value, bean.getAmbigious());
}
public void testConfigureAmbigious3() {
TestBean bean = new TestBean();
Map map = new HashMap();
Double value = new Double(.3);
map.put("ambigious", value);
try {
BeanUtil.configure(bean, map);
}
catch (InvocationTargetException e) {
fail(e.getMessage());
}
assertNotNull(bean.getAmbigious());
assertEquals("Object converted rather than invoking setAmbigious(Object), ordering not predictable",
value.getClass(), bean.getAmbigious().getClass());
assertSame("Object converted rather than invoking setAmbigious(Object), ordering not predictable",
value, bean.getAmbigious());
}
static class TestBean {
private String mString;
private int mInt;
private Double mDouble;
private Object mAmbigious;
public Double getDoubleValue() {
return mDouble;
}
public int getIntValue() {
return mInt;
}
public String getStringValue() {
return mString;
}
public void setStringValue(String pString) {
mString = pString;
}
public void setIntValue(int pInt) {
mInt = pInt;
}
public void setDoubleValue(Double pDouble) {
mDouble = pDouble;
}
public void setAmbigious(String pString) {
mAmbigious = pString;
}
public void setAmbigious(Object pObject) {
mAmbigious = pObject;
}
public void setAmbigious(Integer pInteger) {
mAmbigious = pInteger;
}
public void setAmbigious(int pInt) {
mAmbigious = new Long(pInt); // Just to differentiate...
}
public Object getAmbigious() {
return mAmbigious;
}
}
}

View File

@@ -0,0 +1,321 @@
package com.twelvemonkeys.lang;
import junit.framework.TestCase;
import java.lang.reflect.Method;
import java.io.*;
/**
* AbstractObjectTestCase
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/lang/ObjectAbstractTestCase.java#1 $
*/
public abstract class ObjectAbstractTestCase extends TestCase {
// TODO: See com.tm.util.ObjectAbstractTestCase
// TODO: The idea is that this should be some generic base-class that
// implements the basic object tests
// TODO: Create Serializable test similar way
// TODO: Create Comparable test similar way
/**
* Creates a {@code TestCase}.
*
* @param testName the test class name
*/
protected ObjectAbstractTestCase(String testName) {
super(testName);
}
protected ObjectAbstractTestCase() {
super();
}
/**
* Returns an instance of the class we are testing.
* Implement this method to return the object to test.
*
* @return the object to test
*/
protected abstract Object makeObject();
// TODO: Can we really do serious testing with just one object?
// TODO: How can we make sure we create equal or different objects?!
//protected abstract Object makeDifferentObject(Object pObject);
//protected abstract Object makeEqualObject(Object pObject);
public void testToString() {
assertNotNull(makeObject().toString());
// TODO: What more can we test?
}
// TODO: assert that either BOTH or NONE of equals/hashcode is overridden
public void testEqualsHashCode(){
Object obj = makeObject();
Class cl = obj.getClass();
if (isEqualsOverriden(cl)) {
assertTrue("Class " + cl.getName()
+ " implements equals but not hashCode", isHashCodeOverriden(cl));
}
else if (isHashCodeOverriden(cl)) {
assertTrue("Class " + cl.getName()
+ " implements hashCode but not equals", isEqualsOverriden(cl));
}
}
protected static boolean isEqualsOverriden(Class pClass) {
return getDeclaredMethod(pClass, "equals", new Class[]{Object.class}) != null;
}
protected static boolean isHashCodeOverriden(Class pClass) {
return getDeclaredMethod(pClass, "hashCode", null) != null;
}
private static Method getDeclaredMethod(Class pClass, String pName, Class[] pArameters) {
try {
return pClass.getDeclaredMethod(pName, pArameters);
}
catch (NoSuchMethodException ignore) {
return null;
}
}
public void testObjectEqualsSelf() {
Object obj = makeObject();
assertEquals("An Object should equal itself", obj, obj);
}
public void testEqualsNull() {
Object obj = makeObject();
// NOTE: Makes sure this doesn't throw NPE either
//noinspection ObjectEqualsNull
assertFalse("An object should never equal null", obj.equals(null));
}
public void testObjectHashCodeEqualsSelfHashCode() {
Object obj = makeObject();
assertEquals("hashCode should be repeatable", obj.hashCode(), obj.hashCode());
}
public void testObjectHashCodeEqualsContract() {
Object obj1 = makeObject();
if (obj1.equals(obj1)) {
assertEquals(
"[1] When two objects are equal, their hashCodes should be also.",
obj1.hashCode(), obj1.hashCode());
}
// TODO: Make sure we create at least one equal object, and one different object
Object obj2 = makeObject();
if (obj1.equals(obj2)) {
assertEquals(
"[2] When two objects are equal, their hashCodes should be also.",
obj1.hashCode(), obj2.hashCode());
assertTrue(
"When obj1.equals(obj2) is true, then obj2.equals(obj1) should also be true",
obj2.equals(obj1));
}
}
/*
public void testFinalize() {
// TODO: Implement
}
*/
////////////////////////////////////////////////////////////////////////////
// Cloneable interface
public void testClone() throws Exception {
Object obj = makeObject();
if (obj instanceof Cloneable) {
Class cl = obj.getClass();
Method clone = findMethod(cl, "clone");
// Disregard protected modifier
// NOTE: This will throw a SecurityException if a SecurityManager
// disallows access, but should not happen in a test context
if (!clone.isAccessible()) {
clone.setAccessible(true);
}
Object cloned = clone.invoke(obj, null);
assertNotNull("Cloned object should never be null", cloned);
// TODO: This can only be asserted if equals() test is based on
// value equality, not reference (identity) equality
// Maybe it's possible to do a reflective introspection of
// the objects fields?
if (isHashCodeOverriden(cl)) {
assertEquals("Cloned object not equal", obj, cloned);
}
}
}
private static Method findMethod(Class pClass, String pName) throws NoSuchMethodException {
if (pClass == null) {
throw new IllegalArgumentException("class == null");
}
if (pName == null) {
throw new IllegalArgumentException("name == null");
}
Class cl = pClass;
while (cl != null) {
try {
return cl.getDeclaredMethod(pName, new Class[0]);
}
catch (NoSuchMethodException e) {
}
catch (SecurityException e) {
}
cl = cl.getSuperclass();
}
throw new NoSuchMethodException(pName + " in class " + pClass.getName());
}
///////////////////////////////////////////////////////////////////////////
// Serializable interface
public void testSerializeDeserializeThenCompare() throws Exception {
Object obj = makeObject();
if (obj instanceof Serializable) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(buffer);
try {
out.writeObject(obj);
}
finally {
out.close();
}
Object dest;
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray()));
try {
dest = in.readObject();
}
finally {
in.close();
}
// TODO: This can only be asserted if equals() test is based on
// value equality, not reference (identity) equality
// Maybe it's possible to do a reflective introspection of
// the objects fields?
if (isEqualsOverriden(obj.getClass())) {
assertEquals("obj != deserialize(serialize(obj))", obj, dest);
}
}
}
/**
* Sanity check method, makes sure that any {@code Serializable}
* class can be serialized and de-serialized in memory,
* using the handy makeObject() method
*
* @throws java.io.IOException
* @throws ClassNotFoundException
*/
public void testSimpleSerialization() throws Exception {
Object o = makeObject();
if (o instanceof Serializable) {
byte[] object = writeExternalFormToBytes((Serializable) o);
readExternalFormFromBytes(object);
}
}
/**
* Write a Serializable or Externalizable object as
* a file at the given path.
* <em>NOT USEFUL as part
* of a unit test; this is just a utility method
* for creating disk-based objects in CVS that can become
* the basis for compatibility tests using
* readExternalFormFromDisk(String path)</em>
*
* @param o Object to serialize
* @param path path to write the serialized Object
* @exception java.io.IOException
*/
protected void writeExternalFormToDisk(Serializable o, String path) throws IOException {
FileOutputStream fileStream = new FileOutputStream(path);
writeExternalFormToStream(o, fileStream);
}
/**
* Converts a Serializable or Externalizable object to
* bytes. Useful for in-memory tests of serialization
*
* @param o Object to convert to bytes
* @return serialized form of the Object
* @exception java.io.IOException
*/
protected byte[] writeExternalFormToBytes(Serializable o) throws IOException {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
writeExternalFormToStream(o, byteStream);
return byteStream.toByteArray();
}
/**
* Reads a Serialized or Externalized Object from disk.
* Useful for creating compatibility tests between
* different CVS versions of the same class
*
* @param path path to the serialized Object
* @return the Object at the given path
* @exception java.io.IOException
* @exception ClassNotFoundException
*/
protected Object readExternalFormFromDisk(String path) throws IOException, ClassNotFoundException {
FileInputStream stream = new FileInputStream(path);
return readExternalFormFromStream(stream);
}
/**
* Read a Serialized or Externalized Object from bytes.
* Useful for verifying serialization in memory.
*
* @param b byte array containing a serialized Object
* @return Object contained in the bytes
* @exception java.io.IOException
* @exception ClassNotFoundException
*/
protected Object readExternalFormFromBytes(byte[] b) throws IOException, ClassNotFoundException {
ByteArrayInputStream stream = new ByteArrayInputStream(b);
return readExternalFormFromStream(stream);
}
// private implementation
//-----------------------------------------------------------------------
private Object readExternalFormFromStream(InputStream stream) throws IOException, ClassNotFoundException {
ObjectInputStream oStream = new ObjectInputStream(stream);
return oStream.readObject();
}
private void writeExternalFormToStream(Serializable o, OutputStream stream) throws IOException {
ObjectOutputStream oStream = new ObjectOutputStream(stream);
oStream.writeObject(o);
}
public static final class SanityTestTestCase extends ObjectAbstractTestCase {
/**
* Creates a {@code TestCase}.
*
*/
public SanityTestTestCase() {
super(SanityTestTestCase.class.getName());
}
protected Object makeObject() {
return new Cloneable() {};
}
}
}

View File

@@ -0,0 +1,858 @@
package com.twelvemonkeys.lang;
import junit.framework.TestCase;
import java.awt.*;
import java.util.Date;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.sql.Timestamp;
/**
* StringUtilTestCase
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/lang/StringUtilTestCase.java#1 $
*
*/
public class StringUtilTestCase extends TestCase {
final static Object TEST_OBJECT = new Object();
final static Integer TEST_INTEGER = 42;
final static String TEST_STRING = "TheQuickBrownFox"; // No WS!
final static String TEST_SUB_STRING = TEST_STRING.substring(2, 5);
final static String TEST_DELIM_STRING = "one,two, three\n four\tfive six";
final static String[] STRING_ARRAY = {"one", "two", "three", "four", "five", "six"};
final static String TEST_INT_DELIM_STRING = "1,2, 3\n 4\t5 6";
final static int[] INT_ARRAY = {1, 2, 3, 4, 5, 6};
final static String TEST_DOUBLE_DELIM_STRING = "1.4,2.1, 3\n .4\t-5 6e5";
final static double[] DOUBLE_ARRAY = {1.4, 2.1, 3, .4, -5, 6e5};
final static String EMPTY_STRING = "";
final static String WHITESPACE_STRING = " \t \r \n ";
public void testValueOfObject() {
assertNotNull(StringUtil.valueOf(TEST_OBJECT));
assertEquals(StringUtil.valueOf(TEST_OBJECT), TEST_OBJECT.toString());
assertEquals(StringUtil.valueOf(TEST_INTEGER), TEST_INTEGER.toString());
assertEquals(StringUtil.valueOf(TEST_STRING), TEST_STRING);
assertSame(StringUtil.valueOf(TEST_STRING), TEST_STRING);
assertNull(StringUtil.valueOf(null));
}
public void testToUpperCase() {
String str = StringUtil.toUpperCase(TEST_STRING);
assertNotNull(str);
assertEquals(TEST_STRING.toUpperCase(), str);
str = StringUtil.toUpperCase(null);
assertNull(str);
}
public void testToLowerCase() {
String str = StringUtil.toLowerCase(TEST_STRING);
assertNotNull(str);
assertEquals(TEST_STRING.toLowerCase(), str);
str = StringUtil.toLowerCase(null);
assertNull(str);
}
public void testIsEmpty() {
assertTrue(StringUtil.isEmpty((String) null));
assertTrue(StringUtil.isEmpty(EMPTY_STRING));
assertTrue(StringUtil.isEmpty(WHITESPACE_STRING));
assertFalse(StringUtil.isEmpty(TEST_STRING));
}
public void testIsEmptyArray() {
assertTrue(StringUtil.isEmpty((String[]) null));
assertTrue(StringUtil.isEmpty(new String[]{EMPTY_STRING}));
assertTrue(StringUtil.isEmpty(new String[]{EMPTY_STRING, WHITESPACE_STRING}));
assertFalse(StringUtil.isEmpty(new String[]{EMPTY_STRING, TEST_STRING}));
assertFalse(StringUtil.isEmpty(new String[]{WHITESPACE_STRING, TEST_STRING}));
}
public void testContains() {
assertTrue(StringUtil.contains(TEST_STRING, TEST_STRING));
assertTrue(StringUtil.contains(TEST_STRING, TEST_SUB_STRING));
assertTrue(StringUtil.contains(TEST_STRING, EMPTY_STRING));
assertFalse(StringUtil.contains(TEST_STRING, WHITESPACE_STRING));
assertFalse(StringUtil.contains(TEST_SUB_STRING, TEST_STRING));
assertFalse(StringUtil.contains(EMPTY_STRING, TEST_STRING));
assertFalse(StringUtil.contains(WHITESPACE_STRING, TEST_STRING));
assertFalse(StringUtil.contains(null, TEST_STRING));
assertFalse(StringUtil.contains(null, null));
}
public void testContainsIgnoreCase() {
assertTrue(StringUtil.containsIgnoreCase(TEST_STRING, TEST_STRING));
assertTrue(StringUtil.containsIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING));
assertTrue(StringUtil.containsIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING));
assertTrue(StringUtil.containsIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase()));
assertTrue(StringUtil.containsIgnoreCase(TEST_STRING, TEST_SUB_STRING));
assertTrue(StringUtil.containsIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING));
assertTrue(StringUtil.containsIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING));
assertTrue(StringUtil.containsIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase()));
assertTrue(StringUtil.containsIgnoreCase(TEST_STRING, EMPTY_STRING));
assertFalse(StringUtil.containsIgnoreCase(TEST_STRING, WHITESPACE_STRING));
assertFalse(StringUtil.containsIgnoreCase(TEST_SUB_STRING, TEST_STRING));
assertFalse(StringUtil.containsIgnoreCase(EMPTY_STRING, TEST_STRING));
assertFalse(StringUtil.containsIgnoreCase(WHITESPACE_STRING, TEST_STRING));
assertFalse(StringUtil.containsIgnoreCase(null, TEST_STRING));
assertFalse(StringUtil.containsIgnoreCase(null, null));
}
public void testContainsChar() {
for (int i = 0; i < TEST_STRING.length(); i++) {
assertTrue(StringUtil.contains(TEST_STRING, TEST_STRING.charAt(i)));
assertFalse(StringUtil.contains(EMPTY_STRING, TEST_STRING.charAt(i)));
assertFalse(StringUtil.contains(WHITESPACE_STRING, TEST_STRING.charAt(i)));
assertFalse(StringUtil.contains(null, TEST_STRING.charAt(i)));
}
for (int i = 0; i < TEST_SUB_STRING.length(); i++) {
assertTrue(StringUtil.contains(TEST_STRING, TEST_SUB_STRING.charAt(i)));
}
for (int i = 0; i < WHITESPACE_STRING.length(); i++) {
assertFalse(StringUtil.contains(TEST_STRING, WHITESPACE_STRING.charAt(i)));
}
// Test all alpha-chars
for (int i = 'a'; i < 'z'; i++) {
if (TEST_STRING.indexOf(i) < 0) {
assertFalse(TEST_STRING + " seems to contain '" + (char) i + "', at index " + TEST_STRING.indexOf(i), StringUtil.contains(TEST_STRING, i));
}
else {
assertTrue(TEST_STRING + " seems to not contain '" + (char) i + "', at index " + TEST_STRING.indexOf(i), StringUtil.contains(TEST_STRING, i));
}
}
}
public void testContainsIgnoreCaseChar() {
// Must contain all chars in string
for (int i = 0; i < TEST_STRING.length(); i++) {
assertTrue(StringUtil.containsIgnoreCase(TEST_STRING, TEST_STRING.charAt(i)));
assertTrue(StringUtil.containsIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING.charAt(i)));
assertTrue(StringUtil.containsIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING.charAt(i)));
assertTrue(StringUtil.containsIgnoreCase(TEST_STRING, Character.toUpperCase(TEST_STRING.charAt(i))));
assertFalse(StringUtil.containsIgnoreCase(EMPTY_STRING, TEST_STRING.charAt(i)));
assertFalse(StringUtil.containsIgnoreCase(WHITESPACE_STRING, TEST_STRING.charAt(i)));
assertFalse(StringUtil.containsIgnoreCase(null, TEST_STRING.charAt(i)));
}
for (int i = 0; i < TEST_SUB_STRING.length(); i++) {
assertTrue(StringUtil.containsIgnoreCase(TEST_STRING, TEST_SUB_STRING.charAt(i)));
assertTrue(StringUtil.containsIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING.charAt(i)));
assertTrue(StringUtil.containsIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING.charAt(i)));
assertTrue(StringUtil.containsIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase().charAt(i)));
}
for (int i = 0; i < WHITESPACE_STRING.length(); i++) {
assertFalse(StringUtil.containsIgnoreCase(TEST_STRING, WHITESPACE_STRING.charAt(i)));
}
// Test all alpha-chars
for (int i = 'a'; i < 'z'; i++) {
if ((TEST_STRING.indexOf(i) < 0) && (TEST_STRING.indexOf(Character.toUpperCase((char) i)) < 0)) {
assertFalse(TEST_STRING + " seems to contain '" + (char) i + "', at index " + Math.max(TEST_STRING.indexOf(i), TEST_STRING.indexOf(Character.toUpperCase((char) i))), StringUtil.containsIgnoreCase(TEST_STRING, i));
}
else {
assertTrue(TEST_STRING + " seems to not contain '" + (char) i + "', at index " + TEST_STRING.indexOf(i), StringUtil.containsIgnoreCase(TEST_STRING, i));
}
}
}
public void testIndexOfIgnoreCase() {
assertEquals(0, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING));
assertEquals(0, StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING));
assertEquals(0, StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING));
assertEquals(0, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase()));
assertEquals(0, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.toLowerCase()));
for (int i = 1; i < TEST_STRING.length(); i++) {
assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.substring(i)));
assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING.substring(i)));
assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING.substring(i)));
assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase().substring(i)));
assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.toLowerCase().substring(i)));
}
assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING));
assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING));
assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING));
assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase()));
assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toLowerCase()));
for (int i = 1; i < TEST_STRING.length(); i++) {
assertEquals(-1, StringUtil.indexOfIgnoreCase(TEST_STRING.substring(i), TEST_STRING));
assertEquals(-1, StringUtil.indexOfIgnoreCase(TEST_STRING.substring(i), TEST_STRING));
}
assertEquals(-1, StringUtil.indexOfIgnoreCase(null, TEST_STRING));
assertEquals(-1, StringUtil.indexOfIgnoreCase(null, null));
}
public void testIndexOfIgnoreCasePos() {
assertEquals(-1, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING, 1));
assertEquals(-1, StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING, 2));
assertEquals(-1, StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING, 3));
assertEquals(-1, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase(), 4));
assertEquals(-1, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.toLowerCase(), 5));
for (int i = 1; i < TEST_STRING.length(); i++) {
assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.substring(i), i - 1));
assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING.substring(i), i - 1));
assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING.substring(i), i - 1));
assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase().substring(i), i - 1));
assertEquals(i, StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.toLowerCase().substring(i), i - 1));
}
assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING, 1));
assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING, 1));
assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING, 2));
assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase(), 1));
assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toLowerCase(), 2));
assertEquals(-1, StringUtil.indexOfIgnoreCase(null, TEST_STRING, 234));
assertEquals(-1, StringUtil.indexOfIgnoreCase(null, null, -45));
}
public void testLastIndexOfIgnoreCase() {
assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING));
assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING));
assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING));
assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase()));
assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.toLowerCase()));
for (int i = 1; i < TEST_STRING.length(); i++) {
assertEquals(i, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.substring(i)));
assertEquals(i, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING.substring(i)));
assertEquals(i, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING.substring(i)));
assertEquals(i, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase().substring(i)));
assertEquals(i, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.toLowerCase().substring(i)));
}
assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING));
assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING));
assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING));
assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase()));
assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toLowerCase()));
for (int i = 1; i < TEST_STRING.length(); i++) {
assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.substring(i), TEST_STRING));
assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.substring(i), TEST_STRING));
}
assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(null, TEST_STRING));
assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(null, null));
}
public void testLastIndexOfIgnoreCasePos() {
assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING, 1));
assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING, 2));
assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING, 3));
assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase(), 4));
assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.toLowerCase(), 5));
for (int i = 1; i < TEST_STRING.length(); i++) {
assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.substring(0, i), i - 1));
assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING.substring(0, i), i - 1));
assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING.substring(0, i), i - 1));
assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase().substring(0, i), i - 1));
assertEquals(0, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.toLowerCase().substring(0, i), i - 1));
}
assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING, TEST_SUB_STRING.length() + 3));
assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING, TEST_SUB_STRING.length() + 3));
assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING, TEST_SUB_STRING.length() + 4));
assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase(), TEST_SUB_STRING.length() + 3));
assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toLowerCase(), TEST_SUB_STRING.length() + 4));
assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(null, TEST_STRING, 234));
assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(null, null, -45));
}
public void testIndexOfIgnoreCaseChar() {
// Must contain all chars in string
for (int i = 0; i < TEST_STRING.length(); i++) {
assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.charAt(i)));
assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING.charAt(i)));
assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING.charAt(i)));
assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, Character.toUpperCase(TEST_STRING.charAt(i))));
assertEquals(-1, StringUtil.indexOfIgnoreCase(EMPTY_STRING, TEST_STRING.charAt(i)));
assertEquals(-1, StringUtil.indexOfIgnoreCase(WHITESPACE_STRING, TEST_STRING.charAt(i)));
assertEquals(-1, StringUtil.indexOfIgnoreCase(null, TEST_STRING.charAt(i)));
}
for (int i = 0; i < TEST_SUB_STRING.length(); i++) {
assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.charAt(i)));
assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING.charAt(i)));
assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING.charAt(i)));
assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase().charAt(i)));
}
for (int i = 0; i < WHITESPACE_STRING.length(); i++) {
assertEquals(-1, StringUtil.indexOfIgnoreCase(TEST_STRING, WHITESPACE_STRING.charAt(i)));
}
// Test all alpha-chars
for (int i = 'a'; i < 'z'; i++) {
if ((TEST_STRING.indexOf(i) < 0) && (TEST_STRING.indexOf(Character.toUpperCase((char) i)) < 0)) {
assertEquals(TEST_STRING + " seems to contain '" + (char) i + "', at index " + Math.max(TEST_STRING.indexOf(i), TEST_STRING.indexOf(Character.toUpperCase((char) i))), -1, StringUtil.indexOfIgnoreCase(TEST_STRING, i));
}
else {
assertTrue(TEST_STRING + " seems to not contain '" + (char) i + "', at index " + TEST_STRING.indexOf(i), 0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, i));
}
}
}
public void testIndexOfIgnoreCaseCharPos() {
// Must contain all chars in string
for (int i = 0; i < TEST_STRING.length(); i++) {
assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_STRING.charAt(i), i));
assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING.charAt(i), i));
assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING.charAt(i), i));
assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, Character.toUpperCase(TEST_STRING.charAt(i)), i));
assertEquals(-1, StringUtil.indexOfIgnoreCase(EMPTY_STRING, TEST_STRING.charAt(i), i));
assertEquals(-1, StringUtil.indexOfIgnoreCase(WHITESPACE_STRING, TEST_STRING.charAt(i), i));
assertEquals(-1, StringUtil.indexOfIgnoreCase(null, TEST_STRING.charAt(i), i));
}
for (int i = 0; i < TEST_SUB_STRING.length(); i++) {
assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.charAt(i), i));
assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING.charAt(i), i));
assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING.charAt(i), i));
assertTrue(0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase().charAt(i), i));
}
for (int i = 0; i < WHITESPACE_STRING.length(); i++) {
assertEquals(-1, StringUtil.indexOfIgnoreCase(TEST_STRING, WHITESPACE_STRING.charAt(i), i));
}
// Test all alpha-chars
for (int i = 'a'; i < 'z'; i++) {
if ((TEST_STRING.indexOf(i) < 0) && (TEST_STRING.indexOf(Character.toUpperCase((char) i)) < 0)) {
assertEquals(TEST_STRING + " seems to contain '" + (char) i + "', at index " + Math.max(TEST_STRING.indexOf(i), TEST_STRING.indexOf(Character.toUpperCase((char) i))), -1, StringUtil.indexOfIgnoreCase(TEST_STRING, i, 0));
}
else {
assertTrue(TEST_STRING + " seems to not contain '" + (char) i + "', at index " + TEST_STRING.indexOf(i), 0 <= StringUtil.indexOfIgnoreCase(TEST_STRING, i, 0));
}
}
}
public void testLastIndexOfIgnoreCaseChar() {
// Must contain all chars in string
for (int i = 0; i < TEST_STRING.length(); i++) {
assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.charAt(i)));
assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING.charAt(i)));
assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING.charAt(i)));
assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, Character.toUpperCase(TEST_STRING.charAt(i))));
assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(EMPTY_STRING, TEST_STRING.charAt(i)));
assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(WHITESPACE_STRING, TEST_STRING.charAt(i)));
assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(null, TEST_STRING.charAt(i)));
}
for (int i = 0; i < TEST_SUB_STRING.length(); i++) {
assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.charAt(i)));
assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING.charAt(i)));
assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING.charAt(i)));
assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase().charAt(i)));
}
for (int i = 0; i < WHITESPACE_STRING.length(); i++) {
assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, WHITESPACE_STRING.charAt(i)));
}
// Test all alpha-chars
for (int i = 'a'; i < 'z'; i++) {
if ((TEST_STRING.indexOf(i) < 0) && (TEST_STRING.indexOf(Character.toUpperCase((char) i)) < 0)) {
assertEquals(TEST_STRING + " seems to contain '" + (char) i + "', at index " + Math.max(TEST_STRING.indexOf(i), TEST_STRING.indexOf(Character.toUpperCase((char) i))), -1, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, i));
}
else {
assertTrue(TEST_STRING + " seems to not contain '" + (char) i + "', at index " + TEST_STRING.indexOf(i), 0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, i));
}
}
}
public void testLastIndexOfIgnoreCaseCharPos() {
// Must contain all chars in string
for (int i = 0; i < TEST_STRING.length(); i++) {
assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_STRING.charAt(i), i));
assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_STRING.charAt(i), i));
assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_STRING.charAt(i), i));
assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, Character.toUpperCase(TEST_STRING.charAt(i)), i));
assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(EMPTY_STRING, TEST_STRING.charAt(i), i));
assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(WHITESPACE_STRING, TEST_STRING.charAt(i), i));
assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(null, TEST_STRING.charAt(i), i));
}
for (int i = 0; i < TEST_SUB_STRING.length(); i++) {
assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.charAt(i), TEST_STRING.length()));
assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toUpperCase(), TEST_SUB_STRING.charAt(i), TEST_STRING.length()));
assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING.toLowerCase(), TEST_SUB_STRING.charAt(i), TEST_STRING.length()));
assertTrue(0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, TEST_SUB_STRING.toUpperCase().charAt(i), TEST_STRING.length()));
}
for (int i = 0; i < WHITESPACE_STRING.length(); i++) {
assertEquals(-1, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, WHITESPACE_STRING.charAt(i), TEST_STRING.length()));
}
// Test all alpha-chars
for (int i = 'a'; i < 'z'; i++) {
if ((TEST_STRING.indexOf(i) < 0) && (TEST_STRING.indexOf(Character.toUpperCase((char) i)) < 0)) {
assertEquals(TEST_STRING + " seems to contain '" + (char) i + "', at index " + Math.max(TEST_STRING.indexOf(i), TEST_STRING.indexOf(Character.toUpperCase((char) i))), -1, StringUtil.lastIndexOfIgnoreCase(TEST_STRING, i, TEST_STRING.length()));
}
else {
assertTrue(TEST_STRING + " seems to not contain '" + (char) i + "', at index " + TEST_STRING.indexOf(i), 0 <= StringUtil.lastIndexOfIgnoreCase(TEST_STRING, i, TEST_STRING.length()));
}
}
}
public void testLtrim() {
assertEquals(TEST_STRING, StringUtil.ltrim(TEST_STRING));
assertEquals(TEST_STRING, StringUtil.ltrim(" " + TEST_STRING));
assertEquals(TEST_STRING, StringUtil.ltrim(WHITESPACE_STRING + TEST_STRING));
assertFalse(TEST_STRING.equals(StringUtil.ltrim(TEST_STRING + WHITESPACE_STRING)));
// TODO: Test is not complete
}
public void testRtrim() {
assertEquals(TEST_STRING, StringUtil.rtrim(TEST_STRING));
assertEquals(TEST_STRING, StringUtil.rtrim(TEST_STRING + " "));
assertEquals(TEST_STRING, StringUtil.rtrim(TEST_STRING + WHITESPACE_STRING));
assertFalse(TEST_STRING.equals(StringUtil.rtrim(WHITESPACE_STRING + TEST_STRING)));
// TODO: Test is not complete
}
public void testReplace() {
assertEquals("", StringUtil.replace(TEST_STRING, TEST_STRING, ""));
assertEquals("", StringUtil.replace("", "", ""));
assertEquals("", StringUtil.replace("", "xyzzy", "xyzzy"));
assertEquals(TEST_STRING, StringUtil.replace(TEST_STRING, "", "xyzzy"));
assertEquals("aabbdd", StringUtil.replace("aabbccdd", "c", ""));
assertEquals("aabbccdd", StringUtil.replace("aabbdd", "bd", "bccd"));
// TODO: Test is not complete
}
public void testReplaceIgnoreCase() {
assertEquals("", StringUtil.replaceIgnoreCase(TEST_STRING, TEST_STRING.toUpperCase(), ""));
assertEquals("", StringUtil.replaceIgnoreCase("", "", ""));
assertEquals("", StringUtil.replaceIgnoreCase("", "xyzzy", "xyzzy"));
assertEquals(TEST_STRING, StringUtil.replaceIgnoreCase(TEST_STRING, "", "xyzzy"));
assertEquals("aabbdd", StringUtil.replaceIgnoreCase("aabbCCdd", "c", ""));
assertEquals("aabbdd", StringUtil.replaceIgnoreCase("aabbccdd", "C", ""));
assertEquals("aabbccdd", StringUtil.replaceIgnoreCase("aabbdd", "BD", "bccd"));
assertEquals("aabbccdd", StringUtil.replaceIgnoreCase("aabBDd", "bd", "bccd"));
// TODO: Test is not complete
}
public void testCut() {
assertEquals(TEST_STRING, StringUtil.cut(TEST_STRING, TEST_STRING.length(), ".."));
assertEquals("This is a test..", StringUtil.cut("This is a test of how this works", 16, ".."));
assertEquals("This is a test", StringUtil.cut("This is a test of how this works", 16, null));
assertEquals("This is a test", StringUtil.cut("This is a test of how this works", 16, ""));
// TODO: Test is not complete
}
public void testCaptialize() {
assertNull(StringUtil.capitalize(null));
assertEquals(TEST_STRING.toUpperCase(), StringUtil.capitalize(TEST_STRING.toUpperCase()));
assertTrue(StringUtil.capitalize("abc").charAt(0) == 'A');
}
public void testCaptializePos() {
assertNull(StringUtil.capitalize(null, 45));
// TODO: Should this throw IllegalArgument or StringIndexOutOfBonds?
assertEquals(TEST_STRING, StringUtil.capitalize(TEST_STRING, TEST_STRING.length() + 45));
for (int i = 0; i < TEST_STRING.length(); i++) {
assertTrue(Character.isUpperCase(StringUtil.capitalize(TEST_STRING, i).charAt(i)));
}
}
public void testPad() {
assertEquals(TEST_STRING + "...", StringUtil.pad(TEST_STRING, TEST_STRING.length() + 3, "..", false));
assertEquals(TEST_STRING, StringUtil.pad(TEST_STRING, 4, ".", false));
assertEquals(TEST_STRING, StringUtil.pad(TEST_STRING, 4, ".", true));
assertEquals("..." + TEST_STRING, StringUtil.pad(TEST_STRING, TEST_STRING.length() + 3, "..", true));
}
public void testToDate() {
long time = System.currentTimeMillis();
Date now = new Date(time - time % 60000); // Default format seems to have no seconds..
Date date = StringUtil.toDate(DateFormat.getInstance().format(now));
assertNotNull(date);
assertEquals(now, date);
}
public void testToDateWithFormatString() {
Calendar cal = new GregorianCalendar();
cal.clear();
cal.set(1976, 2, 16); // Month is 0-based
Date date = StringUtil.toDate("16.03.1976", "dd.MM.yyyy");
assertNotNull(date);
assertEquals(cal.getTime(), date);
cal.clear();
cal.set(2004, 4, 13, 23, 51, 3);
date = StringUtil.toDate("2004-5-13 23:51 (03)", "yyyy-MM-dd hh:mm (ss)");
assertNotNull(date);
assertEquals(cal.getTime(), date);
cal.clear();
cal.set(Calendar.HOUR, 1);
cal.set(Calendar.MINUTE, 2);
cal.set(Calendar.SECOND, 3);
date = StringUtil.toDate("123", "hms");
assertNotNull(date);
assertEquals(cal.getTime(), date);
}
public void testToDateWithFormat() {
Calendar cal = new GregorianCalendar();
cal.clear();
cal.set(1976, 2, 16); // Month is 0-based
Date date = StringUtil.toDate("16.03.1976", new SimpleDateFormat("dd.MM.yyyy"));
assertNotNull(date);
assertEquals(cal.getTime(), date);
cal.clear();
cal.set(2004, 4, 13, 23, 51);
date = StringUtil.toDate("13.5.04 23:51",
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, new Locale("no", "NO")));
assertNotNull(date);
assertEquals(cal.getTime(), date);
cal.clear();
cal.set(Calendar.HOUR, 1);
cal.set(Calendar.MINUTE, 2);
date = StringUtil.toDate("1:02 am",
DateFormat.getTimeInstance(DateFormat.SHORT, Locale.US));
assertNotNull(date);
assertEquals(cal.getTime(), date);
}
public void testToTimestamp() {
Calendar cal = new GregorianCalendar();
cal.clear();
cal.set(1976, 2, 16, 21, 28, 4); // Month is 0-based
Date date = StringUtil.toTimestamp("1976-03-16 21:28:04");
assertNotNull(date);
assertTrue(date instanceof Timestamp);
assertEquals(cal.getTime(), date);
}
public void testToStringArray() {
String[] arr = StringUtil.toStringArray(TEST_DELIM_STRING);
assertNotNull(arr);
assertEquals(STRING_ARRAY.length, arr.length);
for (int i = 0; i < arr.length; i++) {
assertEquals(STRING_ARRAY[i], arr[i]);
}
}
public void testToStringArrayDelim() {
String[] arr = StringUtil.toStringArray("-1---2-3--4-5", "---");
String[] arr2 = {"1", "2", "3", "4", "5"};
assertNotNull(arr);
assertEquals(arr2.length, arr.length);
for (int i = 0; i < arr.length; i++) {
assertEquals(arr2[i], arr[i]);
}
arr = StringUtil.toStringArray("1, 2, 3; 4 5", ",; ");
assertNotNull(arr);
assertEquals(arr2.length, arr.length);
for (int i = 0; i < arr.length; i++) {
assertEquals(arr2[i], arr[i]);
}
}
public void testToIntArray() {
int[] arr = StringUtil.toIntArray(TEST_INT_DELIM_STRING);
assertNotNull(arr);
assertEquals(INT_ARRAY.length, arr.length);
for (int i = 0; i < arr.length; i++) {
assertEquals(INT_ARRAY[i], arr[i]);
}
}
public void testToIntArrayDelim() {
int[] arr = StringUtil.toIntArray("-1---2-3--4-5", "---");
int[] arr2 = {1, 2, 3, 4, 5};
assertNotNull(arr);
assertEquals(arr2.length, arr.length);
for (int i = 0; i < arr.length; i++) {
assertEquals(arr2[i], arr[i]);
}
arr = StringUtil.toIntArray("1, 2, 3; 4 5", ",; ");
assertNotNull(arr);
assertEquals(arr2.length, arr.length);
for (int i = 0; i < arr.length; i++) {
assertEquals(arr2[i], arr[i]);
}
}
public void testToIntArrayDelimBase() {
int[] arr = StringUtil.toIntArray("-1___2_3__F_a", "___", 16);
int[] arr2 = {-1, 2, 3, 0xf, 0xa};
assertNotNull(arr);
assertEquals(arr2.length, arr.length);
for (int i = 0; i < arr.length; i++) {
assertEquals(arr2[i], arr[i]);
}
arr = StringUtil.toIntArray("-1, 2, 3; 17 12", ",; ", 8);
assertNotNull(arr);
assertEquals(arr2.length, arr.length);
for (int i = 0; i < arr.length; i++) {
assertEquals(arr2[i], arr[i]);
}
}
public void testToLongArray() {
long[] arr = StringUtil.toLongArray(TEST_INT_DELIM_STRING);
assertNotNull(arr);
assertEquals(INT_ARRAY.length, arr.length);
for (int i = 0; i < arr.length; i++) {
assertEquals(INT_ARRAY[i], arr[i]);
}
}
public void testToLongArrayDelim() {
long[] arr = StringUtil.toLongArray("-12854928752983___2_3__4_5", "___");
long[] arr2 = {-12854928752983L, 2, 3, 4, 5};
assertNotNull(arr);
assertEquals(arr2.length, arr.length);
for (int i = 0; i < arr.length; i++) {
assertEquals(arr2[i], arr[i]);
}
arr = StringUtil.toLongArray("-12854928752983, 2, 3; 4 5", ",; ");
assertNotNull(arr);
assertEquals(arr2.length, arr.length);
for (int i = 0; i < arr.length; i++) {
assertEquals(arr2[i], arr[i]);
}
}
public void testToDoubleArray() {
double[] arr = StringUtil.toDoubleArray(TEST_DOUBLE_DELIM_STRING);
assertNotNull(arr);
assertEquals(DOUBLE_ARRAY.length, arr.length);
for (int i = 0; i < arr.length; i++) {
assertEquals(DOUBLE_ARRAY[i], arr[i], 0d);
}
}
public void testToDoubleArrayDelim() {
double[] arr = StringUtil.toDoubleArray("-12854928752983___.2_3__4_5e4", "___");
double[] arr2 = {-12854928752983L, .2, 3, 4, 5e4};
assertNotNull(arr);
assertEquals(arr2.length, arr.length);
for (int i = 0; i < arr.length; i++) {
assertEquals(arr2[i], arr[i], 0d);
}
arr = StringUtil.toDoubleArray("-12854928752983, .2, 3; 4 5E4", ",; ");
assertNotNull(arr);
assertEquals(arr2.length, arr.length);
for (int i = 0; i < arr.length; i++) {
assertEquals(arr2[i], arr[i], 0d);
}
}
public void testTestToColor() {
// Test all constants
assertEquals(Color.black, StringUtil.toColor("black"));
assertEquals(Color.black, StringUtil.toColor("BLACK"));
assertEquals(Color.blue, StringUtil.toColor("blue"));
assertEquals(Color.blue, StringUtil.toColor("BLUE"));
assertEquals(Color.cyan, StringUtil.toColor("cyan"));
assertEquals(Color.cyan, StringUtil.toColor("CYAN"));
assertEquals(Color.darkGray, StringUtil.toColor("darkGray"));
assertEquals(Color.darkGray, StringUtil.toColor("DARK_GRAY"));
assertEquals(Color.gray, StringUtil.toColor("gray"));
assertEquals(Color.gray, StringUtil.toColor("GRAY"));
assertEquals(Color.green, StringUtil.toColor("green"));
assertEquals(Color.green, StringUtil.toColor("GREEN"));
assertEquals(Color.lightGray, StringUtil.toColor("lightGray"));
assertEquals(Color.lightGray, StringUtil.toColor("LIGHT_GRAY"));
assertEquals(Color.magenta, StringUtil.toColor("magenta"));
assertEquals(Color.magenta, StringUtil.toColor("MAGENTA"));
assertEquals(Color.orange, StringUtil.toColor("orange"));
assertEquals(Color.orange, StringUtil.toColor("ORANGE"));
assertEquals(Color.pink, StringUtil.toColor("pink"));
assertEquals(Color.pink, StringUtil.toColor("PINK"));
assertEquals(Color.red, StringUtil.toColor("red"));
assertEquals(Color.red, StringUtil.toColor("RED"));
assertEquals(Color.white, StringUtil.toColor("white"));
assertEquals(Color.white, StringUtil.toColor("WHITE"));
assertEquals(Color.yellow, StringUtil.toColor("yellow"));
assertEquals(Color.yellow, StringUtil.toColor("YELLOW"));
// System.out.println(StringUtil.deepToString(Color.yellow));
// System.out.println(StringUtil.deepToString(Color.pink, true, -1));
// Test HTML/CSS style color
for (int i = 0; i < 256; i++) {
int c = i;
if (i < 0x10) {
c = i * 16;
}
String colorStr = "#" + Integer.toHexString(i) + Integer.toHexString(i) + Integer.toHexString(i);
String colorStrAlpha = "#" + Integer.toHexString(i) + Integer.toHexString(i) + Integer.toHexString(i) + Integer.toHexString(i);
assertEquals(new Color(c, c, c), StringUtil.toColor(colorStr));
assertEquals(new Color(c, c, c, c), StringUtil.toColor(colorStrAlpha));
}
// Test null
// TODO: Hmmm.. Maybe reconsider this..
assertNull(StringUtil.toColor(null));
// Test
try {
StringUtil.toColor("illegal-color-value");
fail("toColor with illegal color value should throw IllegalArgumentException.");
}
catch (IllegalArgumentException e) {
assertNotNull(e.getMessage());
}
}
public void testToColorString() {
assertEquals("#ff0000", StringUtil.toColorString(Color.red));
assertEquals("#00ff00", StringUtil.toColorString(Color.green));
assertEquals("#0000ff", StringUtil.toColorString(Color.blue));
assertEquals("#101010", StringUtil.toColorString(new Color(0x10, 0x10, 0x10)));
for (int i = 0; i < 256; i++) {
String str = (i < 0x10 ? "0" : "") + Integer.toHexString(i);
assertEquals("#" + str + str + str, StringUtil.toColorString(new Color(i, i, i)));
}
// Test null
// TODO: Hmmm.. Maybe reconsider this..
assertNull(StringUtil.toColorString(null));
}
public void testIsNumber() {
assertTrue(StringUtil.isNumber("0"));
assertTrue(StringUtil.isNumber("12345"));
assertTrue(StringUtil.isNumber(TEST_INTEGER.toString()));
assertTrue(StringUtil.isNumber("1234567890123456789012345678901234567890"));
assertTrue(StringUtil.isNumber(String.valueOf(Long.MAX_VALUE) + String.valueOf(Long.MAX_VALUE)));
assertFalse(StringUtil.isNumber("abc"));
assertFalse(StringUtil.isNumber(TEST_STRING));
}
public void testIsNumberNegative() {
assertTrue(StringUtil.isNumber("-12345"));
assertTrue(StringUtil.isNumber('-' + TEST_INTEGER.toString()));
assertTrue(StringUtil.isNumber("-1234567890123456789012345678901234567890"));
assertTrue(StringUtil.isNumber('-' + String.valueOf(Long.MAX_VALUE) + String.valueOf(Long.MAX_VALUE)));
assertFalse(StringUtil.isNumber("-abc"));
assertFalse(StringUtil.isNumber('-' + TEST_STRING));
}
public void testCamelToLispNull() {
try {
StringUtil.camelToLisp(null);
fail("should not accept null");
}
catch (IllegalArgumentException iae) {
assertNotNull(iae.getMessage());
}
}
public void testCamelToLispNoConversion() {
assertEquals("", StringUtil.camelToLisp(""));
assertEquals("equal", StringUtil.camelToLisp("equal"));
assertEquals("allready-lisp", StringUtil.camelToLisp("allready-lisp"));
}
public void testCamelToLispSimple() {
// Simple tests
assertEquals("foo-bar", StringUtil.camelToLisp("fooBar"));
}
public void testCamelToLispCase() {
// Casing
assertEquals("my-url", StringUtil.camelToLisp("myURL"));
assertEquals("another-url", StringUtil.camelToLisp("AnotherURL"));
}
public void testCamelToLispMulti() {
// Several words
assertEquals("http-request-wrapper", StringUtil.camelToLisp("HttpRequestWrapper"));
String s = StringUtil.camelToLisp("HttpURLConnection");
assertEquals("http-url-connection", s);
// Long and short abbre in upper case
assertEquals("welcome-to-my-world", StringUtil.camelToLisp("WELCOMEToMYWorld"));
}
public void testCamelToLispLeaveUntouched() {
// Leave others untouched
assertEquals("a-slightly-longer-and-more-bumpy-string?.,[]()", StringUtil.camelToLisp("ASlightlyLongerANDMoreBumpyString?.,[]()"));
}
public void testCamelToLispNumbers() {
// Numbers
// TODO: FixMe
String s = StringUtil.camelToLisp("my45Caliber");
assertEquals("my-45-caliber", s);
assertEquals("hello-12345-world-67890", StringUtil.camelToLisp("Hello12345world67890"));
assertEquals("hello-12345-my-world-67890-this-time", StringUtil.camelToLisp("HELLO12345MyWorld67890thisTime"));
assertEquals("hello-12345-world-67890-too", StringUtil.camelToLisp("Hello12345WORLD67890too"));
}
public void testLispToCamelNull() {
try {
StringUtil.lispToCamel(null);
fail("should not accept null");
}
catch (IllegalArgumentException iae) {
assertNotNull(iae.getMessage());
}
}
public void testLispToCamelNoConversion() {
assertEquals("", StringUtil.lispToCamel(""));
assertEquals("equal", StringUtil.lispToCamel("equal"));
assertEquals("allreadyCamel", StringUtil.lispToCamel("allreadyCamel"));
}
public void testLispToCamelSimple() {
// Simple tests
assertEquals("fooBar", StringUtil.lispToCamel("foo-bar"));
assertEquals("myUrl", StringUtil.lispToCamel("my-URL"));
assertEquals("anotherUrl", StringUtil.lispToCamel("ANOTHER-URL"));
}
public void testLispToCamelCase() {
// Casing
assertEquals("Object", StringUtil.lispToCamel("object", true));
assertEquals("object", StringUtil.lispToCamel("Object", false));
}
public void testLispToCamelMulti() {
// Several words
assertEquals("HttpRequestWrapper", StringUtil.lispToCamel("http-request-wrapper", true));
}
public void testLispToCamelLeaveUntouched() {
// Leave others untouched
assertEquals("ASlightlyLongerAndMoreBumpyString?.,[]()", StringUtil.lispToCamel("a-slightly-longer-and-more-bumpy-string?.,[]()", true));
}
public void testLispToCamelNumber() {
// Numbers
assertEquals("my45Caliber", StringUtil.lispToCamel("my-45-caliber"));
}
}

View File

@@ -0,0 +1,146 @@
package com.twelvemonkeys.util;
import java.util.Map;
import java.beans.IntrospectionException;
import java.io.Serializable;
/**
* BeanMapTestCase
* <p/>
* @todo Extend with BeanMap specific tests
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/BeanMapTestCase.java#2 $
*/
public class BeanMapTestCase extends MapAbstractTestCase {
public boolean isPutAddSupported() {
return false;
}
public boolean isRemoveSupported() {
return false;
}
public boolean isSetValueSupported() {
return true;
}
public boolean isAllowNullKey() {
return false;
}
public Object[] getSampleKeys() {
return new Object[] {
"blah", "foo", "bar", "baz", "tmp", "gosh", "golly", "gee"
};
}
public Object[] getSampleValues() {
return new Object[] {
"blahv", "foov", "barv", "bazv", "tmpv", "goshv", "gollyv", "geev"
};
}
public Object[] getNewSampleValues() {
return new Object[] {
"newblahv", "newfoov", "newbarv", "newbazv", "newtmpv", "newgoshv", "newgollyv", "newgeev"
};
}
public Map makeEmptyMap() {
try {
return new BeanMap(new NullBean());
}
catch (IntrospectionException e) {
throw new RuntimeException(e);
}
}
public Map makeFullMap() {
try {
return new BeanMap(new MyBean());
}
catch (IntrospectionException e) {
throw new RuntimeException(e);
}
}
public static class MyBean implements Serializable {
Object blah = "blahv";
Object foo = "foov";
Object bar = "barv";
Object baz = "bazv";
Object tmp = "tmpv";
Object gosh = "goshv";
Object golly = "gollyv";
Object gee = "geev";
public Object getBar() {
return bar;
}
public void setBar(Object pBar) {
bar = pBar;
}
public Object getBaz() {
return baz;
}
public void setBaz(Object pBaz) {
baz = pBaz;
}
public Object getBlah() {
return blah;
}
public void setBlah(Object pBlah) {
blah = pBlah;
}
public Object getFoo() {
return foo;
}
public void setFoo(Object pFoo) {
foo = pFoo;
}
public Object getGee() {
return gee;
}
public void setGee(Object pGee) {
gee = pGee;
}
public Object getGolly() {
return golly;
}
public void setGolly(Object pGolly) {
golly = pGolly;
}
public Object getGosh() {
return gosh;
}
public void setGosh(Object pGosh) {
gosh = pGosh;
}
public Object getTmp() {
return tmp;
}
public void setTmp(Object pTmp) {
tmp = pTmp;
}
}
static class NullBean implements Serializable { }
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,182 @@
/*
* Copyright 2001-2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.twelvemonkeys.util;
import java.util.*;
/**
* Tests LRUMap.
*
* @version $Revision: #2 $ $Date: 2008/07/15 $
*
* @author James Strachan
* @author Morgan Delagrange
* @author Stephen Colebourne
*/
public class LRUMapTestCase extends LinkedMapTestCase {
public boolean isGetStructuralModify() {
return true;
}
//-----------------------------------------------------------------------
public Map makeEmptyMap() {
LRUMap map = new LRUMap();
return map;
}
//-----------------------------------------------------------------------
public void testRemoveLRU() {
LRUMap map2 = new LRUMap(3);
map2.put(new Integer(1),"foo");
map2.put(new Integer(2),"foo");
map2.put(new Integer(3),"foo");
map2.put(new Integer(4),"foo"); // removes 1 since max size exceeded
map2.removeLRU(); // should be Integer(2)
assertTrue("Second to last value should exist",map2.get(new Integer(3)).equals("foo"));
assertTrue("First value inserted should not exist", map2.get(new Integer(1)) == null);
}
public void testMultiplePuts() {
LRUMap map2 = new LRUMap(2);
map2.put(new Integer(1),"foo");
map2.put(new Integer(2),"bar");
map2.put(new Integer(3),"foo");
map2.put(new Integer(4),"bar");
assertTrue("last value should exist",map2.get(new Integer(4)).equals("bar"));
assertTrue("LRU should not exist", map2.get(new Integer(1)) == null);
}
/**
* Confirm that putAll(Map) does not cause the LRUMap
* to exceed its maxiumum size.
*/
public void testPutAll() {
LRUMap map2 = new LRUMap(3);
map2.put(new Integer(1),"foo");
map2.put(new Integer(2),"foo");
map2.put(new Integer(3),"foo");
HashMap hashMap = new HashMap();
hashMap.put(new Integer(4),"foo");
map2.putAll(hashMap);
assertTrue("max size is 3, but actual size is " + map2.size(),
map2.size() == 3);
assertTrue("map should contain the Integer(4) object",
map2.containsKey(new Integer(4)));
}
/**
* Test that the size of the map is reduced immediately
* when setMaximumSize(int) is called
*/
public void testSetMaximumSize() {
LRUMap map = new LRUMap(6);
map.put("1","1");
map.put("2","2");
map.put("3","3");
map.put("4","4");
map.put("5","5");
map.put("6","6");
map.setMaxSize(3);
assertTrue("map should have size = 3, but actually = " + map.size(),
map.size() == 3);
}
public void testGetPromotion() {
LRUMap map = new LRUMap(3);
map.put("1","1");
map.put("2","2");
map.put("3","3");
// LRU is now 1 (then 2 then 3)
// promote 1 to top
// eviction order is now 2,3,1
map.get("1");
// add another value, forcing a remove
// 2 should be evicted (then 3,1,4)
map.put("4","4");
Iterator keyIterator = map.keySet().iterator();
Object[] keys = new Object[3];
for (int i = 0; keyIterator.hasNext() ; ++i) {
keys[i] = keyIterator.next();
}
assertTrue("first evicted should be 3, was " + keys[0], keys[0].equals("3"));
assertTrue("second evicted should be 1, was " + keys[1], keys[1].equals("1"));
assertTrue("third evicted should be 4, was " + keys[2], keys[2].equals("4"));
}
/**
* You should be able to subclass LRUMap and perform a
* custom action when items are removed automatically
* by the LRU algorithm (the removeLRU() method).
*/
public void testLRUSubclass() {
LRUCounter counter = new LRUCounter(3);
// oldest <--> newest
// 1
counter.put("1","foo");
// 1 2
counter.put("2","foo");
// 1 2 3
counter.put("3","foo");
// 2 3 1
counter.put("1","foo");
// 3 1 4 (2 goes out)
counter.put("4","foo");
// 1 4 5 (3 goes out)
counter.put("5","foo");
// 4 5 2 (1 goes out)
counter.put("2","foo");
// 4 2
counter.remove("5");
assertTrue("size should be 2, but was " + counter.size(), counter.size() == 2);
assertTrue("removedCount should be 3 but was " + counter.removedCount,
counter.removedCount == 3);
assertTrue("first removed was '2'",counter.list.get(0).equals("2"));
assertTrue("second removed was '3'",counter.list.get(1).equals("3"));
assertTrue("third removed was '1'",counter.list.get(2).equals("1"));
//assertTrue("oldest key is '4'",counter.get(0).equals("4"));
//assertTrue("newest key is '2'",counter.get(1).equals("2"));
}
private class LRUCounter extends LRUMap {
int removedCount = 0;
List list = new ArrayList(3);
LRUCounter(int i) {
super(i);
}
public void processRemoved(Entry pEntry) {
++removedCount;
list.add(pEntry.getKey());
}
}
}

View File

@@ -0,0 +1,175 @@
/*
* Copyright 2001-2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.twelvemonkeys.util;
import java.util.Iterator;
import java.util.Map;
/**
* Unit tests
* {@link org.apache.commons.collections.SequencedHashMap}.
* Be sure to use the "labRat" instance whenever possible,
* so that subclasses will be tested correctly.
*
* @version $Revision: #2 $ $Date: 2008/07/15 $
*
* @author Morgan Delagrange
* @author Daniel Rall
* @author Henning P. Schmiedehausen
* @author James Strachan
*/
public class LinkedMapTestCase extends MapAbstractTestCase {
/**
* The instance to experiment on.
*/
protected LinkedMap labRat;
public void setUp() throws Exception {
super.setUp();
// use makeMap and cast the result to a SeqHashMap
// so that subclasses of SeqHashMap can share these tests
labRat = (LinkedMap) makeEmptyMap();
}
public Map makeEmptyMap() {
return new LinkedMap();
}
protected Object[] getKeys() {
return new Object[] { "foo", "baz", "eek" };
}
protected Object[] getValues() {
return new Object[] { "bar", "frob", new Object() };
}
public void testSequenceMap() throws Throwable {
Object[] keys = getKeys();
int expectedSize = keys.length;
Object[] values = getValues();
for (int i = 0; i < expectedSize; i++) {
labRat.put(keys[i], values[i]);
}
// Test size().
assertEquals("size() does not match expected size",
expectedSize, labRat.size());
// Test clone(), iterator(), and get(Object).
LinkedMap clone = (LinkedMap) labRat.clone();
assertEquals("Size of clone does not match original",
labRat.size(), clone.size());
Iterator origEntries = labRat.entrySet().iterator();
Iterator copiedEntries = clone.entrySet().iterator();
while (origEntries.hasNext()) {
Map.Entry origEntry = (Map.Entry)origEntries.next();
Map.Entry copiedEntry = (Map.Entry)copiedEntries.next();
assertEquals("Cloned key does not match original",
origEntry.getKey(), copiedEntry.getKey());
assertEquals("Cloned value does not match original",
origEntry.getValue(), copiedEntry.getValue());
assertEquals("Cloned entry does not match original",
origEntry, copiedEntry);
}
assertTrue("iterator() returned different number of elements than keys()",
!copiedEntries.hasNext());
// Test sequence()
/*
List seq = labRat.sequence();
assertEquals("sequence() returns more keys than in the Map",
expectedSize, seq.size());
for (int i = 0; i < seq.size(); i++) {
assertEquals("Key " + i + " is not the same as the key in the Map",
keys[i], seq.get(i));
}
*/
}
/*
public void testYoungest() {
labRat.put(new Integer(1),"foo");
labRat.put(new Integer(2),"bar");
assertTrue("first key is correct",labRat.get(0).equals(new Integer(1)));
labRat.put(new Integer(1),"boo");
assertTrue("second key is reassigned to first",labRat.get(0).equals(new Integer(2)));
}
public void testYoungestReplaceNullWithValue() {
labRat.put(new Integer(1),null);
labRat.put(new Integer(2),"foo");
assertTrue("first key is correct",labRat.get(0).equals(new Integer(1)));
labRat.put(new Integer(1),"bar");
assertTrue("second key is reassigned to first",labRat.get(0).equals(new Integer(2)));
}
public void testYoungestReplaceValueWithNull() {
labRat.put(new Integer(1),"bar");
labRat.put(new Integer(2),"foo");
assertTrue("first key is correct",labRat.get(0).equals(new Integer(1)));
labRat.put(new Integer(1),null);
assertTrue("second key is reassigned to first",labRat.get(0).equals(new Integer(2)));
}
*/
// override TestMap method with more specific tests
/*
public void testFullMapSerialization()
throws IOException, ClassNotFoundException {
LinkedMap map = (LinkedMap) makeFullMap();
if (!(map instanceof Serializable)) return;
byte[] objekt = writeExternalFormToBytes((Serializable) map);
LinkedMap map2 = (LinkedMap) readExternalFormFromBytes(objekt);
assertEquals("Both maps are same size",map.size(), getSampleKeys().length);
assertEquals("Both maps are same size",map2.size(),getSampleKeys().length);
assertEquals("Both maps have the same first key",
map.getFirstKey(),getSampleKeys()[0]);
assertEquals("Both maps have the same first key",
map2.getFirstKey(),getSampleKeys()[0]);
assertEquals("Both maps have the same last key",
map.getLastKey(),getSampleKeys()[getSampleKeys().length - 1]);
assertEquals("Both maps have the same last key",
map2.getLastKey(),getSampleKeys()[getSampleKeys().length - 1]);
}
*/
/*
public void testIndexOf() throws Exception {
Object[] keys = getKeys();
int expectedSize = keys.length;
Object[] values = getValues();
for (int i = 0; i < expectedSize; i++) {
labRat.put(keys[i], values[i]);
}
// test that the index returned are in the same order that they were
// placed in the map
for (int i = 0; i < keys.length; i++) {
assertEquals("indexOf with existing key failed", i, labRat.indexOf(keys[i]));
}
// test non existing key..
assertEquals("test with non-existing key failed", -1, labRat.indexOf("NonExistingKey"));
}
*/
public void tearDown() throws Exception {
labRat = null;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,187 @@
package com.twelvemonkeys.util;
import java.util.Map;
import java.util.HashMap;
/**
* NOTE: This TestCase is written especially for NullMap, and is full of dirty
* tricks. A good indication that NullMap is not a good, general-purpose Map
* implementation...
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/NullMapTestCase.java#2 $
*/
public class NullMapTestCase extends MapAbstractTestCase {
private boolean empty = true;
public Map makeEmptyMap() {
return new NullMap();
}
public Map makeFullMap() {
return new NullMap();
}
public Map makeConfirmedMap() {
// Always empty
return new HashMap();
}
public void resetEmpty() {
empty = true;
super.resetEmpty();
}
public void resetFull() {
empty = false;
super.resetFull();
}
public void verifyAll() {
if (empty) {
super.verifyAll();
}
}
// Overriden, as this map is always empty
public void testMapIsEmpty() {
resetEmpty();
assertEquals("Map.isEmpty() should return true with an empty map",
true, map.isEmpty());
verifyAll();
resetFull();
assertEquals("Map.isEmpty() should return true with a full map",
true, map.isEmpty());
}
// Overriden, as this map is always empty
public void testMapSize() {
resetEmpty();
assertEquals("Map.size() should be 0 with an empty map",
0, map.size());
verifyAll();
resetFull();
assertEquals("Map.size() should equal the number of entries " +
"in the map", 0, map.size());
}
public void testMapContainsKey() {
Object[] keys = getSampleKeys();
resetEmpty();
for(int i = 0; i < keys.length; i++) {
assertTrue("Map must not contain key when map is empty",
!map.containsKey(keys[i]));
}
verifyAll();
}
public void testMapContainsValue() {
Object[] values = getSampleValues();
resetEmpty();
for(int i = 0; i < values.length; i++) {
assertTrue("Empty map must not contain value",
!map.containsValue(values[i]));
}
verifyAll();
}
public void testMapEquals() {
resetEmpty();
assertTrue("Empty maps unequal.", map.equals(confirmed));
verifyAll();
}
public void testMapHashCode() {
resetEmpty();
assertTrue("Empty maps have different hashCodes.",
map.hashCode() == confirmed.hashCode());
}
public void testMapGet() {
resetEmpty();
Object[] keys = getSampleKeys();
for (int i = 0; i < keys.length; i++) {
assertTrue("Empty map.get() should return null.",
map.get(keys[i]) == null);
}
verifyAll();
}
public void testMapPut() {
resetEmpty();
Object[] keys = getSampleKeys();
Object[] values = getSampleValues();
Object[] newValues = getNewSampleValues();
for (int i = 0; i < keys.length; i++) {
Object o = map.put(keys[i], values[i]);
//confirmed.put(keys[i], values[i]);
verifyAll();
assertTrue("First map.put should return null", o == null);
}
for (int i = 0; i < keys.length; i++) {
map.put(keys[i], newValues[i]);
//confirmed.put(keys[i], newValues[i]);
verifyAll();
}
}
public void testMapToString() {
resetEmpty();
assertTrue("Empty map toString() should not return null",
map.toString() != null);
verifyAll();
}
public void testMapPutAll() {
// TODO: Find a menaingful way to test this
}
public void testMapRemove() {
resetEmpty();
Object[] keys = getSampleKeys();
for(int i = 0; i < keys.length; i++) {
Object o = map.remove(keys[i]);
assertTrue("First map.remove should return null", o == null);
}
verifyAll();
}
//-----------------------------------------------------------------------
public void testEntrySetClearChangesMap() {
}
public void testKeySetClearChangesMap() {
}
public void testKeySetRemoveChangesMap() {
}
public void testValuesClearChangesMap() {
}
public void testEntrySetContains1() {
}
public void testEntrySetContains2() {
}
public void testEntrySetContains3() {
}
public void testEntrySetRemove1() {
}
public void testEntrySetRemove2() {
}
public void testEntrySetRemove3() {
}
}

View File

@@ -0,0 +1,307 @@
/*
* Copyright 2001-2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.twelvemonkeys.util;
import org.jmock.cglib.MockObjectTestCase;
import java.io.*;
/**
* Abstract test class for {@link Object} methods and contracts.
* <p>
* To use, simply extend this class, and implement
* the {@link #makeObject()} method.
* <p>
* If your {@link Object} fails one of these tests by design,
* you may still use this base set of cases. Simply override the
* test case (method) your {@link Object} fails.
*
* @version $Revision: #2 $ $Date: 2008/07/15 $
*
* @author Rodney Waldhoff
* @author Stephen Colebourne
* @author Anonymous
*/
public abstract class ObjectAbstractTestCase extends MockObjectTestCase {
//-----------------------------------------------------------------------
/**
* Implement this method to return the object to test.
*
* @return the object to test
*/
public abstract Object makeObject();
/**
* Override this method if a subclass is testing an object
* that cannot serialize an "empty" Collection.
* (e.g. Comparators have no contents)
*
* @return true
*/
public boolean supportsEmptyCollections() {
return true;
}
/**
* Override this method if a subclass is testing an object
* that cannot serialize a "full" Collection.
* (e.g. Comparators have no contents)
*
* @return true
*/
public boolean supportsFullCollections() {
return true;
}
/**
* Is serialization testing supported.
* Default is true.
*/
public boolean isTestSerialization() {
return true;
}
/**
* Returns true to indicate that the collection supports equals() comparisons.
* This implementation returns true;
*/
public boolean isEqualsCheckable() {
return true;
}
//-----------------------------------------------------------------------
public void testObjectEqualsSelf() {
Object obj = makeObject();
assertEquals("A Object should equal itself", obj, obj);
}
public void testEqualsNull() {
Object obj = makeObject();
assertEquals(false, obj.equals(null)); // make sure this doesn't throw NPE either
}
public void testObjectHashCodeEqualsSelfHashCode() {
Object obj = makeObject();
assertEquals("hashCode should be repeatable", obj.hashCode(), obj.hashCode());
}
public void testObjectHashCodeEqualsContract() {
Object obj1 = makeObject();
if (obj1.equals(obj1)) {
assertEquals(
"[1] When two objects are equal, their hashCodes should be also.",
obj1.hashCode(), obj1.hashCode());
}
Object obj2 = makeObject();
if (obj1.equals(obj2)) {
assertEquals(
"[2] When two objects are equal, their hashCodes should be also.",
obj1.hashCode(), obj2.hashCode());
assertTrue(
"When obj1.equals(obj2) is true, then obj2.equals(obj1) should also be true",
obj2.equals(obj1));
}
}
public void testSerializeDeserializeThenCompare() throws Exception {
Object obj = makeObject();
if (obj instanceof Serializable && isTestSerialization()) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(buffer);
out.writeObject(obj);
out.close();
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray()));
Object dest = in.readObject();
in.close();
if (isEqualsCheckable()) {
assertEquals("obj != deserialize(serialize(obj))", obj, dest);
}
}
}
/**
* Sanity check method, makes sure that any Serializable
* class can be serialized and de-serialized in memory,
* using the handy makeObject() method
*
* @throws java.io.IOException
* @throws ClassNotFoundException
*/
public void testSimpleSerialization() throws Exception {
Object o = makeObject();
if (o instanceof Serializable && isTestSerialization()) {
byte[] objekt = writeExternalFormToBytes((Serializable) o);
Object p = readExternalFormFromBytes(objekt);
}
}
/**
* Tests serialization by comparing against a previously stored version in CVS.
* If the test object is serializable, confirm that a canonical form exists.
*/
public void testCanonicalEmptyCollectionExists() {
if (supportsEmptyCollections() && isTestSerialization() && !skipSerializedCanonicalTests()) {
Object object = makeObject();
if (object instanceof Serializable) {
String name = getCanonicalEmptyCollectionName(object);
assertTrue(
"Canonical empty collection (" + name + ") is not in CVS",
new File(name).exists());
}
}
}
/**
* Tests serialization by comparing against a previously stored version in CVS.
* If the test object is serializable, confirm that a canonical form exists.
*/
public void testCanonicalFullCollectionExists() {
if (supportsFullCollections() && isTestSerialization() && !skipSerializedCanonicalTests()) {
Object object = makeObject();
if (object instanceof Serializable) {
String name = getCanonicalFullCollectionName(object);
assertTrue(
"Canonical full collection (" + name + ") is not in CVS",
new File(name).exists());
}
}
}
// protected implementation
//-----------------------------------------------------------------------
/**
* Get the version of Collections that this object tries to
* maintain serialization compatibility with. Defaults to 1, the
* earliest Collections version. (Note: some collections did not
* even exist in this version).
*
* This constant makes it possible for TestMap (and other subclasses,
* if necessary) to automatically check CVS for a versionX copy of a
* Serialized object, so we can make sure that compatibility is maintained.
* See, for example, TestMap.getCanonicalFullMapName(Map map).
* Subclasses can override this variable, indicating compatibility
* with earlier Collections versions.
*
* @return The version, or {@code null} if this object shouldn't be
* tested for compatibility with previous versions.
*/
public String getCompatibilityVersion() {
return "1";
}
protected String getCanonicalEmptyCollectionName(Object object) {
StringBuilder retval = new StringBuilder();
retval.append("data/test/");
String colName = object.getClass().getName();
colName = colName.substring(colName.lastIndexOf(".") + 1, colName.length());
retval.append(colName);
retval.append(".emptyCollection.version");
retval.append(getCompatibilityVersion());
retval.append(".obj");
return retval.toString();
}
protected String getCanonicalFullCollectionName(Object object) {
StringBuilder retval = new StringBuilder();
retval.append("data/test/");
String colName = object.getClass().getName();
colName = colName.substring(colName.lastIndexOf(".") + 1, colName.length());
retval.append(colName);
retval.append(".fullCollection.version");
retval.append(getCompatibilityVersion());
retval.append(".obj");
return retval.toString();
}
/**
* Write a Serializable or Externalizable object as
* a file at the given path. NOT USEFUL as part
* of a unit test; this is just a utility method
* for creating disk-based objects in CVS that can become
* the basis for compatibility tests using
* readExternalFormFromDisk(String path)
*
* @param o Object to serialize
* @param path path to write the serialized Object
* @exception java.io.IOException
*/
protected void writeExternalFormToDisk(Serializable o, String path) throws IOException {
FileOutputStream fileStream = new FileOutputStream(path);
writeExternalFormToStream(o, fileStream);
}
/**
* Converts a Serializable or Externalizable object to
* bytes. Useful for in-memory tests of serialization
*
* @param o Object to convert to bytes
* @return serialized form of the Object
* @exception java.io.IOException
*/
protected byte[] writeExternalFormToBytes(Serializable o) throws IOException {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
writeExternalFormToStream(o, byteStream);
return byteStream.toByteArray();
}
/**
* Reads a Serialized or Externalized Object from disk.
* Useful for creating compatibility tests between
* different CVS versions of the same class
*
* @param path path to the serialized Object
* @return the Object at the given path
* @exception java.io.IOException
* @exception ClassNotFoundException
*/
protected Object readExternalFormFromDisk(String path) throws IOException, ClassNotFoundException {
FileInputStream stream = new FileInputStream(path);
return readExternalFormFromStream(stream);
}
/**
* Read a Serialized or Externalized Object from bytes.
* Useful for verifying serialization in memory.
*
* @param b byte array containing a serialized Object
* @return Object contained in the bytes
* @exception java.io.IOException
* @exception ClassNotFoundException
*/
protected Object readExternalFormFromBytes(byte[] b) throws IOException, ClassNotFoundException {
ByteArrayInputStream stream = new ByteArrayInputStream(b);
return readExternalFormFromStream(stream);
}
protected boolean skipSerializedCanonicalTests() {
return Boolean.getBoolean("org.apache.commons.collections:with-clover");
}
// private implementation
//-----------------------------------------------------------------------
private Object readExternalFormFromStream(InputStream stream) throws IOException, ClassNotFoundException {
ObjectInputStream oStream = new ObjectInputStream(stream);
return oStream.readObject();
}
private void writeExternalFormToStream(Serializable o, OutputStream stream) throws IOException {
ObjectOutputStream oStream = new ObjectOutputStream(stream);
oStream.writeObject(o);
}
}

View File

@@ -0,0 +1,183 @@
/*
* Copyright 2001-2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.twelvemonkeys.util;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* Abstract test class for {@link Set} methods and contracts.
* <p>
* Since {@link Set} doesn't stipulate much new behavior that isn't already
* found in {@link Collection}, this class basically just adds tests for
* {@link Set#equals} and {@link Set#hashCode()} along with an updated
* {@link #verifyAll()} that ensures elements do not appear more than once in the
* set.
* <p>
* To use, subclass and override the {@link #makeEmptySet()}
* method. You may have to override other protected methods if your
* set is not modifiable, or if your set restricts what kinds of
* elements may be added; see {@link CollectionAbstractTestCase} for more details.
*
* @since Commons Collections 3.0
* @version $Revision: #2 $ $Date: 2008/07/15 $
*
* @author Paul Jack
*/
public abstract class SetAbstractTestCase extends CollectionAbstractTestCase {
//-----------------------------------------------------------------------
/**
* Provides additional verifications for sets.
*/
public void verifyAll() {
super.verifyAll();
assertEquals("Sets should be equal", confirmed, collection);
assertEquals("Sets should have equal hashCodes",
confirmed.hashCode(), collection.hashCode());
Collection set = makeConfirmedCollection();
Iterator iterator = collection.iterator();
while (iterator.hasNext()) {
assertTrue("Set.iterator should only return unique elements",
set.add(iterator.next()));
}
}
//-----------------------------------------------------------------------
/**
* Set equals method is defined.
*/
public boolean isEqualsCheckable() {
return true;
}
/**
* Returns an empty Set for use in modification testing.
*
* @return a confirmed empty collection
*/
public Collection makeConfirmedCollection() {
return new HashSet();
}
/**
* Returns a full Set for use in modification testing.
*
* @return a confirmed full collection
*/
public Collection makeConfirmedFullCollection() {
Collection set = makeConfirmedCollection();
set.addAll(Arrays.asList(getFullElements()));
return set;
}
/**
* Makes an empty set. The returned set should have no elements.
*
* @return an empty set
*/
public abstract Set makeEmptySet();
/**
* Makes a full set by first creating an empty set and then adding
* all the elements returned by {@link #getFullElements()}.
*
* Override if your set does not support the add operation.
*
* @return a full set
*/
public Set makeFullSet() {
Set set = makeEmptySet();
set.addAll(Arrays.asList(getFullElements()));
return set;
}
/**
* Makes an empty collection by invoking {@link #makeEmptySet()}.
*
* @return an empty collection
*/
public final Collection makeCollection() {
return makeEmptySet();
}
/**
* Makes a full collection by invoking {@link #makeFullSet()}.
*
* @return a full collection
*/
public final Collection makeFullCollection() {
return makeFullSet();
}
//-----------------------------------------------------------------------
/**
* Return the {@link CollectionAbstractTestCase#collection} fixture, but cast as a Set.
*/
public Set getSet() {
return (Set)collection;
}
/**
* Return the {@link CollectionAbstractTestCase#confirmed} fixture, but cast as a Set.
*/
public Set getConfirmedSet() {
return (Set)confirmed;
}
//-----------------------------------------------------------------------
/**
* Tests {@link Set#equals(Object)}.
*/
public void testSetEquals() {
resetEmpty();
assertEquals("Empty sets should be equal",
getSet(), getConfirmedSet());
verifyAll();
Collection set2 = makeConfirmedCollection();
set2.add("foo");
assertTrue("Empty set shouldn't equal nonempty set",
!getSet().equals(set2));
resetFull();
assertEquals("Full sets should be equal", getSet(), getConfirmedSet());
verifyAll();
set2.clear();
set2.addAll(Arrays.asList(getOtherElements()));
assertTrue("Sets with different contents shouldn't be equal",
!getSet().equals(set2));
}
/**
* Tests {@link Set#hashCode()}.
*/
public void testSetHashCode() {
resetEmpty();
assertEquals("Empty sets have equal hashCodes",
getSet().hashCode(), getConfirmedSet().hashCode());
resetFull();
assertEquals("Equal sets have equal hashCodes",
getSet().hashCode(), getConfirmedSet().hashCode());
}
}

View File

@@ -0,0 +1,111 @@
package com.twelvemonkeys.util;
import java.util.Iterator;
/**
* StringTokenIteratorTestCase
* <p/>
* <!-- To change this template use Options | File Templates. -->
* <!-- Created by IntelliJ IDEA. -->
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/StringTokenIteratorTestCase.java#1 $
*/
public class StringTokenIteratorTestCase extends TokenIteratorAbstractTestCase {
public void setUp() throws Exception {
super.setUp();
}
public void tearDown() throws Exception {
super.tearDown();
}
protected TokenIterator createTokenIterator(String pString) {
return new StringTokenIterator(pString);
}
protected TokenIterator createTokenIterator(String pString, String pDelimiters) {
return new StringTokenIterator(pString, pDelimiters);
}
public void testEmptyDelimiter() {
Iterator iterator = createTokenIterator("", "");
assertFalse("Empty string has elements", iterator.hasNext());
}
public void testSingleToken() {
Iterator iterator = createTokenIterator("A");
assertTrue("String has no elements", iterator.hasNext());
assertEquals("A", iterator.next());
assertFalse("String has more than one element", iterator.hasNext());
}
public void testSingleTokenEmptyDelimiter() {
Iterator iterator = createTokenIterator("A", "");
assertTrue("String has no elements", iterator.hasNext());
assertEquals("A", iterator.next());
assertFalse("String has more than one element", iterator.hasNext());
}
public void testSingleTokenSingleDelimiter() {
Iterator iterator = createTokenIterator("A", ",");
assertTrue("String has no elements", iterator.hasNext());
assertEquals("A", iterator.next());
assertFalse("String has more than one element", iterator.hasNext());
}
public void testSingleSeparatorDefaultDelimiter() {
Iterator iterator = createTokenIterator("A B C D");
assertTrue("String has no elements", iterator.hasNext());
assertEquals("A", iterator.next());
assertTrue("String has no elements", iterator.hasNext());
assertEquals("B", iterator.next());
assertTrue("String has no elements", iterator.hasNext());
assertEquals("C", iterator.next());
assertTrue("String has no elements", iterator.hasNext());
assertEquals("D", iterator.next());
assertFalse("String has more than one element", iterator.hasNext());
}
public void testSingleSeparator() {
Iterator iterator = createTokenIterator("A,B,C", ",");
assertTrue("String has no elements", iterator.hasNext());
assertEquals("A", iterator.next());
assertTrue("String has no elements", iterator.hasNext());
assertEquals("B", iterator.next());
assertTrue("String has no elements", iterator.hasNext());
assertEquals("C", iterator.next());
assertFalse("String has more than one element", iterator.hasNext());
}
public void testMultipleSeparatorDefaultDelimiter() {
Iterator iterator = createTokenIterator("A B C\nD\t\t \nE");
assertTrue("String has no elements", iterator.hasNext());
assertEquals("A", iterator.next());
assertTrue("String has no elements", iterator.hasNext());
assertEquals("B", iterator.next());
assertTrue("String has no elements", iterator.hasNext());
assertEquals("C", iterator.next());
assertTrue("String has no elements", iterator.hasNext());
assertEquals("D", iterator.next());
assertTrue("String has no elements", iterator.hasNext());
assertEquals("E", iterator.next());
assertFalse("String has more than one element", iterator.hasNext());
}
public void testMultipleSeparator() {
Iterator iterator = createTokenIterator("A,B,;,C...D, ., ,E", " ,.;:");
assertTrue("String has no elements", iterator.hasNext());
assertEquals("A", iterator.next());
assertTrue("String has no elements", iterator.hasNext());
assertEquals("B", iterator.next());
assertTrue("String has no elements", iterator.hasNext());
assertEquals("C", iterator.next());
assertTrue("String has no elements", iterator.hasNext());
assertEquals("D", iterator.next());
assertTrue("String has no elements", iterator.hasNext());
assertEquals("E", iterator.next());
assertFalse("String has more than one element", iterator.hasNext());
}
}

View File

@@ -0,0 +1,632 @@
/****************************************************
* *
* (c) 2000-2003 WM-data *
* All rights reserved *
* http://www.twelvemonkeys.no *
* *
* $RCSfile: TimeoutMapTestCase.java,v $
* @version $Revision: #2 $
* $Date: 2008/07/15 $
* *
* @author Last modified by: $Author: haku $
* *
****************************************************/
package com.twelvemonkeys.util;
import junit.framework.Test;
import junit.framework.TestSuite;
import java.util.*;
/**
* TimeoutMapTestCase
* <p/>
* <!-- To change this template use Options | File Templates. -->
* <!-- Created by IntelliJ IDEA. -->
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/TimeoutMapTestCase.java#2 $
*/
public class TimeoutMapTestCase extends MapAbstractTestCase {
/**
* Method suite
*
* @return
*/
public static Test suite() {
return new TestSuite(TimeoutMapTestCase.class);
}
public Map makeEmptyMap() {
return new TimeoutMap(60 * 60 * 1000);
}
/*
* The basic Map interface lets one associate keys and values:
*/
/**
* Method testBasicMap
*/
public void testBasicMap() {
Map map = new TimeoutMap(60000L);
Object key = "key";
Object value = new Integer(3);
map.put(key, value);
assertEquals(value, map.get(key));
}
/*
* If there is no value associated with a key,
* the basic Map will return null for that key:
*/
/**
* Method testBasicMapReturnsNullForMissingKey
*/
public void testBasicMapReturnsNullForMissingKey() {
Map map = new TimeoutMap(60000L);
assertNull(map.get("key"));
}
/*
* One can also explicitly store a null value for
* some key:
*/
/**
* Method testBasicMapAllowsNull
*/
public void testBasicMapAllowsNull() {
Map map = new TimeoutMap(60000L);
Object key = "key";
Object value = null;
map.put(key, value);
assertNull(map.get(key));
}
/**
* Method testBasicMapAllowsMultipleTypes
*/
public void testBasicMapAllowsMultipleTypes() {
Map map = new TimeoutMap(60000L);
map.put("key-1", "value-1");
map.put(new Integer(2), "value-2");
map.put("key-3", new Integer(3));
map.put(new Integer(4), new Integer(4));
map.put(Boolean.FALSE, "");
assertEquals("value-1", map.get("key-1"));
assertEquals("value-2", map.get(new Integer(2)));
assertEquals(new Integer(3), map.get("key-3"));
assertEquals(new Integer(4), map.get(new Integer(4)));
assertEquals("", map.get(Boolean.FALSE));
}
/**
* Method testBasicMapStoresOnlyOneValuePerKey
*/
public void testBasicMapStoresOnlyOneValuePerKey() {
Map map = new TimeoutMap(60000L);
assertNull(map.put("key", "value-1"));
assertEquals("value-1", map.get("key"));
assertEquals("value-1", map.put("key", "value-2"));
assertEquals("value-2", map.get("key"));
}
/**
* Method testBasicMapValuesView
*/
public void testBasicMapValuesView() {
Map map = new TimeoutMap(60000L);
assertNull(map.put("key-1", new Integer(1)));
assertNull(map.put("key-2", new Integer(2)));
assertNull(map.put("key-3", new Integer(3)));
assertNull(map.put("key-4", new Integer(4)));
assertEquals(4, map.size());
Collection values = map.values();
assertEquals(4, values.size());
Iterator it = values.iterator();
assertNotNull(it);
int count = 0;
while (it.hasNext()) {
Object o = it.next();
assertNotNull(o);
assertTrue(o instanceof Integer);
count++;
}
assertEquals(4, count);
}
/**
* Method testBasicMapKeySetView
*/
public void testBasicMapKeySetView() {
Map map = new TimeoutMap(60000L);
assertNull(map.put("key-1", "value-1"));
assertNull(map.put("key-2", "value-2"));
assertNull(map.put("key-3", "value-3"));
assertNull(map.put("key-4", "value-4"));
assertEquals(4, map.size());
Iterator it = map.keySet().iterator();
assertNotNull(it);
int count = 0;
while (it.hasNext()) {
Object o = it.next();
assertNotNull(o);
assertTrue(o instanceof String);
count++;
}
assertEquals(4, count);
}
/**
* Method testBasicMapEntrySetView
*/
public void testBasicMapEntrySetView() {
Map map = new TimeoutMap(60000L);
assertNull(map.put("key-1", new Integer(1)));
assertNull(map.put("key-2", "value-2"));
assertNull(map.put("key-3", new Object()));
assertNull(map.put("key-4", Boolean.FALSE));
assertEquals(4, map.size());
Iterator it = map.entrySet().iterator();
assertNotNull(it);
int count = 0;
while (it.hasNext()) {
Object o = it.next();
assertNotNull(o);
assertTrue(o instanceof Map.Entry);
count++;
}
assertEquals(4, count);
}
/**
* Method testBasicMapValuesView
*/
public void testBasicMapValuesViewRemoval() {
Map map = new TimeoutMap(60000L);
assertNull(map.put("key-1", new Integer(1)));
assertNull(map.put("key-2", new Integer(2)));
assertNull(map.put("key-3", new Integer(3)));
assertNull(map.put("key-4", new Integer(4)));
assertEquals(4, map.size());
Iterator it = map.values().iterator();
assertNotNull(it);
int count = 0;
while (it.hasNext()) {
Object o = it.next();
assertNotNull(o);
assertTrue(o instanceof Integer);
try {
it.remove();
}
catch (UnsupportedOperationException e) {
fail("Removal failed");
}
count++;
}
assertEquals(4, count);
}
/**
* Method testBasicMapKeySetView
*/
public void testBasicMapKeySetViewRemoval() {
Map map = new TimeoutMap(60000L);
assertNull(map.put("key-1", "value-1"));
assertNull(map.put("key-2", "value-2"));
assertNull(map.put("key-3", "value-3"));
assertNull(map.put("key-4", "value-4"));
assertEquals(4, map.size());
Iterator it = map.keySet().iterator();
assertNotNull(it);
int count = 0;
while (it.hasNext()) {
Object o = it.next();
assertNotNull(o);
assertTrue(o instanceof String);
try {
it.remove();
}
catch (UnsupportedOperationException e) {
fail("Removal failed");
}
count++;
}
assertEquals(4, count);
}
/**
* Method testBasicMapEntrySetView
*/
public void testBasicMapEntrySetViewRemoval() {
Map map = new TimeoutMap(60000L);
assertNull(map.put("key-1", new Integer(1)));
assertNull(map.put("key-2", "value-2"));
assertNull(map.put("key-3", new Object()));
assertNull(map.put("key-4", Boolean.FALSE));
assertEquals(4, map.size());
Iterator it = map.entrySet().iterator();
assertNotNull(it);
int count = 0;
while (it.hasNext()) {
Object o = it.next();
assertNotNull(o);
assertTrue(o instanceof Map.Entry);
try {
it.remove();
}
catch (UnsupportedOperationException e) {
fail("Removal failed");
}
count++;
}
assertEquals(4, count);
}
/**
* Method testBasicMapStoresOnlyOneValuePerKey
*/
public void testTimeoutReturnNull() {
Map map = new TimeoutMap(100L);
assertNull(map.put("key", "value-1"));
assertEquals("value-1", map.get("key"));
assertNull(map.put("another", "value-2"));
assertEquals("value-2", map.get("another"));
synchronized (this) {
try {
Thread.sleep(110L);
}
catch (InterruptedException e) {
// Continue, but might break the timeout thing below...
}
}
// Values should now time out
assertNull(map.get("key"));
assertNull(map.get("another"));
}
/**
* Method testTimeoutIsEmpty
*/
public void testTimeoutIsEmpty() {
TimeoutMap map = new TimeoutMap(50L);
assertNull(map.put("key", "value-1"));
assertEquals("value-1", map.get("key"));
assertNull(map.put("another", "value-2"));
assertEquals("value-2", map.get("another"));
synchronized (this) {
try {
Thread.sleep(100L);
}
catch (InterruptedException e) {
// Continue, but might break the timeout thing below...
}
}
// This for loop should not print anything, if the tests succeed.
Set set = map.keySet();
assertEquals(0, set.size());
for (Iterator iterator = set.iterator(); iterator.hasNext(); System.out.println(iterator.next())) {
;
}
assertEquals(0, map.size());
assertTrue(map.isEmpty());
}
/**
* Method testTimeoutWrapIsEmpty
*/
public void testTimeoutWrapIsEmpty() {
Map map = new TimeoutMap(new LRUMap(2), null, 100L);
assertNull(map.put("key", "value-1"));
assertEquals("value-1", map.get("key"));
assertNull(map.put("another", "value-2"));
assertEquals("value-2", map.get("another"));
assertNull(map.put("third", "value-3"));
assertEquals("value-3", map.get("third"));
synchronized (this) {
try {
Thread.sleep(110L);
}
catch (InterruptedException e) {
// Continue, but might break the timeout thing below...
}
}
// This for loop should not print anything, if the tests succeed.
Set set = map.keySet();
assertEquals(0, set.size());
for (Iterator iterator = set.iterator(); iterator.hasNext(); System.out.println(iterator.next())) {
;
}
assertEquals(0, map.size());
assertTrue(map.isEmpty());
}
/**
* Method testTimeoutWrapReturnNull
*/
public void testTimeoutWrapReturnNull() {
Map map = new TimeoutMap(new LRUMap(), null, 100L);
assertNull(map.put("key", "value-1"));
assertEquals("value-1", map.get("key"));
assertNull(map.put("another", "value-2"));
assertEquals("value-2", map.get("another"));
synchronized (this) {
try {
Thread.sleep(110L);
}
catch (InterruptedException e) {
// Continue, but might break the timeout thing below...
}
}
// Values should now time out
assertNull(map.get("key"));
assertNull(map.get("another"));
}
/**
* Method testWrapMaxSize
*/
public void testWrapMaxSize() {
LRUMap lru = new LRUMap();
lru.setMaxSize(2);
TimeoutMap map = new TimeoutMap(lru, null, 1000L);
assertNull(map.put("key", "value-1"));
assertEquals("value-1", map.get("key"));
assertNull(map.put("another", "value-2"));
assertEquals("value-2", map.get("another"));
assertNull(map.put("third", "value-3"));
assertEquals("value-3", map.get("third"));
// This value should have expired
assertNull(map.get("key"));
// These should be left
assertEquals("value-2", map.get("another"));
assertEquals("value-3", map.get("third"));
}
/**
* Method testWrapMapContainingValues
*/
public void testWrapMapContainingValues() {
Map backing = new TreeMap();
backing.put("key", "original");
TimeoutMap map = null;
try {
map = new TimeoutMap(backing, backing, 1000L);
Object value = map.put("key", "value-1");
assertNotNull(value); // Should now have value!
assertEquals("original", value);
}
catch (ClassCastException cce) {
cce.printStackTrace();
fail("Content not converted to TimedEntries properly!");
}
assertEquals("value-1", map.get("key"));
assertNull(map.put("another", "value-2"));
assertEquals("value-2", map.get("another"));
assertNull(map.put("third", "value-3"));
assertEquals("value-3", map.get("third"));
}
public void testIteratorRemove() {
Map map = makeFullMap();
for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
iterator.remove();
}
assertEquals(0, map.size());
map = makeFullMap();
for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
iterator.next();
iterator.remove();
}
assertEquals(0, map.size());
}
public void testIteratorPredictableNext() {
TimeoutMap map = (TimeoutMap) makeFullMap();
map.setExpiryTime(50l);
assertFalse(map.isEmpty());
int count = 0;
for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
if (count == 0) {
// NOTE: Only wait fist time, to avoid slooow tests
synchronized (this) {
try {
wait(60l);
}
catch (InterruptedException e) {
}
}
}
try {
Map.Entry entry = (Map.Entry) iterator.next();
assertNotNull(entry);
count++;
}
catch (NoSuchElementException nse) {
fail("Elements expire between Interator.hasNext() and Iterator.next()");
}
}
assertTrue("Elements expired too early, test did not run as expected.", count > 0);
//assertEquals("Elements did not expire as expected.", 1, count);
}
public void testIteratorPredictableRemove() {
TimeoutMap map = (TimeoutMap) makeFullMap();
map.setExpiryTime(50l);
assertFalse(map.isEmpty());
int count = 0;
for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
if (count == 0) {
// NOTE: Only wait fist time, to avoid slooow tests
synchronized (this) {
try {
wait(60l);
}
catch (InterruptedException e) {
}
}
}
try {
iterator.remove();
count++;
}
catch (NoSuchElementException nse) {
fail("Elements expired between Interator.hasNext() and Iterator.remove()");
}
}
assertTrue("Elements expired too early, test did not run as expected.", count > 0);
//assertEquals("Elements did not expire as expected.", 1, count);
}
public void testIteratorPredictableNextRemove() {
TimeoutMap map = (TimeoutMap) makeFullMap();
map.setExpiryTime(50l);
assertFalse(map.isEmpty());
int count = 0;
for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
if (count == 0) {
// NOTE: Only wait fist time, to avoid slooow tests
synchronized (this) {
try {
wait(60l);
}
catch (InterruptedException e) {
}
}
}
try {
Map.Entry entry = (Map.Entry) iterator.next();
assertNotNull(entry);
}
catch (NoSuchElementException nse) {
fail("Elements expired between Interator.hasNext() and Iterator.next()");
}
try {
iterator.remove();
count++;
}
catch (NoSuchElementException nse) {
fail("Elements expired between Interator.hasNext() and Iterator.remove()");
}
}
assertTrue("Elements expired too early, test did not run as expected.", count > 0);
//assertEquals("Elements did not expire as expected.", 1, count);
}
public void testIteratorPredictableRemovedEntry() {
TimeoutMap map = (TimeoutMap) makeEmptyMap();
map.setExpiryTime(1000l); // No elements should expire during this test
map.put("key-1", new Integer(1));
map.put("key-2", new Integer(2));
assertFalse(map.isEmpty());
Object removedKey = null;
Object otherKey = null;
Iterator iterator = map.entrySet().iterator();
assertTrue("Iterator was empty", iterator.hasNext());
try {
Map.Entry entry = (Map.Entry) iterator.next();
assertNotNull(entry);
removedKey = entry.getKey();
otherKey = "key-1".equals(removedKey) ? "key-2" : "key-1";
}
catch (NoSuchElementException nse) {
fail("Elements expired between Interator.hasNext() and Iterator.next()");
}
try {
iterator.remove();
}
catch (NoSuchElementException nse) {
fail("Elements expired between Interator.hasNext() and Iterator.remove()");
}
assertTrue("Wrong entry removed, keySet().iterator() is broken.", !map.containsKey(removedKey));
assertTrue("Wrong entry removed, keySet().iterator() is broken.", map.containsKey(otherKey));
}
}

View File

@@ -0,0 +1,52 @@
package com.twelvemonkeys.util;
import junit.framework.TestCase;
import java.util.Iterator;
/**
* TokenIteratorAbstractTestCase
* <p/>
* <!-- To change this template use Options | File Templates. -->
* <!-- Created by IntelliJ IDEA. -->
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/TokenIteratorAbstractTestCase.java#1 $
*/
public abstract class TokenIteratorAbstractTestCase extends TestCase {
protected abstract TokenIterator createTokenIterator(String pString);
protected abstract TokenIterator createTokenIterator(String pString, String pDelimiters);
public void testNullString() {
try {
createTokenIterator(null);
fail("Null string parameter not allowed");
}
catch (IllegalArgumentException e) {
// okay!
}
catch (Throwable t) {
fail(t.getMessage());
}
}
public void testNullDelimmiter() {
try {
createTokenIterator("", null);
fail("Null delimiter parameter not allowed");
}
catch (IllegalArgumentException e) {
// okay!
}
catch (Throwable t) {
fail(t.getMessage());
}
}
public void testEmptyString() {
Iterator iterator = createTokenIterator("");
assertFalse("Empty string has elements", iterator.hasNext());
}
}

View File

@@ -0,0 +1,18 @@
package com.twelvemonkeys.util.convert;
import junit.framework.TestCase;
/**
* ConverterTestCase
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/convert/ConverterTestCase.java#1 $
*/
public class ConverterTestCase extends TestCase {
public void testMe() {
// TODO: Implement tests
}
}

View File

@@ -0,0 +1,58 @@
package com.twelvemonkeys.util.convert;
import com.twelvemonkeys.lang.DateUtil;
import java.text.DateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
/**
* DateConverterTestCase
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/convert/DateConverterTestCase.java#2 $
*/
public class DateConverterTestCase extends PropertyConverterAbstractTestCase {
protected final static String FORMAT_STR_1 = "dd.MM.yyyy HH:mm:ss";
protected final static String FORMAT_STR_2 = "dd-MM-yyyy hh:mm:ss a";
protected PropertyConverter makePropertyConverter() {
return new DateConverter();
}
protected Conversion[] getTestConversions() {
// The default format doesn't contain milliseconds, so we have to round
long time = System.currentTimeMillis();
final Date now = new Date(DateUtil.roundToSecond(time));
DateFormat df = DateFormat.getDateTimeInstance();
return new Conversion[] {
new Conversion("01.11.2006 15:26:23", new GregorianCalendar(2006, 10, 1, 15, 26, 23).getTime(), FORMAT_STR_1),
// This doesn't really work.. But close enough
new Conversion(df.format(now), now),
// This format is really stupid
new Conversion("01-11-2006 03:27:44 pm", new GregorianCalendar(2006, 10, 1, 15, 27, 44).getTime(), FORMAT_STR_2, "01-11-2006 03:27:44 PM"),
// These seems to be an hour off (no timezone?)...
new Conversion("42", new Date(42l), "S"),
new Conversion(String.valueOf(time % 1000l), new Date(time % 1000l), "S"),
};
}
@Override
public void testConvert() {
TimeZone old = TimeZone.getDefault();
try {
TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
super.testConvert();
}
finally {
TimeZone.setDefault(old);
}
}
}

View File

@@ -0,0 +1,71 @@
package com.twelvemonkeys.util.convert;
/**
* DefaultConverterTestCase
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/convert/DefaultConverterTestCase.java#1 $
*/
public class DefaultConverterTestCase extends PropertyConverterAbstractTestCase {
protected PropertyConverter makePropertyConverter() {
return new DefaultConverter();
}
protected Conversion[] getTestConversions() {
//noinspection BooleanConstructorCall
return new Conversion[] {
// Booleans
new Conversion("true", Boolean.TRUE),
new Conversion("TRUE", Boolean.TRUE, null, "true"),
new Conversion("false", Boolean.FALSE),
new Conversion("FALSE", new Boolean(false), null, "false"),
// Stupid but valid
new Conversion("fooBar", "fooBar"),
//new Conversion("fooBar", new StringBuilder("fooBar")), - StringBuilder does not impl equals()...
// Stupid test class that reveres chars
new Conversion("fooBar", new FooBar("fooBar")),
// TODO: More tests
};
}
// TODO: Test boolean -> Boolean conversion
public static class FooBar {
private final String mBar;
public FooBar(String pFoo) {
if (pFoo == null) {
throw new IllegalArgumentException("pFoo == null");
}
mBar = reverse(pFoo);
}
private String reverse(String pFoo) {
StringBuilder buffer = new StringBuilder(pFoo.length());
for (int i = pFoo.length() - 1; i >= 0; i--) {
buffer.append(pFoo.charAt(i));
}
return buffer.toString();
}
public String toString() {
return reverse(mBar);
}
public boolean equals(Object obj) {
return obj == this || (obj instanceof FooBar && ((FooBar) obj).mBar.equals(mBar));
}
public int hashCode() {
return 7 * mBar.hashCode();
}
}
}

View File

@@ -0,0 +1,42 @@
package com.twelvemonkeys.util.convert;
/**
* NumberConverterTestCase
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/convert/NumberConverterTestCase.java#2 $
*/
public class NumberConverterTestCase extends PropertyConverterAbstractTestCase {
protected PropertyConverter makePropertyConverter() {
return new NumberConverter();
}
protected Conversion[] getTestConversions() {
return new Conversion[] {
new Conversion("0", 0),
new Conversion("1", 1),
new Conversion("-1001", -1001),
new Conversion("1E3", 1000, null, "1000"),
new Conversion("-2", -2l),
new Conversion("2000651651854", 2000651651854l),
new Conversion("2E10", 20000000000l, null, "20000000000"),
new Conversion("3", 3.0f),
new Conversion("3.1", 3.1f),
new Conversion("3.2", 3.2f, "#.#"),
//new Conversion("3,3", new Float(3), "#", "3"), // Seems to need parseIntegerOnly
new Conversion("-3.4", -3.4f),
new Conversion("-3.5E10", -3.5e10f, null, "-35000000512"),
new Conversion("4", 4.0),
new Conversion("4.1", 4.1),
new Conversion("4.2", 4.2, "#.#"),
//new Conversion("4,3", new Double(4), "#", "4"), // Seems to need parseIntegerOnly
new Conversion("-4.4", -4.4),
new Conversion("-4.5E97", -4.5e97, null, "-45000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
};
}
}

View File

@@ -0,0 +1,99 @@
package com.twelvemonkeys.util.convert;
import com.twelvemonkeys.lang.ObjectAbstractTestCase;
/**
* PropertyConverterAbstractTestCase
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/convert/PropertyConverterAbstractTestCase.java#2 $
*/
public abstract class PropertyConverterAbstractTestCase extends ObjectAbstractTestCase {
protected Object makeObject() {
return makePropertyConverter();
}
protected abstract PropertyConverter makePropertyConverter();
protected abstract Conversion[] getTestConversions();
public void testConvert() {
PropertyConverter converter = makePropertyConverter();
Conversion[] tests = getTestConversions();
for (Conversion test : tests) {
Object obj;
try {
obj = converter.toObject(test.original(), test.type(), test.format());
assertEquals("'" + test.original() + "' convtered to incorrect type", test.type(), obj.getClass());
assertEquals("'" + test.original() + "' not converted", test.value(), obj);
String result = converter.toString(test.value(), test.format());
assertEquals("'" + test.converted() + "' does not macth", test.converted(), result);
obj = converter.toObject(result, test.type(), test.format());
assertEquals("'" + test.original() + "' convtered to incorrect type", test.type(), obj.getClass());
assertEquals("'" + test.original() + "' did not survive roundrip conversion", test.value(), obj);
}
catch (ConversionException e) {
e.printStackTrace();
fail("Converting '" + test.original() + "' to " + test.type() + " failed: " + e.getMessage());
}
}
}
public static final class Conversion {
private final String mStrVal;
private final Object mObjVal;
private final String mFormat;
private final String mConvertedStrVal;
public Conversion(String pStrVal, Object pObjVal) {
this(pStrVal, pObjVal, null, null);
}
public Conversion(String pStrVal, Object pObjVal, String pFormat) {
this(pStrVal, pObjVal, pFormat, null);
}
public Conversion(String pStrVal, Object pObjVal, String pFormat, String pConvertedStrVal) {
if (pStrVal == null) {
throw new IllegalArgumentException("pStrVal == null");
}
if (pObjVal == null) {
throw new IllegalArgumentException("pObjVal == null");
}
mStrVal = pStrVal;
mObjVal = pObjVal;
mFormat = pFormat;
mConvertedStrVal = pConvertedStrVal != null ? pConvertedStrVal : pStrVal;
}
public String original() {
return mStrVal;
}
public Object value() {
return mObjVal;
}
public Class type() {
return mObjVal.getClass();
}
public String format() {
return mFormat;
}
public String converted() {
return mConvertedStrVal;
}
}
}

View File

@@ -0,0 +1,19 @@
package com.twelvemonkeys.util.convert;
/**
* TimeConverterTestCase
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/convert/TimeConverterTestCase.java#1 $
*/
public class TimeConverterTestCase extends PropertyConverterAbstractTestCase {
protected PropertyConverter makePropertyConverter() {
return new TimeConverter();
}
protected Conversion[] getTestConversions() {
return new Conversion[0];// TODO: Implement
}
}

View File

@@ -0,0 +1,122 @@
package com.twelvemonkeys.util.regex;
import com.twelvemonkeys.util.TokenIterator;
import com.twelvemonkeys.util.TokenIteratorAbstractTestCase;
import java.util.Iterator;
/**
* StringTokenIteratorTestCase
* <p/>
* <!-- To change this template use Options | File Templates. -->
* <!-- Created by IntelliJ IDEA. -->
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/util/regex/RegExTokenIteratorTestCase.java#1 $
*/
public class RegExTokenIteratorTestCase extends TokenIteratorAbstractTestCase {
public void setUp() throws Exception {
super.setUp();
}
public void tearDown() throws Exception {
super.tearDown();
}
protected TokenIterator createTokenIterator(String pString) {
return new RegExTokenIterator(pString);
}
protected TokenIterator createTokenIterator(String pString, String pDelimiters) {
return new RegExTokenIterator(pString, pDelimiters);
}
public void testEmptyDelimiter() {
// TODO: What is it supposed to match?
/*
Iterator iterator = createTokenIterator("", ".*");
assertTrue("Empty string has no elements", iterator.hasNext());
iterator.next();
assertFalse("Empty string has more then one element", iterator.hasNext());
*/
}
public void testSingleToken() {
Iterator iterator = createTokenIterator("A");
assertTrue("String has no elements", iterator.hasNext());
assertEquals("A", iterator.next());
assertFalse("String has more than one element", iterator.hasNext());
}
public void testSingleTokenEmptyDelimiter() {
// TODO: What is it supposed to match?
/*
Iterator iterator = createTokenIterator("A", ".*");
assertTrue("String has no elements", iterator.hasNext());
assertEquals("A", iterator.next());
assertFalse("String has more than one element", iterator.hasNext());
*/
}
public void testSingleTokenSingleDelimiter() {
Iterator iterator = createTokenIterator("A", "[^,]+");
assertTrue("String has no elements", iterator.hasNext());
assertEquals("A", iterator.next());
assertFalse("String has more than one element", iterator.hasNext());
}
public void testSingleSeparatorDefaultDelimiter() {
Iterator iterator = createTokenIterator("A B C D");
assertTrue("String has no elements", iterator.hasNext());
assertEquals("A", iterator.next());
assertTrue("String has no elements", iterator.hasNext());
assertEquals("B", iterator.next());
assertTrue("String has no elements", iterator.hasNext());
assertEquals("C", iterator.next());
assertTrue("String has no elements", iterator.hasNext());
assertEquals("D", iterator.next());
assertFalse("String has more than one element", iterator.hasNext());
}
public void testSingleSeparator() {
Iterator iterator = createTokenIterator("A,B,C", "[^,]+");
assertTrue("String has no elements", iterator.hasNext());
assertEquals("A", iterator.next());
assertTrue("String has no elements", iterator.hasNext());
assertEquals("B", iterator.next());
assertTrue("String has no elements", iterator.hasNext());
assertEquals("C", iterator.next());
assertFalse("String has more than one element", iterator.hasNext());
}
public void testMultipleSeparatorDefaultDelimiter() {
Iterator iterator = createTokenIterator("A B C\nD\t\t \nE");
assertTrue("String has no elements", iterator.hasNext());
assertEquals("A", iterator.next());
assertTrue("String has no elements", iterator.hasNext());
assertEquals("B", iterator.next());
assertTrue("String has no elements", iterator.hasNext());
assertEquals("C", iterator.next());
assertTrue("String has no elements", iterator.hasNext());
assertEquals("D", iterator.next());
assertTrue("String has no elements", iterator.hasNext());
assertEquals("E", iterator.next());
assertFalse("String has more than one element", iterator.hasNext());
}
public void testMultipleSeparator() {
Iterator iterator = createTokenIterator("A,B,;,C...D, ., ,E", "[^ ,.;:]+");
assertTrue("String has no elements", iterator.hasNext());
Object o = iterator.next();
assertEquals("A", o);
assertTrue("String has no elements", iterator.hasNext());
assertEquals("B", iterator.next());
assertTrue("String has no elements", iterator.hasNext());
assertEquals("C", iterator.next());
assertTrue("String has no elements", iterator.hasNext());
assertEquals("D", iterator.next());
assertTrue("String has no elements", iterator.hasNext());
assertEquals("E", iterator.next());
assertFalse("String has more than one element", iterator.hasNext());
}
}