// Copyright (c) 2003-2014, Jodd Team (jodd.org). All Rights Reserved.
package jodd.json;
import jodd.introspector.ClassDescriptor;
import jodd.introspector.ClassIntrospector;
import jodd.introspector.PropertyDescriptor;
import jodd.introspector.Setter;
import jodd.typeconverter.TypeConverterManager;
import jodd.util.ClassLoaderUtil;
import jodd.util.ReflectUtil;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Map to bean converter.
* Used when parsing with class metadata enabled.
*/
public class MapToBean {
protected boolean declared = true;
protected final JsonParserBase jsonParser;
protected final String classMetadataName;
public MapToBean(JsonParserBase jsonParser, String classMetadataName) {
this.jsonParser = jsonParser;
this.classMetadataName = classMetadataName;
}
/**
* Converts map to target type.
*/
public Object map2bean(Map map, Class targetType) {
Object target = null;
// create targets type
String className = (String) map.get(classMetadataName);
if (className == null) {
if (targetType == null) {
// nothing to do, no information about target type found
target = map;
}
}
else {
try {
targetType = ClassLoaderUtil.loadClass(className);
} catch (ClassNotFoundException cnfex) {
throw new JsonException(cnfex);
}
}
if (target == null) {
target = jsonParser.newObjectInstance(targetType);
}
ClassDescriptor cd = ClassIntrospector.lookup(target.getClass());
boolean targetIsMap = target instanceof Map;
for (Object key : map.keySet()) {
String keyName = key.toString();
if (classMetadataName != null) {
if (keyName.equals(classMetadataName)) {
continue;
}
}
PropertyDescriptor pd = cd.getPropertyDescriptor(keyName, declared);
if (!targetIsMap && pd == null) {
// target property does not exist, continue
continue;
}
// value is one of JSON basic types, like Number, Map, List...
Object value = map.get(key);
Class propertyType = pd == null ? null : pd.getType();
Class componentType = pd == null ? null : pd.resolveComponentType(true);
if (value != null) {
if (value instanceof List) {
if (componentType != null && componentType != String.class) {
value = generifyList((List) value, componentType);
}
}
else if (value instanceof Map) {
// if the value we want to inject is a Map...
if (ReflectUtil.isTypeOf(propertyType, Map.class) == false) {
// ... and if target is NOT a map
value = map2bean((Map) value, propertyType);
}
else {
// target is also a Map, but we might need to generify it
Class keyType = pd == null ? null : pd.resolveKeyType(true);
if (keyType != String.class || componentType != String.class) {
// generify
value = generifyMap((Map) value, keyType, componentType);
}
}
}
}
if (targetIsMap) {
((Map)target).put(keyName, value);
}
else {
try {
setValue(target, pd, value);
} catch (Exception ignore) {
ignore.printStackTrace();
}
}
}
return target;
}
/**
* Converts type of all list elements to match the component type.
*/
private Object generifyList(List list, Class componentType) {
for (int i = 0; i < list.size(); i++) {
Object element = list.get(i);
if (element != null) {
if (element instanceof Map) {
Object bean = map2bean((Map) element, componentType);
list.set(i, bean);
} else {
Object value = convert(element, componentType);
list.set(i, value);
}
}
}
return list;
}
/**
* Sets the property value.
*/
private void setValue(Object target, PropertyDescriptor pd, Object value) throws InvocationTargetException, IllegalAccessException {
Class propertyType;
Setter setter = pd.getSetter(true);
if (setter != null) {
if (value != null) {
propertyType = setter.getSetterRawType();
value = jsonParser.convertType(value, propertyType);
}
setter.invokeSetter(target, value);
}
}
/**
* Change map elements to match key and value types.
*/
protected <K,V> Map<K, V> generifyMap(Map<Object, Object> map, Class<K> keyType, Class<V> valueType) {
if (keyType == String.class) {
// only value type is changed, we can make value replacements
for (Map.Entry<Object, Object> entry : map.entrySet()) {
Object value = entry.getValue();
Object newValue = convert(value, valueType);
if (value != newValue) {
entry.setValue(newValue);
}
}
return (Map<K, V>) map;
}
// key is changed too, we need a new map
Map<K, V> newMap = new HashMap<K, V>(map.size());
for (Map.Entry<Object, Object> entry : map.entrySet()) {
Object key = entry.getKey();
Object newKey = convert(key, keyType);
Object value = entry.getValue();
Object newValue = convert(value, valueType);
newMap.put((K)newKey, (V)newValue);
}
return newMap;
}
protected Object convert(Object value, Class targetType) {
Class valueClass = value.getClass();
if (valueClass == targetType) {
return value;
}
if (value instanceof Map) {
if (targetType == Map.class) {
return value;
}
return map2bean((Map) value, targetType);
}
try {
return TypeConverterManager.convertType(value, targetType);
}
catch (Exception ex) {
throw new JsonException("Type conversion failed", ex);
}
}
}