Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.boon.core.reflection.MapperSimple Maven / Gradle / Ivy
package org.boon.core.reflection;
import org.boon.Boon;
import org.boon.Lists;
import org.boon.core.Conversions;
import org.boon.core.Typ;
import org.boon.core.TypeType;
import org.boon.core.Value;
import org.boon.core.reflection.fields.FieldAccess;
import org.boon.core.reflection.fields.FieldAccessMode;
import org.boon.core.reflection.fields.FieldsAccessor;
import org.boon.core.value.ValueContainer;
import org.boon.core.value.ValueList;
import org.boon.core.value.ValueMap;
import org.boon.core.value.ValueMapImpl;
import org.boon.primitive.Arry;
import org.boon.primitive.CharBuf;
import java.lang.reflect.Array;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import static org.boon.Boon.className;
import static org.boon.Boon.puts;
import static org.boon.Boon.sputs;
import static org.boon.Exceptions.die;
import static org.boon.Exceptions.handle;
import static org.boon.core.Conversions.coerce;
import static org.boon.core.Conversions.toEnum;
import static org.boon.core.TypeType.*;
/**
* Created by Richard on 9/18/14.
*/
public class MapperSimple implements Mapper {
private FieldsAccessor fieldsAccessor;
public MapperSimple() {
this.fieldsAccessor = FieldAccessMode.FIELD_THEN_PROPERTY.create(true);
}
public MapperSimple(FieldsAccessor fieldsAccessor) {
this.fieldsAccessor = fieldsAccessor;
}
/**
* This converts a list of maps to objects.
* I always forget that this exists. I need to remember.
*
* @param list the input list
* @param generics
* @return a new list
*/
@Override
public List convertListOfMapsToObjects(List list, Class componentType) {
List newList = new ArrayList<>( list.size() );
for ( Object obj : list ) {
if ( obj instanceof Value) {
obj = ( ( Value ) obj ).toValue();
}
if ( obj instanceof Map ) {
Map map = ( Map ) obj;
if ( map instanceof ValueMapImpl) {
newList.add( fromValueMap( ( Map ) map, componentType ) );
} else {
newList.add( fromMap( map, componentType ) );
}
} else {
newList.add( Conversions.coerce(componentType, obj) );
}
}
return ( List ) newList;
}
/**
* fromMap converts a map into a java object
* @param map map to create the object from.
* @param cls class type of new object
* @param map to create teh object from.
* @return new object of type cls
*/
@Override
public T fromMap(final Map map, final Class cls) {
T toObject = Reflection.newInstance( cls );
Map fields = fieldsAccessor.getFields( toObject.getClass() );
Set> mapKeyValuesEntrySet = map.entrySet();
/* Iterate through the map keys/values. */
for ( Map.Entry mapEntry : mapKeyValuesEntrySet ) {
/* Get the field name. */
String key = mapEntry.getKey();
/* Get the field and if it missing then ignore this map entry. */
FieldAccess field = fields.get( fieldsAccessor.isCaseInsensitive() ? key.toLowerCase() : key );
if ( field == null ) {
continue;
}
if ( field.ignore() ) {
continue;
}
/* Get the value from the map. */
Object value = mapEntry.getValue();
/* If the value is a Value (a index overlay), then convert ensure it is not a container and inject
it into the field, and we are done so continue.
*/
if ( value instanceof Value ) {
if ( ( ( Value ) value ).isContainer() ) {
value = ( ( Value ) value ).toValue();
} else {
field.setFromValue( toObject, ( Value ) value );
continue;
}
}
/* If the value is null, then inject an null value into the field.
* Notice we do not check to see if the field is a primitive, if
* it is we die which is the expected behavior.
*/
if ( value == null ) {
field.setObject( toObject, null );
continue;
}
/* if the value's type and the field type are the same or
the field just takes an object, then inject what we have as is.
*/
if ( value.getClass() == field.type() || field.type() == Object.class) {
field.setValue(toObject, value);
} else if ( Typ.isBasicType(value) ) {
field.setValue(toObject, value);
}
/* See if it is a map, and if it is then process it.
* REFACTOR:
* It looks like we are using some utility classes here that we could have used in
* oldMatchAndConvertArgs.
* REFACTOR
* */
else if ( value instanceof Map ) {
setFieldValueFromMap(toObject, field, (Map)value);
} else if ( value instanceof Collection) {
/*It is a collection so process it that way. */
processCollectionFromMapUsingFields( toObject, field, ( Collection ) value);
} else if ( value instanceof Map[] ) {
/* It is an array of maps so, we need to process it as such. */
processArrayOfMaps(toObject, field, ( Map[] )value );
} else {
/* If we could not determine how to convert it into some field
object then we just go ahead an inject it using setValue which
will call Conversion.coerce.
*/
field.setValue( toObject, value );
}
}
return toObject;
}
/** Convert an item from a list into a class using the classes constructor.
*
* REFACTOR: Can't this just be from collection?
* REFACTOR
*
* @param argList list if arguments
* @param clazz the type of the object we are creating
* @param generics
* @return the new object that we just created.
*/
@Override
public T fromList(List argList, Class clazz) {
/* Size of the arguments. */
int size = argList.size();
/* Meta data holder of the class. */
ClassMeta classMeta = ClassMeta.classMeta( clazz );
/* The constructor to match. */
ConstructorAccess constructorToMatch = null;
/* The final arguments. */
Object[] finalArgs = null;
boolean[] flag = new boolean[1];
List convertedArguments = null;
try {
/* List to hold items that we coerce into parameter types. */
convertedArguments = new ArrayList<>( argList );
constructorToMatch = lookupConstructorMeta( size,
convertedArguments, classMeta, constructorToMatch, flag, false);
/* List to hold items that we coerce into parameter types. */
if (constructorToMatch == null) {
convertedArguments = new ArrayList<>( argList );
constructorToMatch = lookupConstructorMeta( size,
convertedArguments, classMeta, constructorToMatch, flag, true);
}
/* If we were not able to match then we bail. */
if ( constructorToMatch != null ) {
finalArgs = convertedArguments.toArray( new Object[argList.size()] );
return constructorToMatch.create( finalArgs );
} else {
return (T) die(Object.class, "Unable to convert list", convertedArguments, "into", clazz);
}
/* Catch all of the exceptions and try to report why this failed.
* Since we are doing reflection and a bit of "magic", we have to be clear as to why/how things failed.
* */
} catch ( Exception e ) {
if (constructorToMatch != null) {
CharBuf buf = CharBuf.create(200);
buf.addLine();
buf.multiply('-', 10).add("FINAL ARGUMENTS").multiply('-', 10).addLine();
if (finalArgs!=null) {
for (Object o : finalArgs) {
buf.puts("argument type ", className(o));
}
}
buf.multiply('-', 10).add("CONSTRUCTOR").add(constructorToMatch).multiply('-', 10).addLine();
buf.multiply('-', 10).add("CONSTRUCTOR PARAMS").multiply('-', 10).addLine();
for (Class c : constructorToMatch.parameterTypes()) {
buf.puts("constructor type ", c);
}
buf.multiply('-', 35).addLine();
if (Boon.debugOn()) {
puts(buf);
}
buf.addLine("PARAMETER TYPES");
buf.add(Lists.list(constructorToMatch.parameterTypes())).addLine();
buf.addLine("ORIGINAL TYPES PASSED");
buf.add(gatherTypes(convertedArguments)).addLine();
buf.add(gatherActualTypes(convertedArguments)).addLine();
buf.addLine("CONVERTED ARGUMENT TYPES");
buf.add(gatherTypes(convertedArguments)).addLine();
buf.add(gatherActualTypes(convertedArguments)).addLine();
Boon.error( e, "unable to create object based on constructor", buf );
return ( T ) handle(Object.class, e, buf.toString());
} else {
return ( T ) handle(Object.class, e,
"\nlist args after conversion", convertedArguments, "types",
gatherTypes(convertedArguments),
"\noriginal args", argList,
"original types", gatherTypes(argList));
}
}
}
/**
* Processes an array of maps.
* @param newInstance new instance we are injecting field into
* @param field field we are injecting a value into
*/
private void processArrayOfMaps( Object newInstance, FieldAccess field, Map[] maps) {
List> list = Lists.list(maps);
handleCollectionOfMaps( newInstance, field,
list);
}
/**
* Processes an collection of maps.
* @param newInstance new instance we are injecting field into
* @param field field we are injecting a value into
*/
@SuppressWarnings("unchecked")
private void handleCollectionOfMaps( Object newInstance,
FieldAccess field, Collection> collectionOfMaps
) {
Collection newCollection = Conversions.createCollection( field.type(), collectionOfMaps.size() );
Class componentClass = field.getComponentClass();
if ( componentClass != null ) {
for ( Map mapComponent : collectionOfMaps ) {
newCollection.add( fromMap( mapComponent, componentClass ) );
}
field.setObject( newInstance, newCollection );
}
}
private ConstructorAccess lookupConstructorMeta(int size,
List convertedArguments,
ClassMeta classMeta,
ConstructorAccess constructorToMatch,
boolean[] flag, boolean loose) {
/* Iterate through the constructors and see if one matches the arguments passed after coercion. */
loop:
for ( ConstructorAccess constructor : classMeta.constructors() ) {
/* Get the parameters on the constructor and see if the size matches what was passed. */
Class[] parameterTypes = constructor.parameterTypes();
if ( parameterTypes.length == size ) {
/* Iterate through each parameter and see if it can be converted. */
for ( int index = 0; index < size; index++ ) {
/* The match and convert does the bulk of the work. */
if ( !matchAndConvertArgs( convertedArguments, constructor,
parameterTypes, index, flag, loose ) ) continue loop;
}
constructorToMatch = constructor;
}
}
return constructorToMatch;
}
/**
* This converts/coerce a constructor argument to the given parameter type.
*
* REFACTOR:
* This method was automatically refactored and its functionality gets duplicated in a few places.
* Namely Invoker lib. It needs to be documented. Refactored to use org.boon.core.TypeType.
* And code coverage. I have used it on several projects and have modified to work on
* edge cases for certain customers and have not updated the unit test.
* This method is beastly and important. It is currently 250 lines of code.
* It started off small, and kept getting added to. It needs love, but it was a bitch to write.
* REFACTOR
*
* @param convertedArgumentList arguments being converted to match parameter types
* @param methodAccess constructor
* @param parameterTypes parameterTypes
* @param index index of argument
* @return true or false
*/
private boolean matchAndConvertArgs( List convertedArgumentList,
BaseAccess methodAccess,
Class[] parameterTypes,
int index,
boolean[] flag, boolean loose) {
Object value = null;
try {
Class parameterClass;
Object item;
parameterClass = parameterTypes[index];
item = convertedArgumentList.get( index );
final TypeType parameterType = TypeType.getType(parameterClass);
if ( item instanceof ValueContainer) {
item = ( ( ValueContainer ) item ).toValue();
convertedArgumentList.set( index, item );
}
if (item == null) {
return true;
}
switch (parameterType) {
case INT:
case SHORT:
case BYTE:
case BOOLEAN:
case CHAR:
case FLOAT:
case DOUBLE:
case LONG:
if (item == null) {
return false;
}
case INTEGER_WRAPPER:
case BYTE_WRAPPER:
case SHORT_WRAPPER:
case BOOLEAN_WRAPPER:
case CHAR_WRAPPER:
case FLOAT_WRAPPER:
case DOUBLE_WRAPPER:
case CHAR_SEQUENCE:
case NUMBER:
case LONG_WRAPPER:
if (!loose ) {
if (item instanceof Number) {
value = Conversions.coerceWithFlag(parameterType, parameterClass, flag, item );
convertedArgumentList.set( index, value );
return flag[0];
} else {
return false;
}
} else {
value = Conversions.coerceWithFlag(parameterType, parameterClass, flag, item );
convertedArgumentList.set( index, value );
return flag[0];
}
case ENUM:
if (item instanceof Enum) {
return true;
}
if (item instanceof CharSequence) {
value = toEnum(parameterClass, item.toString());
convertedArgumentList.set( index, value );
return value!=null;
} else if (item instanceof Number){
value = toEnum(parameterClass, ((Number)item).intValue());
convertedArgumentList.set( index, value );
return value!=null;
} else {
return false;
}
case CLASS:
if (item instanceof Class) {
return true;
}
value = Conversions.coerceWithFlag(parameterType, parameterClass, flag, item );
convertedArgumentList.set( index, value );
return flag[0];
case STRING:
if (item instanceof String) {
return true;
}
if (item instanceof CharSequence) {
value = item.toString();
convertedArgumentList.set( index, value );
return true;
} else if (loose) {
value = item.toString();
convertedArgumentList.set( index, value );
return true;
} else {
return false;
}
case MAP:
case VALUE_MAP:
if (item instanceof Map) {
Map itemMap = (Map)item;
/* This code creates a map based on the parameterized types of the constructor arg.
* This does ninja level generics manipulations and needs to be captured in some
* reusable way.
* */
Type type = methodAccess.getGenericParameterTypes()[index];
if ( type instanceof ParameterizedType) {
ParameterizedType pType = (ParameterizedType) type;
Class keyType = (Class) pType.getActualTypeArguments()[0];
Class valueType = (Class) pType.getActualTypeArguments()[1];
Map newMap = Conversions.createMap(parameterClass, itemMap.size());
/* Iterate through the map items and convert the keys/values to match
the parameterized constructor parameter args.
*/
for (Object o : itemMap.entrySet()) {
Map.Entry entry = (Map.Entry) o;
Object key = entry.getKey();
value = entry.getValue();
key = ValueContainer.toObject(key);
value = ValueContainer.toObject(value);
/* Here is the actual conversion from a list or a map of some object.
This can be captured in helper method the duplication is obvious.
*/
if (value instanceof List) {
value = fromList((List) value, valueType);
} else if (value instanceof Map) {
value = fromMap((Map) value, valueType);
} else {
value = coerce(valueType, value);
}
if (key instanceof List) {
key = fromList((List) key, keyType);
} else if (value instanceof Map) {
key = fromMap((Map) key, keyType);
} else {
key = coerce(keyType, key);
}
newMap.put(key, value);
}
convertedArgumentList.set(index, newMap);
return true;
}
}
break;
case INSTANCE:
if ( parameterClass.isInstance( item ) ) {
return true;
}
if (item instanceof Map) {
item = fromMap( ( Map ) item, parameterClass );
convertedArgumentList.set( index, item );
return true;
} else if ( item instanceof List ) {
List listItem = null;
listItem = ( List ) item;
value = fromList(listItem, parameterClass );
convertedArgumentList.set( index, value );
return true;
} else {
convertedArgumentList.set( index, coerce( parameterClass, item ) );
return true;
}
//break;
case INTERFACE:
case ABSTRACT:
if ( parameterClass.isInstance( item ) ) {
return true;
}
if (item instanceof Map) {
/** Handle conversion of user define interfaces. */
String className = (String) ((Map) item).get("class");
if (className != null) {
item = fromMap( (Map) item, Reflection.loadClass(className));
convertedArgumentList.set(index, item);
return true;
} else {
return false;
}
}
break;
case ARRAY:
case ARRAY_INT:
case ARRAY_BYTE:
case ARRAY_SHORT:
case ARRAY_FLOAT:
case ARRAY_DOUBLE:
case ARRAY_LONG:
case ARRAY_STRING:
case ARRAY_OBJECT:
item = Conversions.toList(item);
case SET:
case COLLECTION:
case LIST:
if (item instanceof List ) {
List itemList = ( List ) item;
/* Items have stuff in it, the item is a list of lists.
* This is like we did earlier with the map.
* Here is some more ninja generics Java programming that needs to be captured in one place.
* */
if ( itemList.size() > 0 && (itemList.get( 0 ) instanceof List ||
itemList.get(0) instanceof ValueContainer) ) {
/** Grab the generic type of the list. */
Type type = methodAccess.getGenericParameterTypes()[index];
/* Try to pull the generic type information out so you can create
a strongly typed list to inject.
*/
if ( type instanceof ParameterizedType ) {
ParameterizedType pType = ( ParameterizedType ) type;
Class componentType;
if (! (pType.getActualTypeArguments()[0] instanceof Class)) {
componentType = Object.class;
} else {
componentType = (Class) pType.getActualTypeArguments()[0];
}
Collection newList = Conversions.createCollection( parameterClass, itemList.size() );
for ( Object o : itemList ) {
if ( o instanceof ValueContainer ) {
o = ( ( ValueContainer ) o ).toValue();
}
if (componentType==Object.class) {
newList.add(o);
} else {
List fromList = ( List ) o;
o = fromList( fromList, componentType );
newList.add( o );
}
}
convertedArgumentList.set( index, newList );
return true;
}
} else {
/* Just a list not a list of lists so see if it has generics and pull out the
* type information and created a strong typed list. This looks a bit familiar.
* There is a big opportunity for some reuse here. */
Type type = methodAccess.getGenericParameterTypes()[index];
if ( type instanceof ParameterizedType ) {
ParameterizedType pType = ( ParameterizedType ) type;
Class componentType = pType.getActualTypeArguments()[0] instanceof Class ? (Class) pType.getActualTypeArguments()[0] : Object.class;
Collection newList = Conversions.createCollection( parameterClass, itemList.size() );
for ( Object o : itemList ) {
if ( o instanceof ValueContainer ) {
o = ( ( ValueContainer ) o ).toValue();
}
if (o instanceof List) {
if (componentType != Object.class) {
List fromList = ( List ) o;
o = fromList(fromList, componentType);
}
newList.add( o );
} else if (o instanceof Map) {
Map fromMap = ( Map ) o;
o = fromMap( fromMap, componentType );
newList.add( o );
} else {
newList.add( Conversions.coerce(componentType, o));
}
}
convertedArgumentList.set( index, newList );
return true;
}
}
}
return false;
default:
final TypeType itemType = TypeType.getInstanceType(item);
switch (itemType) {
case LIST:
convertedArgumentList.set(index, fromList((List) item, parameterClass));
return true;
case MAP:
case VALUE_MAP:
convertedArgumentList.set(index, fromMap( (Map) item, parameterClass));
return true;
case NUMBER:
case BOOLEAN:
case INT:
case SHORT:
case BYTE:
case FLOAT:
case DOUBLE:
case LONG:
case DOUBLE_WRAPPER:
case FLOAT_WRAPPER:
case INTEGER_WRAPPER:
case SHORT_WRAPPER:
case BOOLEAN_WRAPPER:
case BYTE_WRAPPER:
case LONG_WRAPPER:
case CLASS:
case VALUE:
value = Conversions.coerceWithFlag( parameterClass, flag, item );
if (flag[0] == false) {
return false;
}
convertedArgumentList.set( index, value );
return true;
case CHAR_SEQUENCE:
case STRING:
value = Conversions.coerceWithFlag( parameterClass, flag, item );
if (flag[0] == false) {
return false;
}
convertedArgumentList.set( index, value );
return true;
}
}
if ( parameterClass.isInstance( item ) ) {
return true;
}
} catch (Exception ex) {
Boon.error(ex, "PROBLEM WITH oldMatchAndConvertArgs",
"fieldsAccessor", fieldsAccessor, "list", convertedArgumentList,
"constructor", methodAccess, "parameters", parameterTypes,
"index", index);
return false;
}
return false;
}
/**
* Processes an collection of maps.
* This can inject into an array and appears to be using some of the TypeType lib.
* @param newInstance new instance we are injecting field into
* @param field field we are injecting a value into
*/
@SuppressWarnings("unchecked")
private void handleCollectionOfValues(
Object newInstance,
FieldAccess field, Collection acollectionOfValues ) {
Collection collectionOfValues = acollectionOfValues;
if (null == collectionOfValues) {
field.setObject(newInstance, null);
return;
}
if(field.typeEnum() == INSTANCE) {
field.setObject(newInstance, fromList((List) acollectionOfValues, field.type()));
return;
}
if ( collectionOfValues instanceof ValueList) {
collectionOfValues = ( ( ValueList ) collectionOfValues ).list();
}
Class componentClass = field.getComponentClass();
/** If the field is a collection than try to convert the items in the collection to
* the field type.
*/
switch (field.typeEnum() ) {
case LIST:
case SET:
case COLLECTION:
Collection newCollection = Conversions.createCollection( field.type(), collectionOfValues.size() );
for ( Value value : ( List ) collectionOfValues ) {
if ( value.isContainer() ) {
Object oValue = value.toValue();
if ( oValue instanceof Map ) {
newCollection.add( fromValueMap( ( Map ) oValue, componentClass ) );
}
} else {
newCollection.add( Conversions.coerce( componentClass, value.toValue() ) );
}
}
field.setObject( newInstance, newCollection );
break;
case ARRAY:
case ARRAY_INT:
case ARRAY_BYTE:
case ARRAY_SHORT:
case ARRAY_FLOAT:
case ARRAY_DOUBLE:
case ARRAY_LONG:
case ARRAY_STRING:
case ARRAY_OBJECT:
TypeType componentType = field.componentType();
int index = 0;
switch (componentType) {
case INT:
int [] iarray = new int[collectionOfValues.size()];
for ( Value value : ( List ) collectionOfValues ) {
iarray[index] = value.intValue();
index++;
}
field.setObject( newInstance, iarray);
return;
case SHORT:
short [] sarray = new short[collectionOfValues.size()];
for ( Value value : ( List ) collectionOfValues ) {
sarray[index] = value.shortValue();
index++;
}
field.setObject( newInstance, sarray);
return;
case DOUBLE:
double [] darray = new double[collectionOfValues.size()];
for ( Value value : ( List ) collectionOfValues ) {
darray[index] = value.doubleValue();
index++;
}
field.setObject( newInstance, darray);
return;
case FLOAT:
float [] farray = new float[collectionOfValues.size()];
for ( Value value : ( List ) collectionOfValues ) {
farray[index] = value.floatValue();
index++;
}
field.setObject( newInstance, farray);
return;
case LONG:
long [] larray = new long[collectionOfValues.size()];
for ( Value value : ( List ) collectionOfValues ) {
larray[index] = value.longValue();
index++;
}
field.setObject( newInstance, larray);
return;
case BYTE:
byte [] barray = new byte[collectionOfValues.size()];
for ( Value value : ( List ) collectionOfValues ) {
barray[index] = value.byteValue();
index++;
}
field.setObject( newInstance, barray);
return;
case CHAR:
char [] chars = new char[collectionOfValues.size()];
for ( Value value : ( List ) collectionOfValues ) {
chars[index] = value.charValue();
index++;
}
field.setObject( newInstance, chars);
return;
case STRING:
CharBuf buffer = CharBuf.create(100);
String [] strings = new String[collectionOfValues.size()];
for ( Value value : ( List ) collectionOfValues ) {
strings[index] = value.stringValue(buffer);
index++;
}
field.setObject( newInstance, strings);
return;
default:
Object array = Array.newInstance(componentClass, collectionOfValues.size());
Object o;
for ( Value value : ( List ) collectionOfValues ) {
if (value instanceof ValueContainer) {
o = value.toValue();
if (o instanceof List) {
o = fromList( (List)o, componentClass);
if (componentClass.isInstance( o )) {
Array.set(array, index, o);
} else {
break;
}
} else if (o instanceof Map) {
o = fromMap((Map) o, componentClass);
if (componentClass.isInstance( o )) {
Array.set(array, index, o);
} else {
break;
}
}
} else {
o = value.toValue();
if (componentClass.isInstance( o )) {
Array.set(array, index, o);
} else {
Array.set(array, index, Conversions.coerce( componentClass, o ));
}
}
index++;
}
field.setValue( newInstance, array);
}
break;
}
}
/**
* Creates an object from a value map.
*
* This does some special handling to take advantage of us using the value map so it avoids creating
* a bunch of array objects and collections. Things you have to worry about when writing a
* high-speed JSON serializer.
* @return new object from value map
*/
@Override
@SuppressWarnings("unchecked")
public Object fromValueMap(final Map valueMap
) {
try {
String className = valueMap.get( "class" ).toString();
Class cls = Reflection.loadClass( className );
return fromValueMap( valueMap, cls );
} catch ( Exception ex ) {
return handle(Object.class, sputs("fromValueMap", "map", valueMap, "fieldAccessor", fieldsAccessor), ex);
}
}
/**
* Creates an object from a value map.
*
* This does some special handling to take advantage of us using the value map so it avoids creating
* a bunch of array objects and collections. Things you have to worry about when writing a
* high-speed JSON serializer.
* @param cls the new type
* @return new object from value map
*/
@Override
@SuppressWarnings("unchecked")
public T fromValueMap(final Map valueMap,
final Class cls) {
T newInstance = Reflection.newInstance( cls );
ValueMap map = ( ValueMap ) ( Map ) valueMap;
Map fields = fieldsAccessor.getFields( cls);
Map.Entry[] entries;
FieldAccess field = null;
String fieldName = null;
Map.Entry entry;
int size;
/* if the map is not hydrated get its entries right form the array to avoid collection creations. */
if ( !map.hydrated() ) {
size = map.len();
entries = map.items();
} else {
size = map.size();
entries = ( Map.Entry[] ) map.entrySet().toArray( new Map.Entry[size] );
}
/* guard. We should check if this is still needed.
* I might have added it for debugging and forgot to remove it.*/
if ( size == 0 || entries == null ) {
return newInstance;
}
/* Iterate through the entries. */
for ( int index = 0; index < size; index++ ) {
Object value = null;
try {
entry = entries[index];
fieldName = entry.getKey();
field = fields.get(fieldsAccessor.isCaseInsensitive() ? fieldName.toLowerCase() : fieldName);
if ( field == null ) {
continue;
}
if ( field.ignore() ) {
continue;
}
value = entry.getValue();
if ( value instanceof Value ) {
fromValueMapHandleValueCase( newInstance, field, ( Value ) value );
} else {
fromMapHandleNonValueCase( newInstance, field, value );
}
}catch (Exception ex) {
return (T) handle(Object.class, ex, "fieldName", fieldName, "of class", cls, "had issues for value", value, "for field", field);
}
}
return newInstance;
}
/**
*
* Gets called by fromValueMap
* This does some special handling to take advantage of us using the value map so it avoids creating
* a bunch of array objects and collections. Things you have to worry about when writing a
* high-speed JSON serializer.
* @param field field we want to inject something into
* @param newInstance the thing we want to inject a field value into
* @param objectValue object value we want to inject into the field.
* @return new object from value map
*/
private void fromMapHandleNonValueCase( T newInstance, FieldAccess field,
Object objectValue ) {
try {
if ( objectValue instanceof Map ) {
Class clazz = field.type();
if ( !clazz.isInterface() && !Typ.isAbstract( clazz ) ) {
objectValue = fromValueMap( ( Map ) objectValue, field.type() );
} else {
String className = (( Map ) objectValue)
.get("class").toString();
Class cls = Reflection.loadClass( className );
objectValue = fromValueMap( ( Map ) objectValue, cls );
}
field.setValue(newInstance, objectValue);
} else if ( objectValue instanceof Collection ) {
handleCollectionOfValues( newInstance, field,
( Collection ) objectValue );
} else {
field.setValue( newInstance, objectValue );
}
} catch ( Exception ex ) {
handle(sputs("Problem handling non value case of fromValueMap", "field", field.name(),
"fieldType", field.type().getName(), "object from map", objectValue), ex);
}
}
/**
*
* Gets called by fromValueMap
* This does some special handling to take advantage of us using the value map so it avoids creating
* a bunch of array objects and collections. Things you have to worry about when writing a
* high-speed JSON serializer.
* @param field field we want to inject something into
* @param newInstance the thing we want to inject a field value into
* @param value object value of type Value we want to inject into the field.
* @return new object from value map
*/
private void fromValueMapHandleValueCase(
T newInstance, FieldAccess field, Value value ) {
Object objValue =
ValueContainer.toObject(value);
Class clazz = field.type();
switch (field.typeEnum()) {
case OBJECT:
case ABSTRACT:
case INTERFACE:
if (objValue instanceof Map) {
final Map valueMap = (Map) objValue;
final Value aClass = valueMap.get("class");
clazz = Reflection.loadClass(aClass.stringValue());
}
case INSTANCE:
switch (value.type()) {
case MAP:
objValue = fromValueMap( ( Map ) objValue, clazz );
break;
case LIST:
objValue = fromList((List) objValue, clazz);
break;
}
field.setValue(newInstance, objValue);
break;
case MAP:
case VALUE_MAP:
if (objValue==null) {
field.setValue(newInstance, null);
break;
}
Class keyType = (Class)field.getParameterizedType().getActualTypeArguments()[0];
Class valueType = (Class)field.getParameterizedType().getActualTypeArguments()[1];
Map mapInner = (Map)objValue;
Set set = mapInner.entrySet();
Map newMap = new LinkedHashMap( );
for (Map.Entry entry : set) {
Object evalue = entry.getValue();
Object key = entry.getKey();
if (evalue instanceof ValueContainer) {
evalue = ((ValueContainer) evalue).toValue();
}
key = Conversions.coerce( keyType, key );
evalue = Conversions.coerce( valueType, evalue );
newMap.put( key, evalue );
}
objValue = newMap;
field.setValue(newInstance, objValue);
break;
case LIST:
case COLLECTION:
case SET:
case ARRAY:
case ARRAY_INT:
case ARRAY_BYTE:
case ARRAY_SHORT:
case ARRAY_FLOAT:
case ARRAY_DOUBLE:
case ARRAY_LONG:
case ARRAY_STRING:
case ARRAY_OBJECT:
handleCollectionOfValues( newInstance, field,
( Collection ) objValue );
break;
default:
field.setFromValue(newInstance, value);
}
}
/**
* Inject a map into an object's field.
* @param field field we are injecting a value into
*/
private void setFieldValueFromMap( final Object parentObject,
final FieldAccess field, final Map mapInner ) {
Class fieldClassType = field.type();
Object value = null;
/* Is the field not a map. */
if ( !Typ.isMap( fieldClassType ) ) {
if ( !fieldClassType.isInterface() && !Typ.isAbstract( fieldClassType ) ) {
value = fromMap( mapInner, field.type() );
} else {
Object oClassName = mapInner.get( "class" );
if (oClassName != null) {
value = fromMap( mapInner, Reflection.loadClass( oClassName.toString() ));
} else {
value = null;
}
}
/*
REFACTOR:
This is at least the third time that I have seen this code in the class.
It was either cut and pasted or I forgot I wrote it three times.
REFACTOR:
*/
} else if (Typ.isMap( fieldClassType )) {
Class keyType;
Class valueType;
if (field.getParameterizedType() == null) {
keyType = String.class;
valueType = Object.class;
} else {
keyType = (Class) field.getParameterizedType().getActualTypeArguments()[0];
valueType = (Class) field.getParameterizedType().getActualTypeArguments()[1];
}
Set set = mapInner.entrySet();
Map newMap = new LinkedHashMap( );
for (Map.Entry entry : set) {
Object evalue = entry.getValue();
Object key = entry.getKey();
if (evalue instanceof ValueContainer) {
evalue = ((ValueContainer) evalue).toValue();
}
key = Conversions.coerce(keyType, key);
evalue = Conversions.coerce( valueType, evalue );
newMap.put( key, evalue );
}
value = newMap;
}
field.setValue(parentObject, value);
}
/**
* Helper method to extract collection of values into some field collection.
* REFACTOR:
* This could be refactored to use the org.boon.core.TypeType system which should be faster.
* REFACTOR
* @param collection the collection we are coercing into a field value
*/
private void processCollectionFromMapUsingFields(final Object newInstance,
final FieldAccess field,
final Collection collection ) {
final Class fieldComponentClass = field.getComponentClass();
final Class valueComponentClass = Reflection.getComponentType(collection);
/** See if we have a collection of maps because if we do, then we have some
* recursive processing to do.
*/
if ( Typ.isMap( valueComponentClass ) ) {
handleCollectionOfMaps( newInstance, field,
( Collection> ) collection );
return;
}
/** See if this is a value object of some sort. */
if ( Typ.isValue( valueComponentClass ) ) {
handleCollectionOfValues( newInstance, field,
( Collection ) collection );
return;
}
/**
* See if the collection implements the same type as the field.
* I saw a few places that could have used this helper method earlier in the file but were not.
*/
if (Typ.implementsInterface( collection.getClass(), field.type() )) {
if (fieldComponentClass!=null && fieldComponentClass.isAssignableFrom(valueComponentClass)) {
field.setValue(newInstance, collection);
return;
}
}
/** See if this is some sort of collection.
* TODO we need a coerce that needs a respectIgnore
*
* REFACTOR:
* Note we are assuming it is a collection of instances.
* We don't handle enums here.
*
* We do in other places.
*
* We handle all sorts of generics but not here.
*
* REFACTOR
*
**/
if (!field.typeEnum().isCollection()) {
if (collection instanceof List) {
try {
Object value = fromList( (List) collection, field.getComponentClass());
field.setValue(newInstance, value);
} catch (Exception ex) {
//There is an edge case that needs this. We need a coerce that takes respectIngore, etc.
field.setValue(newInstance, collection);
}
} else {
field.setValue(newInstance, collection);
}
return;
}
/**
* Create a new collection. if the types already match then just copy them over.
* Note that this is currently untyped in the null case.
* We are relying on the fact that the field.setValue calls the Conversion.coerce.
*/
Collection newCollection = Conversions.createCollection( field.type(), collection.size() );
if ( fieldComponentClass == null || fieldComponentClass.isAssignableFrom(valueComponentClass)) {
newCollection.addAll(collection);
field.setValue( newInstance, newCollection );
return;
}
/* Here we try to do the coercion for each individual collection item. */
for (Object itemValue : collection) {
newCollection.add(Conversions.coerce(fieldComponentClass, itemValue));
field.setValue(newInstance, newCollection);
}
}
/**
* fromMap converts a map into a Java object.
* This version will see if there is a class parameter in the map, and dies if there is not.
* @param map map to create the object from.
* @return new object
*/
@Override
public Object fromMap(Map map) {
String clazz = (String) map.get( "class" );
Class cls = Reflection.loadClass( clazz );
return fromMap(map, cls);
}
/**
* This could be refactored to use core.TypeType class and it would run faster.
* Converts an object into a map
* @param object the object that we want to convert
* @return map map representation of the object
*/
@Override
public Map toMap(final Object object) {
if ( object == null ) {
return null;
}
if (object instanceof Map) {
return (Map) object;
}
Map map = new LinkedHashMap<>();
final Map fieldMap = Reflection.getAllAccessorFields( object.getClass() );
List fields = new ArrayList( fieldMap.values() );
Collections.reverse( fields ); // make super classes fields first that
for ( FieldAccess access : fields ) {
String fieldName = access.name();
if (access.isStatic()) {
continue;
}
Object value = access.getValue(object);
if ( value == null ) {
continue;
}
switch (access.typeEnum()) {
case BYTE:
case BYTE_WRAPPER:
case SHORT:
case SHORT_WRAPPER:
case INT:
case INTEGER_WRAPPER:
case LONG:
case LONG_WRAPPER:
case FLOAT:
case FLOAT_WRAPPER:
case DOUBLE:
case DOUBLE_WRAPPER:
case CHAR:
case CHAR_WRAPPER:
case BIG_DECIMAL:
case BIG_INT:
case BOOLEAN:
case BOOLEAN_WRAPPER:
case CURRENCY:
case CALENDAR:
case DATE:
map.put( fieldName, value );
break;
case ARRAY:
case ARRAY_INT:
case ARRAY_BYTE:
case ARRAY_SHORT:
case ARRAY_FLOAT:
case ARRAY_DOUBLE:
case ARRAY_LONG:
case ARRAY_STRING:
case ARRAY_OBJECT:
if (Typ.isBasicType( access.getComponentClass() )) {
map.put(fieldName, value);
} else {
int length = Arry.len(value);
List> list = new ArrayList<>( length );
for ( int index = 0; index < length; index++ ) {
Object item = Arry.fastIndex( value, index );
list.add( toMap( item ) );
}
map.put( fieldName, list );
}
break;
case COLLECTION:
case LIST:
case SET:
Collection collection = ( Collection ) value;
Class componentType = access.getComponentClass();
if ( Typ.isBasicType( componentType ) ) {
map.put(fieldName, value);
} else if (Typ.isEnum(componentType)) {
List list = new ArrayList<>(
collection.size() );
for ( Object item : collection ) {
if ( item != null ) {
list.add( item.toString() );
}
}
map.put( fieldName, list );
} else {
List> list = new ArrayList<>(
collection.size() );
for ( Object item : collection ) {
if ( item != null ) {
list.add( toMap( item ) );
}
}
map.put( fieldName, list );
}
break;
case MAP:
map.put(fieldName, value);
break;
case INSTANCE:
map.put(fieldName, toMap(value));
break;
case INTERFACE:
case ABSTRACT:
final Map abstractMap = toMap(value);
abstractMap.put("class", Boon.className(value));
map.put(fieldName, abstractMap);
break;
case ENUM:
map.put(fieldName, value);
break;
default:
map.put(fieldName, Conversions.toString(value));
break;
}
}
return map;
}
/**
* Creates a list of maps from a list of class instances.
* @param collection the collection we are coercing into a field value
* @return the return value.
*/
@Override
public List> toListOfMaps(Collection collection) {
List> list = new ArrayList<>();
for ( Object o : collection ) {
list.add( toMap( o ) );
}
return list;
}
/** Convert an object to a list.
*
* @param object the object we want to convert to a list
* @return new list from an object
*/
@Override
public List toList(Object object) {
TypeType instanceType = TypeType.getInstanceType(object);
switch (instanceType) {
case NULL:
return Lists.list((Object)null);
case ARRAY:
case ARRAY_INT:
case ARRAY_BYTE:
case ARRAY_SHORT:
case ARRAY_FLOAT:
case ARRAY_DOUBLE:
case ARRAY_LONG:
case ARRAY_STRING:
case ARRAY_OBJECT:
return Conversions.toList(object);
}
return Lists.list(object);
}
}