All Downloads are FREE. Search and download functionalities are using the official Maven repository.

flexjson.ObjectBinder Maven / Gradle / Ivy

package flexjson;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import flexjson.factories.ArrayObjectFactory;
import flexjson.factories.BeanObjectFactory;
import flexjson.factories.BooleanObjectFactory;
import flexjson.factories.ByteObjectFactory;
import flexjson.factories.CharacterObjectFactory;
import flexjson.factories.ClassLocatorObjectFactory;
import flexjson.factories.ClassObjectFactory;
import flexjson.factories.DoubleObjectFactory;
import flexjson.factories.EnumObjectFactory;
import flexjson.factories.FloatObjectFactory;
import flexjson.factories.IntegerObjectFactory;
import flexjson.factories.ListObjectFactory;
import flexjson.factories.LongObjectFactory;
import flexjson.factories.MapObjectFactory;
import flexjson.factories.SetObjectFactory;
import flexjson.factories.ShortObjectFactory;
import flexjson.factories.StringObjectFactory;

public class ObjectBinder
{

	private Stack objectStack= new Stack();
	private Stack jsonStack= new Stack();
	private Path currentPath= new Path();
	private Map factories;
	private Map pathFactories= new HashMap();
	public Map references= new HashMap();

	public Map getReferences()
	{
		return references;
	}

	public ObjectBinder()
	{
		factories= new HashMap();
		factories.put(Class.class, new ClassObjectFactory());
		factories.put(Object.class, new BeanObjectFactory());
		factories.put(Collection.class, new ListObjectFactory());
		factories.put(List.class, new ListObjectFactory());
		factories.put(Set.class, new SetObjectFactory());
		//        factories.put( SortedSet.class, new SortedSetObjectFactory() );
		factories.put(Map.class, new MapObjectFactory());
		factories.put(Integer.class, new IntegerObjectFactory());
		factories.put(int.class, new IntegerObjectFactory());
		factories.put(Float.class, new FloatObjectFactory());
		factories.put(float.class, new FloatObjectFactory());
		factories.put(Double.class, new DoubleObjectFactory());
		factories.put(double.class, new DoubleObjectFactory());
		factories.put(Short.class, new ShortObjectFactory());
		factories.put(short.class, new ShortObjectFactory());
		factories.put(Long.class, new LongObjectFactory());
		factories.put(long.class, new LongObjectFactory());
		factories.put(Byte.class, new ByteObjectFactory());
		factories.put(byte.class, new ByteObjectFactory());
		factories.put(Boolean.class, new BooleanObjectFactory());
		factories.put(boolean.class, new BooleanObjectFactory());
		factories.put(Character.class, new CharacterObjectFactory());
		factories.put(char.class, new CharacterObjectFactory());
		factories.put(Enum.class, new EnumObjectFactory());
		//        factories.put( Date.class, new DateObjectFactory() );
		factories.put(String.class, new StringObjectFactory());
		factories.put(Array.class, new ArrayObjectFactory());
		//        factories.put( BigDecimal.class, new BigDecimalFactory() );
		//        factories.put( BigInteger.class, new BigIntegerFactory() );
	}

	public ObjectBinder use(Path path, ObjectFactory factory)
	{
		pathFactories.put(path, factory);
		return this;
	}

	public ObjectBinder use(Object clazz, ObjectFactory factory)
	{
		factories.put(clazz, factory);
		return this;
	}

	public Path getCurrentPath()
	{
		return currentPath;
	}

	public Object bind(Object input)
	{
		return this.bind(input, null);
	}

	public Object bind(Object source, Object target)
	{
		if (target instanceof Map)
		{
			bindIntoMap((Map) source, (Map) target, null, null);
		}
		else if (target instanceof Collection)
		{
			bindIntoCollection((Collection) source, (Collection) target, null);
		}
		else
		{
			bindIntoObject((Map) source, target, target.getClass());
		}
		return target;
	}

	public Object bind(Object input, Type targetType)
	{
		jsonStack.add(input);
		try
		{
			if (input == null)
				return null;
			Class targetClass= findClassName(input, getTargetClass(targetType));
			ObjectFactory factory= findFactoryFor(targetClass);
			if (factory == null)
				throw new JSONException(currentPath + ": + Could not find a suitable ObjectFactory for " + targetClass);
			return factory.instantiate(this, input, targetType, targetClass);
		}
		finally
		{
			jsonStack.pop();
		}
	}

	public > T bindIntoCollection(Collection value, T target, Type targetType)
	{
		Type valueType= null;
		if (targetType instanceof ParameterizedType)
		{
			valueType= ((ParameterizedType) targetType).getActualTypeArguments()[0];
		}
		jsonStack.add(value);
		objectStack.add(target);
		getCurrentPath().enqueue("values");
		for (Object obj : value)
		{
			target.add(bind(obj, valueType));
		}
		getCurrentPath().pop();
		objectStack.pop();
		jsonStack.pop();
		return target;
	}

	public Object bindIntoMap(Map input, Map result, Type keyType, Type valueType)
	{
		jsonStack.add(input);
		objectStack.add(result);
		for (Object inputKey : input.keySet())
		{
			currentPath.enqueue("keys");
			Object key= bind(inputKey, keyType);
			currentPath.pop();
			currentPath.enqueue("values");
			Object value= bind(input.get(inputKey), valueType);
			currentPath.pop();
			result.put(key, value);
		}
		objectStack.pop();
		jsonStack.pop();
		return result;
	}

	public Object bindIntoObject(Map jsonOwner, Object target, Type targetType)
	{
		try
		{
			objectStack.add(target);
			BeanAnalyzer analyzer= BeanAnalyzer.analyze(target.getClass());
			for (BeanProperty descriptor : analyzer.getProperties())
			{
				Object value= findFieldInJson(jsonOwner, descriptor);
				if (value != null)
				{
					currentPath.enqueue(descriptor.getName());
					Method setMethod= descriptor.getWriteMethod();
					if (setMethod != null)
					{
						Type[] types= setMethod.getGenericParameterTypes();
						if (types.length == 1)
						{
							Type paramType= types[0];
							setMethod.invoke(objectStack.peek(), bind(value, resolveParameterizedTypes(paramType, targetType)));
						}
						else
						{
							throw new JSONException(currentPath + ":  Expected a single parameter for method " + target.getClass().getName() + "." + setMethod.getName() + " but got " + types.length);
						}
					}
					else
					{
						Field field= descriptor.getProperty();
						if (field != null)
						{
							field.setAccessible(true);
							field.set(target, bind(value, field.getGenericType()));
						}
					}
					currentPath.pop();
				}
			}
			return objectStack.pop();
		}
		catch (IllegalAccessException e)
		{
			throw new JSONException(currentPath + ":  Could not access the no-arg constructor for " + target.getClass().getName(), e);
		}
		catch (InvocationTargetException ex)
		{
			throw new JSONException(currentPath + ":  Exception while trying to invoke setter method.", ex);
		}
	}

	public JSONException cannotConvertValueToTargetType(Object value, Class targetType)
	{
		return new JSONException(String.format("%s:  Can not convert %s into %s", currentPath, value.getClass().getName(), targetType.getName()));
	}

	private Class getTargetClass(Type targetType)
	{
		if (targetType == null)
		{
			return null;
		}
		else if (targetType instanceof Class)
		{
			return (Class) targetType;
		}
		else if (targetType instanceof ParameterizedType)
		{
			return (Class) ((ParameterizedType) targetType).getRawType();
		}
		else if (targetType instanceof GenericArrayType)
		{
			return Array.class;
		}
		else if (targetType instanceof WildcardType)
		{
			return null; // nothing you can do about these.  User will have to specify this with use()
		}
		else if (targetType instanceof TypeVariable)
		{
			return null; // nothing you can do about these.  User will have to specify this with use()
		}
		else
		{
			throw new JSONException(currentPath + ":  Unknown type " + targetType);
		}
	}

	private Type resolveParameterizedTypes(Type genericType, Type targetType)
	{
		if (genericType instanceof Class)
		{
			return genericType;
		}
		else if (genericType instanceof ParameterizedType)
		{
			return genericType;
		}
		else if (genericType instanceof TypeVariable)
		{
			return targetType;
		}
		else if (genericType instanceof WildcardType)
		{
			return targetType;
		}
		else if (genericType instanceof GenericArrayType)
		{
			return ((GenericArrayType) genericType).getGenericComponentType();
		}
		else
		{
			throw new JSONException(currentPath + ":  Unknown generic type " + genericType + ".");
		}
	}

	private Class findClassName(Object map, Class targetType) throws JSONException
	{
		if (!pathFactories.containsKey(currentPath))
		{
			Class mostSpecificType= useMostSpecific(targetType, map instanceof Map ? findClassInMap((Map) map, null) : null);
			if (mostSpecificType == null)
			{
				return map.getClass();
			}
			else
			{
				return mostSpecificType;
			}
		}
		else
		{
			return null;
		}
	}

	protected Class useMostSpecific(Class classFromTarget, Class typeFound)
	{
		if (classFromTarget != null && typeFound != null)
		{
			return typeFound.isAssignableFrom(classFromTarget) ? classFromTarget : typeFound;
		}
		else if (typeFound != null)
		{
			return typeFound;
		}
		else if (classFromTarget != null)
		{
			return classFromTarget;
		}
		else
		{
			return null;
		}
	}

	protected Class findClassInMap(Map map, Class override)
	{
		if (override == null)
		{
			Integer reference= (Integer) map.get("@ref");
			if (reference != null)
				return getReferences().get(reference).getClass();

			String classname= (String) map.remove("class");
			try
			{
				if (classname != null)
				{
					return Class.forName(classname);
				}
				else
				{
					return null;
				}
			}
			catch (ClassNotFoundException e)
			{
				throw new JSONException( /*String.format( "%s:  Could not load %s", currentPath, classname ), */e);
			}
		}
		else
		{
			return override;
		}
	}

	private ObjectFactory findFactoryFor(Class targetType)
	{
		ObjectFactory factory= pathFactories.get(currentPath);
		if (factory == null)
		{
			if (targetType != null && targetType.isArray())
				return factories.get(Array.class);
			return findFactoryByTargetClass(targetType);
		}
		return factory;
	}

	private ObjectFactory findFactoryByTargetClass(Class targetType)
	{
		ObjectFactory factory;
		factory= factories.get(targetType);
		if (factory == null && targetType != null && targetType.getSuperclass() != null)
		{
			for (Class intf : targetType.getInterfaces())
			{
				factory= findFactoryByTargetClass(intf);
				if (factory != null)
					return factory;
			}
			return findFactoryByTargetClass(targetType.getSuperclass());
		}
		else
		{
			return factory;
		}
	}

	protected Object instantiate(Class clazz)
	{
		try
		{
			Constructor constructor= clazz.getDeclaredConstructor();
			constructor.setAccessible(true);
			return constructor.newInstance();
		}
		catch (InstantiationException e)
		{
			throw new JSONException(currentPath + ":There was an exception trying to instantiate an instance of " + clazz.getName(), e);
		}
		catch (IllegalAccessException e)
		{
			throw new JSONException(currentPath + ":There was an exception trying to instantiate an instance of " + clazz.getName(), e);
		}
		catch (InvocationTargetException e)
		{
			throw new JSONException(currentPath + ":There was an exception trying to instantiate an instance of " + clazz.getName(), e);
		}
		catch (NoSuchMethodException e)
		{
			throw new JSONException(currentPath + ": " + clazz.getName() + " lacks a no argument constructor.  Flexjson will instantiate any protected, private, or public no-arg constructor.", e);
		}
	}

	private Object findFieldInJson(Map map, BeanProperty property)
	{
		Object value= map.get(property.getName());
		if (value == null)
		{
			String field= property.getName();
			value= map.get(upperCase(field));
		}

		return value;
	}

	private String upperCase(String field)
	{
		return Character.toUpperCase(field.charAt(0)) + field.substring(1);
	}

	public Object getTarget()
	{
		return objectStack.peek();
	}

	public Object getSource()
	{
		return jsonStack.peek();
	}

	public Object bindPrimitive(Object value, Class clazz)
	{
		if (value.getClass() == clazz)
		{
			return value;
		}
		else if (value instanceof Number && clazz.equals(Double.class))
		{
			return ((Number) value).doubleValue();
		}
		else if (value instanceof Number && clazz.equals(Integer.class))
		{
			return ((Number) value).intValue();
		}
		else if (value instanceof Number && clazz.equals(Long.class))
		{
			return ((Number) value).longValue();
		}
		else if (value instanceof Number && clazz.equals(Short.class))
		{
			return ((Number) value).shortValue();
		}
		else if (value instanceof Number && clazz.equals(Byte.class))
		{
			return ((Number) value).byteValue();
		}
		else if (value instanceof Number && clazz.equals(Float.class))
		{
			return ((Number) value).floatValue();
		}
		else if (value instanceof Boolean && clazz.equals(Boolean.class))
		{
			return value;
		}
		else if (value instanceof Long && clazz == Date.class)
		{
			return new Date((Long) value);
		}
		else
		{
			throw new JSONException(String.format("%s: Don't know how to bind %s into class %s.  You might need to use an ObjectFactory instead of a plain class.", getCurrentPath().toString(), value, clazz.getName()));
		}
	}

	public Class findClassAtPath(Path currentPath) throws ClassNotFoundException
	{
		ObjectFactory factory= pathFactories.get(currentPath);
		if (factory instanceof ClassLocatorObjectFactory)
		{
			return ((ClassLocatorObjectFactory) factory).getLocator().locate(this, currentPath);
		}
		else
		{
			return null;
		}
	}
}