package net.sourceforge.javautil.common.coersion.impl;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import net.sourceforge.javautil.common.ReflectionUtil;
import net.sourceforge.javautil.common.coersion.ICoersion;
import net.sourceforge.javautil.common.coersion.CoersionException;
import net.sourceforge.javautil.common.coersion.ICoersionProvider;
import net.sourceforge.javautil.common.exception.ThrowableManagerRegistry;
import net.sourceforge.javautil.common.io.IVirtualDirectory;
import net.sourceforge.javautil.common.io.IVirtualFile;
import net.sourceforge.javautil.common.io.impl.SystemDirectory;
import net.sourceforge.javautil.common.io.impl.SystemFile;
/**
* The standard provider for common coersion.
*
* @author elponderador
* @author $Author: ponderator $
* @version $Id: StandardCoersionProvider.java 2297 2010-06-16 00:13:14Z ponderator $
*/
public class StandardCoersionProvider implements ICoersionProvider {
protected final List<ICoersion> external = new CopyOnWriteArrayList<ICoersion>();
protected final List<ICoersion> internal = new CopyOnWriteArrayList<ICoersion>();
public StandardCoersionProvider () {
this.internal.add(new CoersionUtilDate());
this.internal.add(new CoersionSQLDate());
this.internal.add(new CoersionVirtualArtifact());
this.internal.add(new CoersionURL());
}
public <T> T coerce(Object original, Class<T> targetType) throws CoersionException {
if (targetType == void.class) return null;
if (original == null && !targetType.isPrimitive()) return null;
if (original == null && targetType.isPrimitive()) return (T) ReflectionUtil.getPrimitiveDefault(targetType);
if (targetType.isAssignableFrom( original.getClass() )) return (T) original;
if ("".equals(original) && targetType.isPrimitive()) return (T) ReflectionUtil.getPrimitiveDefault(targetType);
if (targetType.isPrimitive() && ReflectionUtil.isBoxedType(original.getClass()) &&
ReflectionUtil.getPrimitiveType(original.getClass()) == targetType) return (T) original;
if (external.size() > 0) {
Iterator<ICoersion> external = this.external.iterator();
while (external.hasNext()) {
ICoersion coersion = external.next();
if (coersion.isCanCoerce(original, targetType)) return (T) coersion.coerce(original, targetType);
}
}
Iterator<ICoersion> internal = this.internal.iterator();
while (internal.hasNext()) {
ICoersion coersion = internal.next();
if (coersion.isCanCoerce(original, targetType)) return (T) coersion.coerce(original, targetType);
}
if (original instanceof CharSequence) {
return (T) this.coerceCharSequence((CharSequence)original, targetType);
}
if (original instanceof Number && (Number.class.isAssignableFrom(targetType) ||
(targetType.isPrimitive() && Number.class.isAssignableFrom(ReflectionUtil.getBoxedType(targetType) )))) {
return (T) this.coerceNumber((Number)original, targetType);
}
if (CharSequence.class.isAssignableFrom(targetType)) {
if (original.getClass().isArray()) {
if (original.getClass() == char[].class) return (T) this.coerceToStringType(new String( (char[]) original ), targetType);
else if (original.getClass() == byte[].class) return (T) this.coerceToStringType(new String( (byte[]) original ), targetType);
} else
return (T) this.coerceToStringType(String.valueOf(original), targetType);
}
try {
// Last Attempt, try to coerce to a string and then to the target type
return this.coerce(this.coerce(original, String.class), targetType);
} catch (CoersionException e) {
throw new CoersionException("Cannot coerce: " + original + " to " + targetType, e);
}
}
public synchronized void register(ICoersion... coersions) {
this.external.addAll( Arrays.asList(coersions) );
}
public synchronized void unregister(ICoersion... coersions) {
this.external.removeAll( Arrays.asList(coersions) );
}
protected Number coerceNumber (Number number1, Class numberType) {
if (numberType.isPrimitive()) numberType = ReflectionUtil.getBoxedType(numberType);
if (Float.class == numberType) return number1.floatValue();
if (Integer.class == numberType) return number1.intValue();
if (Byte.class == numberType) return number1.byteValue();
if (Double.class == numberType) return number1.doubleValue();
if (Short.class == numberType) return number1.shortValue();
if (Long.class == numberType) return number1.longValue();
if (BigDecimal.class == numberType) return new BigDecimal(number1.toString());
if (BigInteger.class == numberType) return new BigInteger(number1.toString());
throw new UnsupportedOperationException("Could not convert: " + number1 + " to a " + numberType);
}
/**
* @param <T> The type to coerce to
* @param str The string to coerce
* @param target The class of the type
* @return The coerced char sequence type
*/
protected <T extends CharSequence> T coerceToStringType (String str, Class target) {
if (target == String.class) return (T) str;
if (target == StringBuffer.class) return (T) new StringBuffer(str);
if (target == StringBuilder.class) return (T) new StringBuilder(str);
for (Class type : new Class[] { CharSequence.class, String.class }) {
try {
Constructor c = target.getConstructor(type);
return (T) c.newInstance(str);
} catch (Exception e) {}
}
throw new CoersionException("Unsupported char sequence type: " + target);
}
/**
* @param original The original string
* @param type The type to coerce the string to
* @return The coerced object
*/
protected Object coerceCharSequence (CharSequence chars, Class type) {
String original = String.valueOf(chars);
if (type == boolean.class || type == Boolean.class) return Boolean.parseBoolean(original);
else if (type == char.class || type == Character.class) return new Character(original.charAt(0));
else if (type == int.class || type == Integer.class) return Integer.parseInt(original);
else if (type == long.class || type == Long.class) return Long.parseLong(original);
else if (type == double.class || type == Double.class) return Double.parseDouble(original);
else if (type == float.class || type == Float.class) return Float.parseFloat(original);
else if (type == short.class || type == Short.class) return Short.parseShort(original);
else if (type == byte.class || type == Byte.class) return Byte.parseByte(original);
else if (type == byte[].class) return original.getBytes();
else if (type == char[].class) {
char[] data = new char[original.length()];
original.getChars(0, original.length(), data, 0);
return data;
}
else if (type == StringBuffer.class) return new StringBuffer().append(original);
else if (type == InputStream.class) return new ByteArrayInputStream(original.getBytes());
else if (Enum.class.isAssignableFrom(type)) {
try {
String name = original.toString().toUpperCase();
for (Object constant : type.getEnumConstants()) {
if (constant.toString().toUpperCase().equals(name)) return constant;
}
return Enum.valueOf(type, original);
} catch (IllegalArgumentException e) {
return Enum.valueOf(type, original.toUpperCase());
}
}
else {
try {
Constructor c = type.getConstructor(String.class);
return c.newInstance(original);
} catch (Exception e) {
throw new CoersionException("Could not coerce: " + original + " to type " + type, e);
}
}
}
}