/*
This file is part of Cyclos (www.cyclos.org).
A project of the Social Trade Organisation (www.socialtrade.org).
Cyclos is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
Cyclos is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Cyclos; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package nl.strohalm.cyclos.utils.conversion;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.Locale;
import nl.strohalm.cyclos.entities.Entity;
import nl.strohalm.cyclos.utils.ClassHelper;
import nl.strohalm.cyclos.utils.EntityHelper;
import nl.strohalm.cyclos.utils.IntValuedEnum;
import nl.strohalm.cyclos.utils.StringValuedEnum;
import org.apache.commons.beanutils.ConstructorUtils;
import org.apache.commons.collections.IteratorUtils;
import org.apache.commons.lang.ClassUtils;
/**
* Class used to convert from a data type to another
* @author luis
*/
public class CoercionHelper {
/**
* Converts the given value to the given type, using heuristics. The supported types are: primitives, arrays, Number, String, Boolean, Character,
* Calendar, Date, Enum, Entity, Locale and Collection
*/
@SuppressWarnings("unchecked")
public static <T> T coerce(final Class<T> toType, final Object value) {
try {
return (T) convert(toType, value);
} catch (final ConversionException e) {
throw e;
} catch (final Exception e) {
throw new ConversionException("Cannot convert " + value + " to " + toType.getName(), e);
}
}
/**
* Converts an object to a collection of the given type, using the given element type
* @return The resulting collection - it may be empty, but never null
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T> Collection<T> coerceCollection(final Class<T> elementType, final Class<? extends Collection> collectionType, final Object value) {
final Collection<T> collection = ClassHelper.instantiate(collectionType == null ? ArrayList.class : collectionType);
final Iterator<?> iterator = IteratorUtils.getIterator(value);
while (iterator.hasNext()) {
collection.add(coerce(elementType, iterator.next()));
}
return collection;
}
/**
* Converts an object to a collection with the given element type.
* @return The resulting collection - it may be empty, but never null
*/
public static <T> Collection<T> coerceCollection(final Class<T> elementType, final Object value) {
return coerceCollection(elementType, null, value);
}
/**
* Returns the first array item, or null if an empty array
*/
public static <T> T first(final T[] array) {
if (array == null || array.length == 0) {
return null;
}
return array[0];
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private static Object convert(Class toType, Object value) {
if ("".equals(value)) {
value = null;
}
// If we do not want a collection, but the value is one, use the first value
if (value != null && !(Collection.class.isAssignableFrom(toType) || toType.isArray()) && (value.getClass().isArray() || value instanceof Collection)) {
final Iterator it = IteratorUtils.getIterator(value);
if (!it.hasNext()) {
value = null;
} else {
value = it.next();
}
}
// Check for null values
if (value == null) {
if (toType.isPrimitive()) {
// On primitives, use the default value
if (toType == Boolean.TYPE) {
value = Boolean.FALSE;
} else if (toType == Character.TYPE) {
value = '\0';
} else {
value = 0;
}
} else {
// For objects, return null
return null;
}
}
// Check if the value is already of the expected type
if (toType.isInstance(value)) {
return value;
}
// If the class is primitive, use the wrapper, so we have an easier work of testing instances
if (toType.isPrimitive()) {
toType = ClassUtils.primitiveToWrapper(toType);
}
// Convert to well-known types
if (String.class.isAssignableFrom(toType)) {
if (value instanceof Entity) {
final Long entityId = ((Entity) value).getId();
return entityId == null ? null : entityId.toString();
}
return value.toString();
} else if (Number.class.isAssignableFrom(toType)) {
if (!(value instanceof Number)) {
if (value instanceof String) {
value = new BigDecimal((String) value);
} else if (value instanceof Date) {
value = ((Date) value).getTime();
} else if (value instanceof Calendar) {
value = ((Calendar) value).getTimeInMillis();
} else if (value instanceof Entity) {
value = ((Entity) value).getId();
if (value == null) {
return null;
}
} else {
throw new ConversionException("Invalid number: " + value);
}
}
final Number number = (Number) value;
if (Byte.class.isAssignableFrom(toType)) {
return number.byteValue();
} else if (Short.class.isAssignableFrom(toType)) {
return number.shortValue();
} else if (Integer.class.isAssignableFrom(toType)) {
return number.intValue();
} else if (Long.class.isAssignableFrom(toType)) {
return number.longValue();
} else if (Float.class.isAssignableFrom(toType)) {
return number.floatValue();
} else if (Double.class.isAssignableFrom(toType)) {
return number.doubleValue();
} else if (BigInteger.class.isAssignableFrom(toType)) {
return new BigInteger(number.toString());
} else if (BigDecimal.class.isAssignableFrom(toType)) {
return new BigDecimal(number.toString());
}
} else if (Boolean.class.isAssignableFrom(toType)) {
if (value instanceof Number) {
return ((Number) value).intValue() != 0;
} else if ("on".equalsIgnoreCase(value.toString())) {
return true;
} else {
return Boolean.parseBoolean(value.toString());
}
} else if (Character.class.isAssignableFrom(toType)) {
final String str = value.toString();
return (str.length() == 0) ? null : str.charAt(0);
} else if (Calendar.class.isAssignableFrom(toType)) {
if (value instanceof Date) {
final Calendar cal = new GregorianCalendar();
cal.setTime((Date) value);
return cal;
}
} else if (Date.class.isAssignableFrom(toType)) {
if (value instanceof Calendar) {
final long millis = ((Calendar) value).getTimeInMillis();
try {
return ConstructorUtils.invokeConstructor(toType, millis);
} catch (final Exception e) {
throw new IllegalStateException(e);
}
}
} else if (Enum.class.isAssignableFrom(toType)) {
Object ret;
try {
ret = Enum.valueOf(toType, value.toString());
} catch (final Exception e) {
ret = null;
}
if (ret == null) {
Object[] possible;
try {
possible = (Object[]) toType.getMethod("values").invoke(null);
} catch (final Exception e) {
throw new IllegalStateException("Couldn't invoke the 'values' method for enum " + toType.getName());
}
if (StringValuedEnum.class.isAssignableFrom(toType)) {
final String test = coerce(String.class, value);
for (final Object item : possible) {
if (((StringValuedEnum) item).getValue().equals(test)) {
ret = item;
break;
}
}
} else if (IntValuedEnum.class.isAssignableFrom(toType)) {
final int test = coerce(Integer.TYPE, value);
for (final Object item : possible) {
if (((IntValuedEnum) item).getValue() == test) {
ret = item;
break;
}
}
} else {
throw new ConversionException("Invalid enum: " + value);
}
}
return ret;
} else if (Entity.class.isAssignableFrom(toType)) {
final Long id = coerce(Long.class, value);
return EntityHelper.reference(toType, id);
} else if (Locale.class.isAssignableFrom(toType)) {
return LocaleConverter.instance().valueOf(value.toString());
} else if (Collection.class.isAssignableFrom(toType)) {
final Collection collection = (Collection) ClassHelper.instantiate(toType);
final Iterator iterator = IteratorUtils.getIterator(value);
while (iterator.hasNext()) {
collection.add(iterator.next());
}
return collection;
} else if (toType.isArray()) {
final Collection collection = coerceCollection(toType.getComponentType(), value);
final Object[] array = (Object[]) Array.newInstance(toType.getComponentType(), collection.size());
return collection.toArray(array);
}
// We don't know how to convert the value
throw new ConversionException("Cannot coerce value to: " + toType.getName());
}
}