/**
* $Id: ConversionUtils.java 2 2008-10-01 10:04:26Z azeckoski $
* $URL: http://reflectutils.googlecode.com/svn/trunk/src/main/java/org/azeckoski/reflectutils/ConversionUtils.java $
* ConversionUtils.java - genericdao - May 19, 2008 10:10:15 PM - azeckoski
**************************************************************************
* Copyright (c) 2008 Aaron Zeckoski
* Licensed under the Apache License, Version 2.0
*
* A copy of the Apache License has been included in this
* distribution and is available at: http://www.apache.org/licenses/LICENSE-2.0.txt
*
* Aaron Zeckoski (azeckoski @ gmail.com) (aaronz @ vt.edu) (aaron @ caret.cam.ac.uk)
*/
package org.azeckoski.reflectutils;
import java.io.File;
import java.lang.ref.SoftReference;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.Map.Entry;
import org.azeckoski.reflectutils.converters.ArrayConverter;
import org.azeckoski.reflectutils.converters.BigDecimalConverter;
import org.azeckoski.reflectutils.converters.BigIntegerConverter;
import org.azeckoski.reflectutils.converters.BooleanConverter;
import org.azeckoski.reflectutils.converters.ByteConverter;
import org.azeckoski.reflectutils.converters.CalendarConverter;
import org.azeckoski.reflectutils.converters.CharacterConverter;
import org.azeckoski.reflectutils.converters.ClassConverter;
import org.azeckoski.reflectutils.converters.CollectionConverter;
import org.azeckoski.reflectutils.converters.DateConverter;
import org.azeckoski.reflectutils.converters.DoubleConverter;
import org.azeckoski.reflectutils.converters.EnumConverter;
import org.azeckoski.reflectutils.converters.FileConverter;
import org.azeckoski.reflectutils.converters.FloatConverter;
import org.azeckoski.reflectutils.converters.IntegerConverter;
import org.azeckoski.reflectutils.converters.LongConverter;
import org.azeckoski.reflectutils.converters.MapConverter;
import org.azeckoski.reflectutils.converters.NumberConverter;
import org.azeckoski.reflectutils.converters.SQLDateConverter;
import org.azeckoski.reflectutils.converters.SQLTimeConverter;
import org.azeckoski.reflectutils.converters.ScalarConverter;
import org.azeckoski.reflectutils.converters.ShortConverter;
import org.azeckoski.reflectutils.converters.StringConverter;
import org.azeckoski.reflectutils.converters.TimestampConverter;
import org.azeckoski.reflectutils.converters.URLConverter;
import org.azeckoski.reflectutils.converters.api.Converter;
import org.azeckoski.reflectutils.converters.api.InterfaceConverter;
import org.azeckoski.reflectutils.converters.api.VariableConverter;
import org.azeckoski.reflectutils.refmap.ReferenceMap;
import org.azeckoski.reflectutils.refmap.ReferenceType;
/**
* Class which provides methods for converting between object types,
* can be extended with various converters
*
* @author Aaron Zeckoski (azeckoski @ gmail.com)
*/
public class ConversionUtils {
/**
* Empty constructor
*/
protected ConversionUtils() {
this(null);
}
/**
* Constructor which allows adding to the initial set of converters
* <br/>
* <b>WARNING:</b> if you don't need this control then just use the {@link #getInstance()} method to get this
*
* @param converters a map of converters to add to the default set
*/
public ConversionUtils(Map<Class<?>, Converter<?>> converters) {
// populate the converters
setConverters(converters);
setVariableConverters(null);
ConversionUtils.setInstance(this);
}
protected Map<Class<?>, Converter<?>> converters = null;
/**
* Set the object converters to add to the default converters
* @param converters a map of converters to add to the default set
*/
public void setConverters(Map<Class<?>, Converter<?>> converters) {
loadDefaultConverters();
if (converters != null) {
for (Entry<Class<?>, Converter<?>> entry : converters.entrySet()) {
if (entry.getKey() != null && entry.getValue() != null) {
this.converters.put(entry.getKey(), entry.getValue());
}
}
}
}
protected Map<Class<?>, Converter<?>> getConverters() {
if (converters == null) {
loadDefaultConverters();
}
return converters;
}
/**
* this loads up all the default converters
*/
protected void loadDefaultConverters() {
if (converters == null) {
converters = new ReferenceMap<Class<?>, Converter<?>>(ReferenceType.WEAK, ReferenceType.STRONG);
} else {
converters.clear();
}
// order is not important here but maintain alpha order for readability
converters.put(BigDecimal.class, new BigDecimalConverter());
converters.put(BigInteger.class, new BigIntegerConverter());
converters.put(Boolean.class, new BooleanConverter());
converters.put(Byte.class, new ByteConverter());
converters.put(Calendar.class, new CalendarConverter());
converters.put(Character.class, new CharacterConverter());
converters.put(Class.class, new ClassConverter());
converters.put(Collection.class, new CollectionConverter());
converters.put(Date.class, new DateConverter());
converters.put(Double.class, new DoubleConverter());
converters.put(Enum.class, new EnumConverter());
converters.put(File.class, new FileConverter());
converters.put(Float.class, new FloatConverter());
converters.put(Integer.class, new IntegerConverter());
converters.put(Long.class, new LongConverter());
converters.put(Map.class, new MapConverter());
converters.put(Number.class, new NumberConverter());
converters.put(Short.class, new ShortConverter());
converters.put(String.class, new StringConverter());
converters.put(java.sql.Date.class, new SQLDateConverter());
converters.put(java.sql.Time.class, new SQLTimeConverter());
converters.put(java.sql.Timestamp.class, new TimestampConverter());
converters.put(URL.class, new URLConverter());
}
/**
* Add a converter to the default set which will convert objects to the supplied type
* @param type the type this converter will convert objects to
* @param converter the converter
*/
public void addConverter(Class<?> type, Converter<?> converter) {
if (type == null || converter == null) {
throw new IllegalArgumentException("You must specify a type and a converter in order to add a converter (no nulls)");
}
getConverters().put(type, converter);
}
protected List<VariableConverter> variableConverters = null;
/**
* Replace the current or default variable converters with a new set,
* this will remove the default variable converters and will not add them back in
* @param variableConverters the variable object converters
*/
public void setVariableConverters(List<VariableConverter> variableConverters) {
loadDefaultVariableConverters();
if (variableConverters != null) {
for (VariableConverter variableConverter : variableConverters) {
if (variableConverter != null) {
this.variableConverters.add(variableConverter);
}
}
}
}
protected List<VariableConverter> getVariableConverters() {
if (variableConverters == null) {
loadDefaultVariableConverters();
}
return variableConverters;
}
protected void loadDefaultVariableConverters() {
if (variableConverters == null) {
variableConverters = new Vector<VariableConverter>();
} else {
clearVariableConverters();
}
variableConverters.add( new ArrayConverter() );
variableConverters.add( new ScalarConverter() );
}
/**
* Adds a variable converter to the end of the list of default variable converters
* @param variableConverter
*/
public void addVariableConverter(VariableConverter variableConverter) {
if (variableConverter == null) {
throw new IllegalArgumentException("You must specify a variableConverter in order to add it (no nulls)");
}
getVariableConverters().add(variableConverter);
}
/**
* Resets and removes all variable converters including the defaults,
* use this when you want to override the existing variable converters
*/
public void clearVariableConverters() {
if (variableConverters != null) {
variableConverters.clear();
}
}
/**
* Added for apache commons beanutils compatibility,
* you should probably use {@link #convert(Object, Class)}<br/>
* Convert the specified value into a String. If the specified value
* is an array, the first element (converted to a String) will be
* returned. The registered {@link Converter} for the
* <code>java.lang.String</code> class will be used, which allows
* applications to customize Object->String conversions (the default
* implementation simply uses toString()).
*
* @param object any object
* @return the string OR null if one cannot be found
*/
public String convertToString(Object object) {
// code here is basically from ConvertUtilsBeans 1.8.0
String convert = null;
if (object == null) {
convert = null;
} else if (object.getClass().isArray()) {
if (Array.getLength(object) < 1) {
convert = null;
} else {
Object value = Array.get(object, 0);
if (value == null) {
convert = null;
} else {
Converter<String> converter = getConverter(String.class);
return converter.convert(value);
}
}
} else {
Converter<String> converter = getConverter(String.class);
return converter.convert(object);
}
return convert;
}
/**
* Added for apache commons beanutils compatibility,
* you should probably use {@link #convert(Object, Class)}<br/>
* Convert the string value to an object of the specified class (if
* possible). Otherwise, return a String representation of the value.
*
* @param value the string value to be converted
* @param type any class type that you want to try to convert the object to
* @return the converted value
* @throws UnsupportedOperationException if the conversion cannot be completed
*/
public Object convertString(String value, Class<?> type) {
Object convert = null;
try {
convert = convert(value, type);
} catch (UnsupportedOperationException e) {
convert = value;
}
return convert;
}
/**
* Converts an object to any other object if possible using the current set of converters,
* will allow nulls to pass through unless there is a converter which will handle them <br/>
* Includes special handling for primitives, arrays, and collections
* (will take the first value when converting to scalar)
*
* @param <T>
* @param value any object
* @param type any class type that you want to try to convert the object to
* @return the converted value (allows null to pass through except in the case of primitives which become the primitive default)
* @throws UnsupportedOperationException if the conversion cannot be completed
*/
@SuppressWarnings("unchecked")
public <T> T convert(Object value, Class<T> type) {
T convert = null;
Object toConvert = value;
if (toConvert != null) {
Class<?> fromType = toConvert.getClass();
// first we check to see if we even need to do the conversion
if ( ConstructorUtils.classAssignable(fromType, type) ) {
// this is already equivalent so no reason to convert
convert = (T) value;
} else {
// needs to be converted
try {
convert = convertWithConverter(toConvert, type);
} catch (RuntimeException e) {
throw new UnsupportedOperationException("Could not convert object ("+toConvert+") from type ("+fromType+") to type ("+type+"): " + e.getMessage(), e);
}
}
} else {
// object is null but type requested may be primitive
if ( ConstructorUtils.isClassPrimitive(type) ) {
// for primitives we return the default value
if (ConstructorUtils.getPrimitiveDefaults().containsKey(type)) {
convert = (T) ConstructorUtils.getPrimitiveDefaults().get(type);
}
}
}
return convert;
}
/**
* Use the converters to convert the value to the provided type,
* this simply finds the converter and does the conversion,
* will convert interface types automatically <br/>
* WARNING: you should use {@link #convert(Object, Class)} unless you have a special need
* for this, it is primarily for reducing code complexity
*
* @param <T>
* @param value any object to be converted
* @param type the type to convert to
* @return the converted object (may be null)
* @throws UnsupportedOperationException is the conversion could not be completed
*/
protected <T> T convertWithConverter(Object value, Class<T> type) {
T convert = null;
// check for a variable converter that says it will handle the conversion first
VariableConverter variableConverter = getVariableConverter(value, type);
if (variableConverter != null) {
// use the variable converter
convert = variableConverter.convert(value, type);
} else {
// use a converter
Converter<T> converter = getConverterOrFail(type);
if (InterfaceConverter.class.isAssignableFrom(converter.getClass())) {
convert = ((InterfaceConverter<T>)converter).convertInterface(value, type);
} else {
// standard converter
convert = converter.convert(value);
}
}
return convert;
}
/**
* Get the converter or throw exception
* @param <T>
* @param type type to convert to
* @return the converter or die
* @throws UnsupportedOperationException if the converter cannot be found
*/
protected <T> Converter<T> getConverterOrFail(Class<T> type) {
Converter<T> converter = getConverter(type);
if (converter == null) {
throw new UnsupportedOperationException("Conversion failure: No converter available to handle conversions to type ("+type+")");
}
return converter;
}
/**
* Get the converter for the given type if there is one
* @param <T>
* @param type the type to convert to
* @return the converter for this type OR null if there is not one
*/
@SuppressWarnings("unchecked")
protected <T> Converter<T> getConverter(Class<T> type) {
if (type == null) {
throw new IllegalArgumentException("Cannot get a converter for nulls");
}
// first make sure we are using an actual wrapper class and not the primitive class (int.class)
Class<?> toType = ConstructorUtils.getWrapper(type);
Converter<T> converter = (Converter<T>) getConverters().get(toType);
if (converter == null) {
// none found so try not using the interface
toType = ConstructorUtils.getClassFromInterface(toType);
converter = (Converter<T>) getConverters().get(toType);
if (converter == null) {
// still no converter found so try the interfaces
for (Class<?> iface : ConstructorUtils.getExtendAndInterfacesForClass(toType)) {
converter = (Converter<T>) getConverters().get(iface);
if (converter != null) {
// found a converter
break;
}
}
}
}
return converter;
}
/**
* Get the variable converter for this value and type if there is one,
* returns null if no converter is available
* @param value the value to convert
* @param type the type to convert to
* @return the variable converter if there is one OR null if none exists
*/
protected VariableConverter getVariableConverter(Object value, Class<?> type) {
VariableConverter converter = null;
Class<?> toType = ConstructorUtils.getWrapper(type);
for (VariableConverter variableConverter : getVariableConverters()) {
if (variableConverter.canConvert(value, toType)) {
converter = variableConverter;
break;
}
}
return converter;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("converters=");
sb.append(getConverters().size());
sb.append(":");
for (Entry<Class<?>, Converter<?>> entry : getConverters().entrySet()) {
sb.append("[");
sb.append(entry.getKey().getName());
sb.append("=>");
sb.append(entry.getValue().getClass().getName());
sb.append("]");
}
sb.append(":variable=");
sb.append(getVariableConverters().size());
sb.append(":");
for (VariableConverter variableConverter : getVariableConverters()) {
sb.append("(");
sb.append(variableConverter.getClass().getName());
sb.append(")");
}
return "Convert::c="+ConversionUtils.timesCreated+":s="+singleton+":" + sb.toString();
}
// STATIC access
protected static SoftReference<ConversionUtils> instanceStorage;
/**
* Get a singleton instance of this class to work with (stored statically) <br/>
* <b>WARNING</b>: do not hold onto this object or cache it yourself, call this method again if you need it again
* @return a singleton instance of this class
*/
public static ConversionUtils getInstance() {
ConversionUtils instance = (instanceStorage == null ? null : instanceStorage.get());
if (instance == null) {
instance = ConversionUtils.setInstance(null);
}
return instance;
}
/**
* Set the singleton instance of the class which will be stored statically
* @param instance the instance to use as the singleton instance
*/
public static ConversionUtils setInstance(ConversionUtils newInstance) {
ConversionUtils instance = newInstance;
if (instance == null) {
instance = new ConversionUtils();
instance.singleton = true;
}
ConversionUtils.timesCreated++;
instanceStorage = new SoftReference<ConversionUtils>(instance);
return instance;
}
private static int timesCreated = 0;
public static int getTimesCreated() {
return timesCreated;
}
private boolean singleton = false;
/**
* @return true if this object is the singleton
*/
public boolean isSingleton() {
return singleton;
}
protected ConstructorUtils getConstructorUtils() {
return ConstructorUtils.getInstance();
}
protected FieldUtils getFieldUtils() {
return FieldUtils.getInstance();
}
}