/*******************************************************************************
 * Copyright (c) 2007 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 ******************************************************************************/

package org.eclipse.core.databinding;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.core.databinding.conversion.IConverter;
import org.eclipse.core.databinding.conversion.NumberToStringConverter;
import org.eclipse.core.databinding.conversion.StringToNumberConverter;
import org.eclipse.core.databinding.util.Policy;
import org.eclipse.core.internal.databinding.ClassLookupSupport;
import org.eclipse.core.internal.databinding.Pair;
import org.eclipse.core.internal.databinding.conversion.IdentityConverter;
import org.eclipse.core.internal.databinding.conversion.IntegerToStringConverter;
import org.eclipse.core.internal.databinding.conversion.NumberToBigDecimalConverter;
import org.eclipse.core.internal.databinding.conversion.NumberToBigIntegerConverter;
import org.eclipse.core.internal.databinding.conversion.NumberToByteConverter;
import org.eclipse.core.internal.databinding.conversion.NumberToDoubleConverter;
import org.eclipse.core.internal.databinding.conversion.NumberToFloatConverter;
import org.eclipse.core.internal.databinding.conversion.NumberToIntegerConverter;
import org.eclipse.core.internal.databinding.conversion.NumberToLongConverter;
import org.eclipse.core.internal.databinding.conversion.NumberToShortConverter;
import org.eclipse.core.internal.databinding.conversion.ObjectToStringConverter;
import org.eclipse.core.internal.databinding.conversion.StringToByteConverter;
import org.eclipse.core.internal.databinding.conversion.StringToShortConverter;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;

import com.ibm.icu.text.NumberFormat;

/**
 * @since 1.0
 * 
 */
/* package */class UpdateStrategy {

	private static final String BOOLEAN_TYPE = "java.lang.Boolean.TYPE"; //$NON-NLS-1$
	private static final String SHORT_TYPE = "java.lang.Short.TYPE"; //$NON-NLS-1$
	private static final String BYTE_TYPE = "java.lang.Byte.TYPE"; //$NON-NLS-1$
	private static final String DOUBLE_TYPE = "java.lang.Double.TYPE"; //$NON-NLS-1$
	private static final String FLOAT_TYPE = "java.lang.Float.TYPE"; //$NON-NLS-1$
	private static final String INTEGER_TYPE = "java.lang.Integer.TYPE"; //$NON-NLS-1$
	private static final String LONG_TYPE = "java.lang.Long.TYPE"; //$NON-NLS-1$
	private static Map converterMap;

	private static Class autoboxed(Class clazz) {
		if (clazz == Float.TYPE)
			return Float.class;
		else if (clazz == Double.TYPE)
			return Double.class;
		else if (clazz == Short.TYPE)
			return Short.class;
		else if (clazz == Integer.TYPE)
			return Integer.class;
		else if (clazz == Long.TYPE)
			return Long.class;
		else if (clazz == Byte.TYPE)
			return Byte.class;
		else if (clazz == Boolean.TYPE)
			return Boolean.class;
		return clazz;
	}

	final protected void checkAssignable(Object toType, Object fromType,
			String errorString) {
		Boolean assignableFromModelToModelConverter = isAssignableFromTo(
				fromType, toType);
		if (assignableFromModelToModelConverter != null
				&& !assignableFromModelToModelConverter.booleanValue()) {
			throw new BindingException(errorString
					+ " Expected: " + fromType + ", actual: " + toType); //$NON-NLS-1$//$NON-NLS-2$
		}
	}

	/**
	 * Tries to create a converter that can convert from values of type
	 * fromType. Returns <code>null</code> if no converter could be created.
	 * Either toType or modelDescription can be <code>null</code>, but not
	 * both.
	 * 
	 * @param fromType
	 * @param toType
	 * @return an IConverter, or <code>null</code> if unsuccessful
	 */
	protected IConverter createConverter(Object fromType, Object toType) {
		if (!(fromType instanceof Class) || !(toType instanceof Class)) {
			return new DefaultConverter(fromType, toType);
		}
		Class toClass = (Class) toType;
		Class originalToClass = toClass;
		if (toClass.isPrimitive()) {
			toClass = autoboxed(toClass);
		}
		Class fromClass = (Class) fromType;
		Class originalFromClass = fromClass;
		if (fromClass.isPrimitive()) {
			fromClass = autoboxed(fromClass);
		}
		if (!((Class) toType).isPrimitive()
				&& toClass.isAssignableFrom(fromClass)) {
			return new IdentityConverter(originalFromClass, originalToClass);
		}
		if (((Class) fromType).isPrimitive() && ((Class) toType).isPrimitive()
				&& fromType.equals(toType)) {
			return new IdentityConverter(originalFromClass, originalToClass);
		}
		Map converterMap = getConverterMap();
		Class[] supertypeHierarchyFlattened = ClassLookupSupport
				.getTypeHierarchyFlattened(fromClass);
		for (int i = 0; i < supertypeHierarchyFlattened.length; i++) {
			Class currentFromClass = supertypeHierarchyFlattened[i];
			if (currentFromClass == toType) {
				// converting to toType is just a widening
				return new IdentityConverter(fromClass, toClass);
			}
			Pair key = new Pair(getKeyForClass(fromType, currentFromClass),
					getKeyForClass(toType, toClass));
			Object converterOrClassname = converterMap.get(key);
			if (converterOrClassname instanceof IConverter) {
				return (IConverter) converterOrClassname;
			} else if (converterOrClassname instanceof String) {
				String classname = (String) converterOrClassname;
				Class converterClass;
				try {
					converterClass = Class.forName(classname);
					IConverter result = (IConverter) converterClass
							.newInstance();
					converterMap.put(key, result);
					return result;
				} catch (Exception e) {
					Policy
							.getLog()
							.log(
									new Status(
											IStatus.ERROR,
											Policy.JFACE_DATABINDING,
											0,
											"Error while instantiating default converter", e)); //$NON-NLS-1$
				}
			}
		}
		// Since we found no converter yet, try a "downcast" converter;
		// the IdentityConverter will automatically check the actual types at
		// runtime.
		if (fromClass.isAssignableFrom(toClass)) {
			return new IdentityConverter(originalFromClass, originalToClass);
		}
		return new DefaultConverter(fromType, toType);
	}

	private static Map getConverterMap() {
		// using string-based lookup avoids loading of too many classes
		if (converterMap == null) {
			//NumberFormat to be shared across converters for the formatting of integer values
			NumberFormat integerFormat = NumberFormat.getIntegerInstance();
			//NumberFormat to be shared across converters for formatting non integer values
			NumberFormat numberFormat = NumberFormat.getNumberInstance();
			
			converterMap = new HashMap();
			converterMap
					.put(
							new Pair("java.util.Date", "java.lang.String"), "org.eclipse.core.internal.databinding.conversion.DateToStringConverter"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			converterMap
					.put(
							new Pair("java.lang.String", "java.lang.Boolean"), "org.eclipse.core.internal.databinding.conversion.StringToBooleanConverter"); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
			converterMap
					.put(
							new Pair("java.lang.String", "java.lang.Byte"), StringToByteConverter.toByte(integerFormat, false)); //$NON-NLS-1$//$NON-NLS-2$
			converterMap
					.put(
							new Pair("java.lang.String", "java.lang.Character"), "org.eclipse.core.internal.databinding.conversion.StringToCharacterConverter"); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
			converterMap
					.put(
							new Pair("java.lang.String", "java.util.Date"), "org.eclipse.core.internal.databinding.conversion.StringToDateConverter"); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
			converterMap
					.put(
							new Pair("java.lang.String", "java.lang.Short"), StringToShortConverter.toShort(integerFormat, false)); //$NON-NLS-1$//$NON-NLS-2$
			
			converterMap.put(new Pair("java.lang.String", "java.lang.Integer"), StringToNumberConverter.toInteger(integerFormat, false));  //$NON-NLS-1$//$NON-NLS-2$
			converterMap.put(new Pair("java.lang.String", "java.lang.Double"), StringToNumberConverter.toDouble(numberFormat, false));  //$NON-NLS-1$//$NON-NLS-2$
			converterMap.put(new Pair("java.lang.String", "java.lang.Long"), StringToNumberConverter.toLong(integerFormat, false));  //$NON-NLS-1$//$NON-NLS-2$
			converterMap.put(new Pair("java.lang.String", "java.lang.Float"), StringToNumberConverter.toFloat(numberFormat, false));  //$NON-NLS-1$//$NON-NLS-2$			
			converterMap.put(new Pair("java.lang.String", "java.math.BigInteger"), StringToNumberConverter.toBigInteger(integerFormat));  //$NON-NLS-1$//$NON-NLS-2$			
			converterMap.put(new Pair("java.lang.Integer", "java.lang.String"), NumberToStringConverter.fromInteger(integerFormat, false));  //$NON-NLS-1$//$NON-NLS-2$
			converterMap.put(new Pair("java.lang.Long", "java.lang.String"), NumberToStringConverter.fromLong(integerFormat, false));  //$NON-NLS-1$//$NON-NLS-2$
			converterMap.put(new Pair("java.lang.Double", "java.lang.String"), NumberToStringConverter.fromDouble(numberFormat, false));  //$NON-NLS-1$//$NON-NLS-2$
			converterMap.put(new Pair("java.lang.Float", "java.lang.String"), NumberToStringConverter.fromFloat(numberFormat, false));  //$NON-NLS-1$//$NON-NLS-2$
			converterMap.put(new Pair("java.math.BigInteger", "java.lang.String"), NumberToStringConverter.fromBigInteger(integerFormat));  //$NON-NLS-1$//$NON-NLS-2$
			converterMap.put(new Pair("java.lang.Byte", "java.lang.String"), IntegerToStringConverter.fromByte(integerFormat, false));   //$NON-NLS-1$//$NON-NLS-2$
			converterMap.put(new Pair("java.lang.Short", "java.lang.String"), IntegerToStringConverter.fromShort(integerFormat, false));  //$NON-NLS-1$//$NON-NLS-2$
			
			converterMap
					.put(
							new Pair("java.lang.Object", "java.lang.String"), "org.eclipse.core.internal.databinding.conversion.ObjectToStringConverter"); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$

			// Integer.TYPE
			converterMap.put(new Pair("java.lang.String", INTEGER_TYPE), StringToNumberConverter.toInteger(integerFormat, true)); //$NON-NLS-1$
			converterMap
					.put(
							new Pair(INTEGER_TYPE, "java.lang.Integer"), new IdentityConverter(Integer.TYPE, Integer.class)); //$NON-NLS-1$
			converterMap
					.put(
							new Pair(INTEGER_TYPE, "java.lang.Object"), new IdentityConverter(Integer.TYPE, Object.class)); //$NON-NLS-1$
			converterMap.put(new Pair(INTEGER_TYPE, "java.lang.String"), NumberToStringConverter.fromInteger(integerFormat, true)); //$NON-NLS-1$

			// Byte.TYPE
			converterMap
					.put(
							new Pair("java.lang.String", BYTE_TYPE), StringToByteConverter.toByte(integerFormat, true)); //$NON-NLS-1$
			converterMap
					.put(
							new Pair(BYTE_TYPE, "java.lang.Byte"), new IdentityConverter(Byte.TYPE, Byte.class)); //$NON-NLS-1$
			converterMap
					.put(
							new Pair(BYTE_TYPE, "java.lang.String"), IntegerToStringConverter.fromByte(integerFormat, true)); //$NON-NLS-1$
			converterMap
					.put(
							new Pair(BYTE_TYPE, "java.lang.Object"), new IdentityConverter(Byte.TYPE, Object.class)); //$NON-NLS-1$

			// Double.TYPE
			converterMap.put(new Pair("java.lang.String", DOUBLE_TYPE), StringToNumberConverter.toDouble(numberFormat, true)); //$NON-NLS-1$
			converterMap.put(new Pair(DOUBLE_TYPE, "java.lang.String"), NumberToStringConverter.fromDouble(numberFormat, true)); //$NON-NLS-1$
			
			converterMap
					.put(
							new Pair(DOUBLE_TYPE, "java.lang.Double"), new IdentityConverter(Double.TYPE, Double.class)); //$NON-NLS-1$
			converterMap
					.put(
							new Pair(DOUBLE_TYPE, "java.lang.Object"), new IdentityConverter(Double.TYPE, Object.class)); //$NON-NLS-1$

			// Boolean.TYPE
			converterMap
					.put(
							new Pair("java.lang.String", BOOLEAN_TYPE), "org.eclipse.core.internal.databinding.conversion.StringToBooleanPrimitiveConverter"); //$NON-NLS-1$ //$NON-NLS-2$
			converterMap
					.put(
							new Pair(BOOLEAN_TYPE, "java.lang.Boolean"), new IdentityConverter(Boolean.TYPE, Boolean.class)); //$NON-NLS-1$
			converterMap
					.put(
							new Pair(BOOLEAN_TYPE, "java.lang.String"), new ObjectToStringConverter(Boolean.TYPE)); //$NON-NLS-1$
			converterMap
					.put(
							new Pair(BOOLEAN_TYPE, "java.lang.Object"), new IdentityConverter(Boolean.TYPE, Object.class)); //$NON-NLS-1$

			// Float.TYPE
			converterMap.put(new Pair("java.lang.String", FLOAT_TYPE), StringToNumberConverter.toFloat(numberFormat, true)); //$NON-NLS-1$
			converterMap.put(new Pair(FLOAT_TYPE, "java.lang.String"), NumberToStringConverter.fromFloat(numberFormat, true)); //$NON-NLS-1$
			converterMap
					.put(
							new Pair(FLOAT_TYPE, "java.lang.Float"), new IdentityConverter(Float.TYPE, Float.class)); //$NON-NLS-1$
			converterMap
					.put(
							new Pair(FLOAT_TYPE, "java.lang.Object"), new IdentityConverter(Float.TYPE, Object.class)); //$NON-NLS-1$		

			// Short.TYPE
			converterMap
					.put(
							new Pair("java.lang.String", SHORT_TYPE), StringToShortConverter.toShort(integerFormat, true)); //$NON-NLS-1$
			converterMap
					.put(
							new Pair(SHORT_TYPE, "java.lang.Short"), new IdentityConverter(Short.TYPE, Short.class)); //$NON-NLS-1$
			converterMap
					.put(
							new Pair(SHORT_TYPE, "java.lang.String"), IntegerToStringConverter.fromShort(integerFormat, true)); //$NON-NLS-1$
			converterMap
					.put(
							new Pair(SHORT_TYPE, "java.lang.Object"), new IdentityConverter(Short.TYPE, Object.class)); //$NON-NLS-1$		

			// Long.TYPE
			converterMap.put(new Pair("java.lang.String", LONG_TYPE), StringToNumberConverter.toLong(integerFormat, true)); //$NON-NLS-1$
			converterMap.put(new Pair(LONG_TYPE, "java.lang.String"), NumberToStringConverter.fromLong(integerFormat, true)); //$NON-NLS-1$
			converterMap
					.put(
							new Pair(LONG_TYPE, "java.lang.Long"), new IdentityConverter(Long.TYPE, Long.class)); //$NON-NLS-1$
			converterMap
					.put(
							new Pair(LONG_TYPE, "java.lang.Object"), new IdentityConverter(Long.TYPE, Object.class)); //$NON-NLS-1$		

			converterMap
					.put(
							new Pair("org.eclipse.core.runtime.IStatus", "java.lang.String"), "org.eclipse.core.internal.databinding.conversion.StatusToStringConverter"); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
			
			addNumberToByteConverters(converterMap, integerFormat, integerClasses);
			addNumberToByteConverters(converterMap, numberFormat, floatClasses);
			
			addNumberToShortConverters(converterMap, integerFormat, integerClasses);
			addNumberToShortConverters(converterMap, numberFormat, floatClasses);
			
			addNumberToIntegerConverters(converterMap, integerFormat, integerClasses);
			addNumberToIntegerConverters(converterMap, numberFormat, floatClasses);
			
			addNumberToLongConverters(converterMap, integerFormat, integerClasses);
			addNumberToLongConverters(converterMap, numberFormat, floatClasses);
			
			addNumberToFloatConverters(converterMap, integerFormat, integerClasses);
			addNumberToFloatConverters(converterMap, numberFormat, floatClasses);
			
			addNumberToDoubleConverters(converterMap, integerFormat, integerClasses);
			addNumberToDoubleConverters(converterMap, numberFormat, floatClasses);
			
			addNumberToBigIntegerConverters(converterMap, integerFormat, integerClasses);
			addNumberToBigIntegerConverters(converterMap, numberFormat, floatClasses);	
			
			addNumberToBigDecimalConverters(converterMap, integerFormat, integerClasses);
			addNumberToBigDecimalConverters(converterMap, numberFormat, floatClasses);
		}

		return converterMap;
	}
	
	private static final Class[] integerClasses = new Class[]{
		Byte.TYPE, Byte.class,
		Short.TYPE, Short.class, 
		Integer.TYPE, Integer.class,
		Long.TYPE, Long.class,
		BigInteger.class};

	private static final Class[] floatClasses = new Class[] {
		Float.TYPE, Float.class,
		Double.TYPE, Double.class,
		BigDecimal.class};
	
	/**
	 * Registers converters to boxed and unboxed types from a list of from classes.
	 * 
	 * @param map
	 * @param numberFormat
	 * @param fromTypes
	 */
	private static void addNumberToByteConverters(Map map,
			NumberFormat numberFormat, Class[] fromTypes) {
		
		for (int i = 0; i < fromTypes.length; i++) {
			Class fromType = fromTypes[i];
			if (!fromType.equals(Byte.class) && !fromType.equals(Byte.TYPE)) {
				String fromName = (fromType.isPrimitive()) ? getKeyForClass(fromType, null) : fromType.getName();
				
				map.put(new Pair(fromName, BYTE_TYPE), new NumberToByteConverter(numberFormat, fromType, true));
				map.put(new Pair(fromName, Byte.class.getName()), new NumberToByteConverter(numberFormat, fromType, false));
			}
		}
	}
	
	/**
	 * Registers converters to boxed and unboxed types from a list of from classes.
	 * 
	 * @param map
	 * @param numberFormat
	 * @param fromTypes
	 */
	private static void addNumberToShortConverters(Map map, NumberFormat numberFormat, Class[] fromTypes) {
		for (int i = 0; i < fromTypes.length; i++) {
			Class fromType = fromTypes[i];
			if (!fromType.equals(Short.class) && !fromType.equals(Short.TYPE)) {
				String fromName = (fromType.isPrimitive()) ? getKeyForClass(fromType, null) : fromType.getName();
				
				map.put(new Pair(fromName, SHORT_TYPE), new NumberToShortConverter(numberFormat, fromType, true));
				map.put(new Pair(fromName, Short.class.getName()), new NumberToShortConverter(numberFormat, fromType, false));
			}
		}
	}
	
	/**
	 * Registers converters to boxed and unboxed types from a list of from classes.
	 * 
	 * @param map
	 * @param numberFormat
	 * @param fromTypes
	 */
	private static void addNumberToIntegerConverters(Map map, NumberFormat numberFormat, Class[] fromTypes) {
		for (int i = 0; i < fromTypes.length; i++) {
			Class fromType = fromTypes[i];
			if (!fromType.equals(Integer.class) && !fromType.equals(Integer.TYPE)) {
				String fromName = (fromType.isPrimitive()) ? getKeyForClass(fromType, null) : fromType.getName();
				
				map.put(new Pair(fromName, INTEGER_TYPE), new NumberToIntegerConverter(numberFormat, fromType, true));
				map.put(new Pair(fromName, Integer.class.getName()), new NumberToIntegerConverter(numberFormat, fromType, false));
			}
		}
	}
	
	/**
	 * Registers converters to boxed and unboxed types from a list of from classes.
	 * 
	 * @param map
	 * @param numberFormat
	 * @param fromTypes
	 */
	private static void addNumberToLongConverters(Map map, NumberFormat numberFormat, Class[] fromTypes) {
		for (int i = 0; i < fromTypes.length; i++) {
			Class fromType = fromTypes[i];
			if (!fromType.equals(Long.class) && !fromType.equals(Long.TYPE)) {
				String fromName = (fromType.isPrimitive()) ? getKeyForClass(fromType, null) : fromType.getName();
				
				map.put(new Pair(fromName, LONG_TYPE), new NumberToLongConverter(numberFormat, fromType, true));
				map.put(new Pair(fromName, Long.class.getName()), new NumberToLongConverter(numberFormat, fromType, false));
			}
		}
	}

	/**
	 * Registers converters to boxed and unboxed types from a list of from classes.
	 * 
	 * @param map
	 * @param numberFormat
	 * @param fromTypes
	 */
	private static void addNumberToFloatConverters(Map map, NumberFormat numberFormat, Class[] fromTypes) {
		for (int i = 0; i < fromTypes.length; i++) {
			Class fromType = fromTypes[i];
			if (!fromType.equals(Float.class) && !fromType.equals(Float.TYPE)) {
				String fromName = (fromType.isPrimitive()) ? getKeyForClass(fromType, null) : fromType.getName();
				
				map.put(new Pair(fromName, FLOAT_TYPE), new NumberToFloatConverter(numberFormat, fromType, true));
				map.put(new Pair(fromName, Float.class.getName()), new NumberToFloatConverter(numberFormat, fromType, false));
			}
		}
	}
	
	/**
	 * Registers converters to boxed and unboxed types from a list of from classes.
	 * 
	 * @param map
	 * @param numberFormat
	 * @param fromTypes
	 */
	private static void addNumberToDoubleConverters(Map map, NumberFormat numberFormat, Class[] fromTypes) {
		for (int i = 0; i < fromTypes.length; i++) {
			Class fromType = fromTypes[i];
			if (!fromType.equals(Double.class) && !fromType.equals(Double.TYPE)) {
				String fromName = (fromType.isPrimitive()) ? getKeyForClass(fromType, null) : fromType.getName();
				
				map.put(new Pair(fromName, DOUBLE_TYPE), new NumberToDoubleConverter(numberFormat, fromType, true));
				map.put(new Pair(fromName, Double.class.getName()), new NumberToDoubleConverter(numberFormat, fromType, false));
			}
		}
	}

	/**
	 * Registers converters to boxed and unboxed types from a list of from classes.
	 * 
	 * @param map
	 * @param numberFormat
	 * @param fromTypes
	 */
	private static void addNumberToBigIntegerConverters(Map map, NumberFormat numberFormat, Class[] fromTypes) {
		for (int i = 0; i < fromTypes.length; i++) {
			Class fromType = fromTypes[i];
			if (!fromType.equals(BigInteger.class)) {
				String fromName = (fromType.isPrimitive()) ? getKeyForClass(fromType, null) : fromType.getName();
				
				map.put(new Pair(fromName, BigInteger.class.getName()), new NumberToBigIntegerConverter(numberFormat, fromType));
			}
		}
	}
	
	/**
	 * Registers converters to boxed and unboxed types from a list of from classes.
	 * 
	 * @param map
	 * @param numberFormat
	 * @param fromTypes
	 */
	private static void addNumberToBigDecimalConverters(Map map, NumberFormat numberFormat, Class[] fromTypes) {
		for (int i = 0; i < fromTypes.length; i++) {
			Class fromType = fromTypes[i];
			if (!fromType.equals(BigDecimal.class)) {
				String fromName = (fromType.isPrimitive()) ? getKeyForClass(fromType, null) : fromType.getName();
				
				map.put(new Pair(fromName, BigDecimal.class.getName()), new NumberToBigDecimalConverter(numberFormat, fromType));
			}
		}
	}
	
	private static String getKeyForClass(Object originalValue, Class filteredValue) {
		if (originalValue instanceof Class) {
			Class originalClass = (Class) originalValue;
			if (originalClass.equals(Integer.TYPE)) {
				return INTEGER_TYPE;
			} else if (originalClass.equals(Byte.TYPE)) {
				return BYTE_TYPE;
			} else if (originalClass.equals(Boolean.TYPE)) {
				return BOOLEAN_TYPE;
			} else if (originalClass.equals(Double.TYPE)) {
				return DOUBLE_TYPE;
			} else if (originalClass.equals(Float.TYPE)) {
				return FLOAT_TYPE;
			} else if (originalClass.equals(Long.TYPE)) {
				return LONG_TYPE;
			} else if (originalClass.equals(Short.TYPE)) {
				return SHORT_TYPE;
			}
		}
		return filteredValue.getName();
	}

	/**
	 * @param fromType
	 * @param toType
	 * @return whether fromType is assignable to toType
	 */
	protected Boolean isAssignableFromTo(Object fromType, Object toType) {
		if (fromType instanceof Class && toType instanceof Class) {
			Class toClass = (Class) toType;
			if (toClass.isPrimitive()) {
				toClass = autoboxed(toClass);
			}
			Class fromClass = (Class) fromType;
			if (fromClass.isPrimitive()) {
				fromClass = autoboxed(fromClass);
			}
			return toClass.isAssignableFrom(fromClass) ? Boolean.TRUE
					: Boolean.FALSE;
		}
		return null;
	}

	/*
	 * Default converter implementation, does not perform any conversion.
	 */
	protected static final class DefaultConverter implements IConverter {

		private final Object toType;

		private final Object fromType;

		/**
		 * @param fromType
		 * @param toType
		 */
		DefaultConverter(Object fromType, Object toType) {
			this.toType = toType;
			this.fromType = fromType;
		}

		public Object convert(Object fromObject) {
			return fromObject;
		}

		public Object getFromType() {
			return fromType;
		}

		public Object getToType() {
			return toType;
		}
	}

}
