Open Source Repository

Home /json/flexjson-2.1 | Repository Home



flexjson/ObjectBinder.java
package flexjson;

import flexjson.factories.*;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.lang.reflect.*;

public class ObjectBinder {

    private LinkedList<Object> objectStack = new LinkedList<Object>();
    private LinkedList<Object> jsonStack = new LinkedList<Object>();
    private Path currentPath = new Path();
    private Map<Class,ObjectFactory> factories;
    private Map<Path,ObjectFactory> pathFactories = new HashMap<Path,ObjectFactory>();

    public ObjectBinder() {
        factories = new HashMap<Class,ObjectFactory>();
        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(Class 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<Object,Object>)target, null, null );
        } else if( target instanceof Collection ) {
            bindIntoCollection( (Collection)source, (Collection<Object>)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.removeLast();
        }
    }

    public <T extends Collection<Object>> 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.removeLast();
        jsonStack.removeLast();
        return target;
    }

    public Object bindIntoMap(Map input, Map<Object, Object> 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.removeLast();
        jsonStack.removeLast();
        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.getLast(), 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.removeLast();
        } 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 ) {
            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.getLast();
    }

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

    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;
        }
    }
}