/*******************************************************************************
* Copyright (c) 2009 Fraunhofer IWU and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Fraunhofer IWU - initial API and implementation
*******************************************************************************/
package net.enilink.commons.util;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.IdentityHashMap;
import java.util.Map;
/**
*
* @author Ken Wenzel
*/
public class ValueUtils {
protected Map<Class<?>, ValueType> typeMap = new IdentityHashMap<Class<?>, ValueType>(
16);
protected Map<Class<?>, Object> defaultValues = new IdentityHashMap<Class<?>, Object>(
16);
private static ValueUtils instance = null;
protected ValueUtils() {
typeMap.put(Boolean.class, ValueType.BOOLEAN);
typeMap.put(Boolean.TYPE, ValueType.BOOLEAN);
typeMap.put(Byte.class, ValueType.BYTE);
typeMap.put(Byte.TYPE, ValueType.BYTE);
typeMap.put(Short.class, ValueType.SHORT);
typeMap.put(Short.TYPE, ValueType.SHORT);
typeMap.put(Integer.class, ValueType.INTEGER);
typeMap.put(Integer.TYPE, ValueType.INTEGER);
typeMap.put(Long.class, ValueType.LONG);
typeMap.put(Long.TYPE, ValueType.LONG);
typeMap.put(BigInteger.class, ValueType.BIGINTEGER);
typeMap.put(Float.class, ValueType.FLOAT);
typeMap.put(Float.TYPE, ValueType.FLOAT);
typeMap.put(Double.class, ValueType.DOUBLE);
typeMap.put(Double.TYPE, ValueType.DOUBLE);
typeMap.put(BigDecimal.class, ValueType.BIGDECIMAL);
defaultValues.put(Boolean.TYPE, Boolean.FALSE);
defaultValues.put(Boolean.class, Boolean.FALSE);
defaultValues.put(Byte.TYPE, new Byte((byte) 0));
defaultValues.put(Byte.class, new Byte((byte) 0));
defaultValues.put(Character.TYPE, new Character((char) 0));
defaultValues.put(Character.class, new Character((char) 0));
defaultValues.put(Short.TYPE, new Short((short) 0));
defaultValues.put(Short.class, new Short((short) 0));
defaultValues.put(Integer.TYPE, new Integer(0));
defaultValues.put(Integer.class, new Integer(0));
defaultValues.put(Long.TYPE, new Long(0L));
defaultValues.put(Long.class, new Long(0L));
defaultValues.put(Float.TYPE, new Float(0.0f));
defaultValues.put(Float.class, new Float(0.0f));
defaultValues.put(Double.TYPE, new Double(0.0));
defaultValues.put(Double.class, new Double(0.0));
}
public static synchronized ValueUtils getInstance() {
if (instance == null)
instance = new ValueUtils();
return instance;
}
public ValueType getType(Object value) {
ValueType result = ValueType.ANY;
if (value != null) {
Class<?> c = value.getClass();
result = typeMap.get(c);
if (result == null)
result = ValueType.ANY;
}
return result;
}
/**
* Returns the constant from the NumericTypes interface that best expresses
* the type of an operation, which can be either numeric or not, on the two
* given types.
*
* @param t1
* one argument type to an operator
* @param t2
* the other argument type
* @param canBeNonNumeric
* whether the operator can be interpreted as non-numeric
*
* @return the appropriate constant from the NumericTypes interface
*/
public ValueType getNumericType(ValueType t1, ValueType t2,
boolean canBeNonNumeric) {
if (t1 == t2) {
return t1;
}
if (canBeNonNumeric
&& ((t1 == ValueType.ANY) || (t2 == ValueType.ANY)
|| (t1 == ValueType.CHARACTER) || (t2 == ValueType.CHARACTER))) {
return ValueType.ANY;
}
if (t1 == ValueType.ANY) {
t1 = ValueType.DOUBLE; // Try to interpret strings as doubles...
}
if (t2 == ValueType.ANY) {
t2 = ValueType.DOUBLE; // Try to interpret strings as doubles...
}
if (t1.compareTo(ValueType.FLOAT) >= 0) {
if (t2.compareTo(ValueType.FLOAT) >= 0) {
return ValueType.max(t1, t2);
}
if (t2.compareTo(ValueType.INTEGER) < 0) {
return t1;
}
if (t2 == ValueType.BIGINTEGER) {
return ValueType.BIGDECIMAL;
}
return ValueType.max(ValueType.DOUBLE, t2);
} else if (t2.compareTo(ValueType.FLOAT) >= 0) {
if (t1.compareTo(ValueType.INTEGER) < 0) {
return t2;
}
if (t1 == ValueType.BIGINTEGER) {
return ValueType.BIGDECIMAL;
}
return ValueType.max(ValueType.DOUBLE, t2);
} else {
return ValueType.max(t1, t2);
}
}
public int compareWithConversion(Object v1, Object v2) {
return compareWithConversion(v1, v2, null);
}
@SuppressWarnings("unchecked")
public int compareWithConversion(Object v1, Object v2, Number epsilon) {
int result;
if (v1 == v2) {
result = 0;
} else {
ValueType t1 = getType(v1), t2 = getType(v2), type = getNumericType(
t1, t2, true);
switch (type) {
case BIGINTEGER:
result = bigIntValue(v1).compareTo(bigIntValue(v2));
break;
case BIGDECIMAL:
result = bigDecValue(v1).compareTo(bigDecValue(v2));
break;
case ANY:
if ((t1 == ValueType.ANY) && (t2 == ValueType.ANY)) {
if (v1 instanceof Comparable) {
result = ((Comparable) v1).compareTo(v2);
break;
} else {
throw new RuntimeException("invalid comparison: " + v1
+ " <-> " + v2);
}
}
// else fall through
case FLOAT:
case DOUBLE:
double dv1 = doubleValue(v1),
dv2 = doubleValue(v2);
if (epsilon != null) {
double diff = Math.abs(dv1 - dv2);
result = (diff < Math.abs(epsilon.doubleValue())) ? 0
: ((dv1 < dv2) ? -1 : 1);
} else {
result = (dv1 == dv2) ? 0 : ((dv1 < dv2) ? -1 : 1);
}
break;
default:
long lv1 = longValue(v1),
lv2 = longValue(v2);
result = (lv1 == lv2) ? 0 : ((lv1 < lv2) ? -1 : 1);
break;
}
}
return result;
}
/**
* Evaluates the given object as a String and trims it if the trim flag is
* true.
*
* @param value
* an object to interpret as a String
* @param trim
* if true this returned string value is trimmed of whitespace
* using String.trim().
*
* @return the String value implied by the given object as returned by the
* toString() method, or "null" if the object is null.
*/
public String stringValue(Object value, boolean trim) {
String result;
if (value == null) {
result = "";
} else {
result = value.toString();
if (trim) {
result = result.trim();
}
}
return result;
}
/**
* Evaluates the given object as a long integer.
*
* @param value
* an object to interpret as a long integer
*
* @return the long integer value implied by the given object
*
* @throws NumberFormatException
* if the given object can't be understood as a long integer
*/
public long longValue(Object value) throws NumberFormatException {
if (value == null)
return 0;
Class<?> c = value.getClass();
if (c.getSuperclass() == Number.class) {
return ((Number) value).longValue();
}
if (c == Character.class) {
return ((Character) value).charValue();
}
if (c == Boolean.class) {
return ((Boolean) value).booleanValue() ? 1 : 0;
}
return Long.parseLong(stringValue(value, true));
}
/**
* Evaluates the given object as a double-precision floating-point number.
*
* @param value
* an object to interpret as a double
*
* @return the double value implied by the given object
*
* @throws NumberFormatException
* if the given object can't be understood as a double
*/
public double doubleValue(Object value) throws NumberFormatException {
if (value == null)
return 0.0;
Class<?> c = value.getClass();
if (c.getSuperclass() == Number.class) {
return ((Number) value).doubleValue();
}
if (c == Character.class) {
return ((Character) value).charValue();
}
if (c == Boolean.class) {
return ((Boolean) value).booleanValue() ? 1 : 0;
}
String s = stringValue(value, true);
return (s.length() == 0) ? 0.0 : Double.parseDouble(s);
}
/**
* Evaluates the given object as a BigDecimal.
*
* @param value
* an object to interpret as a BigDecimal
*
* @return the BigDecimal value implied by the given object
*
* @throws NumberFormatException
* if the given object can't be understood as a BigDecimal
*/
public BigDecimal bigDecValue(Object value) throws NumberFormatException {
if (value == null)
return new BigDecimal(0);
Class<?> c = value.getClass();
if (c == BigDecimal.class) {
return (BigDecimal) value;
}
if (c == BigInteger.class) {
return new BigDecimal((BigInteger) value);
}
if (c.getSuperclass() == Number.class) {
return new BigDecimal(((Number) value).doubleValue());
}
if (c == Character.class) {
return BigDecimal.valueOf(((Character) value).charValue());
}
if (c == Boolean.class) {
return BigDecimal.valueOf(((Boolean) value).booleanValue() ? 1 : 0);
}
return new BigDecimal(stringValue(value, true));
}
/**
* Evaluates the given object as a BigInteger.
*
* @param value
* an object to interpret as a BigInteger
*
* @return the BigInteger value implied by the given object
*
* @throws NumberFormatException
* if the given object can't be understood as a BigInteger
*/
public BigInteger bigIntValue(Object value) throws NumberFormatException {
if (value == null)
return new BigInteger(new byte[] { 0 });
Class<?> c = value.getClass();
if (c == BigInteger.class) {
return (BigInteger) value;
}
if (c == BigDecimal.class) {
return ((BigDecimal) value).toBigInteger();
}
if (c.getSuperclass() == Number.class) {
return BigInteger.valueOf(((Number) value).longValue());
}
if (c == Character.class) {
return BigInteger.valueOf(((Character) value).charValue());
}
if (c == Boolean.class) {
return BigInteger.valueOf(((Boolean) value).booleanValue() ? 1 : 0);
}
return new BigInteger(stringValue(value, true));
}
/*
* FIXME: check for String-conversion! old - String set: true, String empty:
* false new - String set and "true" (case insensitive): true, else: false
*/
public Boolean booleanValue(Object value) {
if (value == null)
return Boolean.FALSE;
if ((value instanceof Boolean) && Boolean.FALSE.equals(value)
|| (value instanceof Number)
&& ((Number) value).intValue() == 0
|| (value instanceof String)
&& !Boolean.parseBoolean((String) value)
|| (value instanceof Character)
&& ((Character) value).charValue() == 0) {
return Boolean.FALSE;
}
return Boolean.TRUE;
}
/**
* Adds the two objects given. For numeric values this will add them
* numerically; for one of the arguments being non-numeric this will
* catenate them as Strings.
*
* @param v1
* First addend
* @param v2
* Second addend
*
* @return v1 + v2 or concatenated String
*/
public Object add(Object v1, Object v2) {
ValueType t1 = getType(v1), t2 = getType(v2);
ValueType type = getNumericType(t1, t2, true);
switch (type) {
case BIGINTEGER:
return bigIntValue(v1).add(bigIntValue(v2));
case BIGDECIMAL:
return bigDecValue(v1).add(bigDecValue(v2));
case FLOAT:
case DOUBLE:
return createReal(type, doubleValue(v1) + doubleValue(v2));
case ANY:
return stringValue(v1, false) + stringValue(v2, false);
default:
return createInteger(type, longValue(v1) + longValue(v2));
}
}
/**
* Subtracts v2 from v1.
*
* @param v1
* Value to be subtracted from.
* @param v2
* Value to be subtracted from v1.
*
* @return v1 - v2
*/
public Object subtract(Object v1, Object v2) {
ValueType type = getNumericType(getType(v1), getType(v2), false);
switch (type) {
case BIGINTEGER:
return bigIntValue(v1).subtract(bigIntValue(v2));
case BIGDECIMAL:
return bigDecValue(v1).subtract(bigDecValue(v2));
case FLOAT:
case DOUBLE:
return createReal(type, doubleValue(v1) - doubleValue(v2));
default:
return createInteger(type, longValue(v1) - longValue(v2));
}
}
/**
* Negates v1.
*
* @param v1
* Value to be negated.
*
* @return -v1
*/
public Object negate(Object v1) {
ValueType type = getType(v1);
switch (type) {
case BIGINTEGER:
return bigIntValue(v1).negate();
case BIGDECIMAL:
return bigDecValue(v1).negate();
case FLOAT:
case DOUBLE:
return createReal(type, -doubleValue(v1));
default:
return createInteger(type, -longValue(v1));
}
}
/**
* Multiplies v1 times v2.
*
* @param v1
* First multiplicand
* @param v2
* Second multiplicand
*
* @return v1 * v2
*/
public Object multiply(Object v1, Object v2) {
ValueType type = getNumericType(getType(v1), getType(v2), false);
switch (type) {
case BIGINTEGER:
return bigIntValue(v1).multiply(bigIntValue(v2));
case BIGDECIMAL:
return bigDecValue(v1).multiply(bigDecValue(v2));
case FLOAT:
case DOUBLE:
return createReal(type, doubleValue(v1) * doubleValue(v2));
default:
return createInteger(type, longValue(v1) * longValue(v2));
}
}
/**
* Divides the first argument by the second
*
* @param v1
* Dividend
* @param v2
* Divisor
*
* @return Value of v1 / v2.
*/
public Object divide(Object v1, Object v2) {
ValueType type = getNumericType(getType(v1), getType(v2), false);
switch (type) {
case BIGINTEGER:
return bigIntValue(v1).divide(bigIntValue(v2));
case BIGDECIMAL:
return bigDecValue(v1).divide(bigDecValue(v2),
BigDecimal.ROUND_HALF_EVEN);
case FLOAT:
case DOUBLE:
return createReal(type, doubleValue(v1) / doubleValue(v2));
default:
return createInteger(type, longValue(v1) / longValue(v2));
}
}
/**
* Returns the number of the first argument, v1, divided by the second
* number's remainder (modulus).
*
* @param v1
* Dividend
* @param v2
* Divisor
*
* @return Remainder of dividing v1 / v2.
*/
public Object remainder(Object v1, Object v2) {
ValueType type = getNumericType(getType(v1), getType(v2), false);
switch (type) {
case BIGDECIMAL:
case BIGINTEGER:
return bigIntValue(v1).remainder(bigIntValue(v2));
default:
return createInteger(type, longValue(v1) % longValue(v2));
}
}
/**
* Returns a new Number object of an appropriate type to hold the given
* integer value. The type of the returned object is consistent with the
* given type argument, which is a constant from the NumericTypes interface.
*
* @param type
* the nominal numeric type of the result
* @param value
* the integer value to convert to a Number object
*
* @return a Number object with the given value, of type implied by the type
* argument
*/
public Number createInteger(ValueType type, long value) {
switch (type) {
case BOOLEAN:
case CHARACTER:
case INTEGER:
return new Integer((int) value);
case FLOAT:
if ((long) (float) value == value) {
return new Float((float) value);
}
// else fall through:
case DOUBLE:
if ((long) (double) value == value) {
return new Double((double) value);
}
// else fall through:
case LONG:
return new Long(value);
case BYTE:
return new Byte((byte) value);
case SHORT:
return new Short((short) value);
default:
return BigInteger.valueOf(value);
}
}
/**
* Returns a new Number object of an appropriate type to hold the given real
* value. The type of the returned object is always either Float or Double,
* and is only Float if the given type tag (a constant from the NumericTypes
* interface) is FLOAT.
*
* @param type
* the nominal numeric type of the result
* @param value
* the real value to convert to a Number object
*
* @return a Number object with the given value, of type implied by the type
* argument
*/
public Number createReal(ValueType type, double value) {
if (type == ValueType.FLOAT) {
return new Float((float) value);
}
return new Double(value);
}
/**
* Returns the value converted numerically to the given class type.
*
* @param toType
* class type to be converted to
* @param value
* an object to be converted to the given type
* @param defaultValue
* value returned in the event that no conversion is possible to
* the given type
*
* @return converted value of the type given, or defaultValue if the value
* cannot be converted to the given type.
*/
public Object convertValue(Class<?> toType, Object value,
Object defaultValue) {
Object result;
if (value != null) {
if (toType == Integer.class || toType == Integer.TYPE) {
result = new Integer((int) longValue(value));
} else if (toType == Double.class || toType == Double.TYPE) {
result = new Double(doubleValue(value));
} else if (toType == Boolean.class || toType == Boolean.TYPE) {
result = booleanValue(value) ? Boolean.TRUE : Boolean.FALSE;
} else if (toType == Byte.class || toType == Byte.TYPE) {
result = new Byte((byte) longValue(value));
} else if (toType == Character.class || toType == Character.TYPE) {
result = new Character((char) longValue(value));
} else if (toType == Short.class || toType == Short.TYPE) {
result = new Short((short) longValue(value));
} else if (toType == Long.class || toType == Long.TYPE) {
result = new Long(longValue(value));
} else if (toType == Float.class || toType == Float.TYPE) {
result = new Float(doubleValue(value));
} else if (toType == BigInteger.class) {
result = bigIntValue(value);
} else if (toType == BigDecimal.class) {
result = bigDecValue(value);
} else if (toType == String.class) {
result = stringValue(value, false);
} else {
result = value;
}
} else {
result = defaultValue;
}
return result;
}
}