/* * Copyright (C) 2004, 2005, 2006 Joe Walnes. * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 XStream Committers. * All rights reserved. * * The software in this package is published under the terms of the BSD * style license a copy of which has been included with this distribution in * the LICENSE.txt file. * * Created on 02. March 2006 by Joerg Schaible */ package com.thoughtworks.xstream.converters.reflection; import com.thoughtworks.xstream.converters.ConversionException; import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.SingleValueConverter; import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.core.Caching; import com.thoughtworks.xstream.core.ReferencingMarshallingContext; import com.thoughtworks.xstream.core.util.ArrayIterator; import com.thoughtworks.xstream.core.util.FastField; import com.thoughtworks.xstream.core.util.HierarchicalStreams; import com.thoughtworks.xstream.core.util.Primitives; import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.mapper.CannotResolveClassException; import com.thoughtworks.xstream.mapper.Mapper; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.AbstractList; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; public abstract class AbstractReflectionConverter implements Converter, Caching { protected final ReflectionProvider reflectionProvider; protected final Mapper mapper; protected transient SerializationMethodInvoker serializationMethodInvoker; private transient ReflectionProvider pureJavaReflectionProvider; public AbstractReflectionConverter(Mapper mapper, ReflectionProvider reflectionProvider) { this.mapper = mapper; this.reflectionProvider = reflectionProvider; serializationMethodInvoker = new SerializationMethodInvoker(); } public void marshal(Object original, final HierarchicalStreamWriter writer, final MarshallingContext context) { final Object source = serializationMethodInvoker.callWriteReplace(original); if (source != original && context instanceof ReferencingMarshallingContext) { ((ReferencingMarshallingContext)context).replace(original, source); } if (source.getClass() != original.getClass()) { String attributeName = mapper.aliasForSystemAttribute("resolves-to"); if (attributeName != null) { writer.addAttribute(attributeName, mapper.serializedClass(source.getClass())); } context.convertAnother(source); } else { doMarshal(source, writer, context); } } protected void doMarshal(final Object source, final HierarchicalStreamWriter writer, final MarshallingContext context) { final List fields = new ArrayList(); final Map defaultFieldDefinition = new HashMap(); // Attributes might be preferred to child elements ... reflectionProvider.visitSerializableFields(source, new ReflectionProvider.Visitor() { final Set writtenAttributes = new HashSet(); public void visit(String fieldName, Class type, Class definedIn, Object value) { if (!mapper.shouldSerializeMember(definedIn, fieldName)) { return; } if (!defaultFieldDefinition.containsKey(fieldName)) { Class lookupType = source.getClass(); // See XSTR-457 and OmitFieldsTest // if (definedIn != source.getClass() && !mapper.shouldSerializeMember(lookupType, fieldName)) { // lookupType = definedIn; // } defaultFieldDefinition.put(fieldName, reflectionProvider.getField(lookupType, fieldName)); } SingleValueConverter converter = mapper.getConverterFromItemType(fieldName, type, definedIn); if (converter != null) { final String attribute = mapper.aliasForAttribute(mapper.serializedMember(definedIn, fieldName)); if (value != null) { if (writtenAttributes.contains(fieldName)) { // TODO: use attribute throw new ConversionException("Cannot write field with name '" + fieldName + "' twice as attribute for object of type " + source.getClass().getName()); } final String str = converter.toString(value); if (str != null) { writer.addAttribute(attribute, str); } } writtenAttributes.add(fieldName); // TODO: use attribute } else { fields.add(new FieldInfo(fieldName, type, definedIn, value)); } } }); new Object() { { for (Iterator fieldIter = fields.iterator(); fieldIter.hasNext();) { FieldInfo info = (FieldInfo)fieldIter.next(); if (info.value != null) { Mapper.ImplicitCollectionMapping mapping = mapper .getImplicitCollectionDefForFieldName( source.getClass(), info.fieldName); if (mapping != null) { if (context instanceof ReferencingMarshallingContext) { ReferencingMarshallingContext refContext = (ReferencingMarshallingContext)context; refContext.registerImplicit(info.value); } final boolean isCollection = info.value instanceof Collection; final boolean isMap = info.value instanceof Map; final boolean isEntry = isMap && mapping.getKeyFieldName() == null; final boolean isArray = info.value.getClass().isArray(); for (Iterator iter = isArray ? new ArrayIterator(info.value) : isCollection ? ((Collection)info.value).iterator() : isEntry ? ((Map)info.value).entrySet().iterator() : ((Map)info.value).values().iterator(); iter.hasNext();) { Object obj = iter.next(); final String itemName; final Class itemType; if (obj == null) { itemType = Object.class; itemName = mapper.serializedClass(null); } else if (isEntry) { final String entryName = mapping.getItemFieldName() != null ? mapping.getItemFieldName() : mapper.serializedClass(Map.Entry.class); Map.Entry entry = (Map.Entry) obj; ExtendedHierarchicalStreamWriterHelper.startNode(writer, entryName, Map.Entry.class); writeItem(entry.getKey(), context, writer); writeItem(entry.getValue(), context, writer); writer.endNode(); continue; } else if (mapping.getItemFieldName() != null) { itemType = mapping.getItemType(); itemName = mapping.getItemFieldName(); } else { itemType = obj.getClass(); itemName = mapper.serializedClass(itemType); } writeField( info.fieldName, itemName, itemType, info.definedIn, obj); } } else { writeField( info.fieldName, null, info.type, info.definedIn, info.value); } } } } void writeField(String fieldName, String aliasName, Class fieldType, Class definedIn, Object newObj) { ExtendedHierarchicalStreamWriterHelper.startNode(writer, aliasName != null ? aliasName : mapper.serializedMember(source.getClass(), fieldName), fieldType); if (newObj != null) { Class actualType = newObj.getClass(); Class defaultType = mapper.defaultImplementationOf(fieldType); if (!actualType.equals(defaultType)) { String serializedClassName = mapper.serializedClass(actualType); if (!serializedClassName.equals(mapper.serializedClass(defaultType))) { String attributeName = mapper.aliasForSystemAttribute("class"); if (attributeName != null) { writer.addAttribute(attributeName, serializedClassName); } } } final Field defaultField = (Field)defaultFieldDefinition.get(fieldName); if (defaultField.getDeclaringClass() != definedIn) { String attributeName = mapper.aliasForSystemAttribute("defined-in"); if (attributeName != null) { writer.addAttribute( attributeName, mapper.serializedClass(definedIn)); } } Field field = reflectionProvider.getField(definedIn, fieldName); marshallField(context, newObj, field); } writer.endNode(); } void writeItem(Object item, MarshallingContext context, HierarchicalStreamWriter writer) { if (item == null) { String name = mapper.serializedClass(null); ExtendedHierarchicalStreamWriterHelper.startNode(writer, name, Mapper.Null.class); writer.endNode(); } else { String name = mapper.serializedClass(item.getClass()); ExtendedHierarchicalStreamWriterHelper.startNode(writer, name, item.getClass()); context.convertAnother(item); writer.endNode(); } } }; } protected void marshallField(final MarshallingContext context, Object newObj, Field field) { context.convertAnother(newObj, mapper.getLocalConverter(field.getDeclaringClass(), field.getName())); } public Object unmarshal(final HierarchicalStreamReader reader, final UnmarshallingContext context) { Object result = instantiateNewInstance(reader, context); result = doUnmarshal(result, reader, context); return serializationMethodInvoker.callReadResolve(result); } public Object doUnmarshal(final Object result, final HierarchicalStreamReader reader, final UnmarshallingContext context) { final Set seenFields = new HashSet() { public boolean add(Object e) { if (!super.add(e)) { throw new DuplicateFieldException(e.toString()); } return true; } }; Iterator it = reader.getAttributeNames(); // Process attributes before recursing into child elements. while (it.hasNext()) { String attrAlias = (String) it.next(); // TODO: realMember should return FastField String attrName = mapper.realMember(result.getClass(), mapper.attributeForAlias(attrAlias)); boolean fieldExistsInClass = reflectionProvider.fieldDefinedInClass(attrName, result.getClass()); if (fieldExistsInClass) { Field field = reflectionProvider.getField(result.getClass(), attrName); if (Modifier.isTransient(field.getModifiers()) && !shouldUnmarshalTransientFields()) { continue; } Class classDefiningField = field.getDeclaringClass(); if (!mapper.shouldSerializeMember(classDefiningField, attrName)) { continue; } SingleValueConverter converter = mapper.getConverterFromAttribute(classDefiningField, attrName, field.getType()); Class type = field.getType(); if (converter != null) { Object value = converter.fromString(reader.getAttribute(attrAlias)); if (type.isPrimitive()) { type = Primitives.box(type); } if (value != null && !type.isAssignableFrom(value.getClass())) { throw new ConversionException("Cannot convert type " + value.getClass().getName() + " to type " + type.getName()); } seenFields.add(new FastField(classDefiningField, attrName)); reflectionProvider.writeField(result, attrName, value, classDefiningField); } } } Map implicitCollectionsForCurrentObject = null; while (reader.hasMoreChildren()) { reader.moveDown(); String originalNodeName = reader.getNodeName(); Class classDefiningField = determineWhichClassDefinesField(reader); Class fieldDeclaringClass = classDefiningField == null ? result.getClass() : classDefiningField; String fieldName = mapper.realMember(fieldDeclaringClass, originalNodeName); Mapper.ImplicitCollectionMapping implicitCollectionMapping = mapper .getImplicitCollectionDefForFieldName(fieldDeclaringClass, fieldName); boolean fieldExistsInClass = implicitCollectionMapping == null && reflectionProvider.fieldDefinedInClass(fieldName, fieldDeclaringClass); Class type = implicitCollectionMapping == null || implicitCollectionMapping.getItemType() == null ? determineType( reader, fieldExistsInClass, result, fieldName, classDefiningField) : implicitCollectionMapping.getItemType(); final Object value; if (fieldExistsInClass) { Field field = reflectionProvider.getField(classDefiningField != null ? classDefiningField : result.getClass(), fieldName); if ((Modifier.isTransient(field.getModifiers()) && !shouldUnmarshalTransientFields()) || !mapper.shouldSerializeMember(classDefiningField != null ? classDefiningField : result.getClass(), fieldName)) { reader.moveUp(); continue; } value = unmarshallField(context, result, type, field); // TODO the reflection provider should have returned the proper field in first place .... Class definedType = reflectionProvider.getFieldType(result, fieldName, classDefiningField); if (!definedType.isPrimitive()) { type = definedType; } } else { if (Map.Entry.class.equals(type)) { reader.moveDown(); final Object key = context.convertAnother(result, HierarchicalStreams.readClassType(reader, mapper)); reader.moveUp(); reader.moveDown(); final Object v = context.convertAnother(result, HierarchicalStreams.readClassType(reader, mapper)); reader.moveUp(); value = Collections.singletonMap(key, v).entrySet().iterator().next(); } else { value = type != null ? context.convertAnother(result, type) : null; } } if (value != null && !type.isAssignableFrom(value.getClass())) { throw new ConversionException("Cannot convert type " + value.getClass().getName() + " to type " + type.getName()); } if (fieldExistsInClass) { reflectionProvider.writeField(result, fieldName, value, classDefiningField); seenFields.add(new FastField(classDefiningField, fieldName)); } else if (type != null) { implicitCollectionsForCurrentObject = writeValueToImplicitCollection(context, value, implicitCollectionsForCurrentObject, result, originalNodeName); } reader.moveUp(); } if (implicitCollectionsForCurrentObject != null) { for(Iterator iter = implicitCollectionsForCurrentObject.entrySet().iterator(); iter.hasNext();) { Map.Entry entry = (Map.Entry)iter.next(); Object value = entry.getValue(); if (value instanceof ArraysList) { Object array = ((ArraysList)value).toPhysicalArray(); reflectionProvider.writeField(result, (String)entry.getKey(), array, null); } } } return result; } protected Object unmarshallField(final UnmarshallingContext context, final Object result, Class type, Field field) { return context.convertAnother(result, type, mapper.getLocalConverter(field.getDeclaringClass(), field.getName())); } protected boolean shouldUnmarshalTransientFields() { return false; } private Map writeValueToImplicitCollection(UnmarshallingContext context, Object value, Map implicitCollections, Object result, String itemFieldName) { String fieldName = mapper.getFieldNameForItemTypeAndName( context.getRequiredType(), value != null ? value.getClass() : Mapper.Null.class, itemFieldName); if (fieldName != null) { if (implicitCollections == null) { implicitCollections = new HashMap(); // lazy instantiation } Collection collection = (Collection)implicitCollections.get(fieldName); if (collection == null) { Class physicalFieldType = reflectionProvider.getFieldType(result, fieldName, null); if (physicalFieldType.isArray()) { collection = new ArraysList(physicalFieldType); } else { Class fieldType = mapper.defaultImplementationOf(physicalFieldType); if (!(Collection.class.isAssignableFrom(fieldType) || Map.class.isAssignableFrom(fieldType))) { throw new ObjectAccessException("Field " + fieldName + " of " + result.getClass().getName() + " is configured for an implicit Collection or Map, but field is of type " + fieldType.getName()); } if (pureJavaReflectionProvider == null) { pureJavaReflectionProvider = new PureJavaReflectionProvider(); } Object instance = pureJavaReflectionProvider.newInstance(fieldType); if (instance instanceof Collection) { collection = (Collection)instance; } else { Mapper.ImplicitCollectionMapping implicitCollectionMapping = mapper .getImplicitCollectionDefForFieldName(result.getClass(), fieldName); collection = new MappingList((Map)instance, implicitCollectionMapping.getKeyFieldName()); } reflectionProvider.writeField(result, fieldName, instance, null); } implicitCollections.put(fieldName, collection); } collection.add(value); } else { throw new ConversionException("Element " + itemFieldName + " of type " + value.getClass().getName() + " is not defined as field in type " + result.getClass().getName()); } return implicitCollections; } private Class determineWhichClassDefinesField(HierarchicalStreamReader reader) { String attributeName = mapper.aliasForSystemAttribute("defined-in"); String definedIn = attributeName == null ? null : reader.getAttribute(attributeName); return definedIn == null ? null : mapper.realClass(definedIn); } protected Object instantiateNewInstance(HierarchicalStreamReader reader, UnmarshallingContext context) { String attributeName = mapper.aliasForSystemAttribute("resolves-to"); String readResolveValue = attributeName == null ? null : reader.getAttribute(attributeName); Object currentObject = context.currentObject(); if (currentObject != null) { return currentObject; } else if (readResolveValue != null) { return reflectionProvider.newInstance(mapper.realClass(readResolveValue)); } else { return reflectionProvider.newInstance(context.getRequiredType()); } } private Class determineType(HierarchicalStreamReader reader, boolean validField, Object result, String fieldName, Class definedInCls) { String classAttribute = HierarchicalStreams.readClassAttribute(reader, mapper); if (!validField) { Class itemType = mapper.getItemTypeForItemFieldName(result.getClass(), fieldName); if (itemType != null) { if (classAttribute != null) { return mapper.realClass(classAttribute); } return itemType; } else { String originalNodeName = reader.getNodeName(); if (definedInCls == null) { for(definedInCls = result.getClass(); definedInCls != null; definedInCls = definedInCls.getSuperclass()) { if (!mapper.shouldSerializeMember(definedInCls, originalNodeName)) { return null; } } } try { return mapper.realClass(originalNodeName); } catch (CannotResolveClassException e) { throw new UnknownFieldException(result.getClass().getName(), fieldName); } } } else { if (classAttribute != null) { return mapper.realClass(classAttribute); } return mapper.defaultImplementationOf(reflectionProvider.getFieldType(result, fieldName, definedInCls)); } } public void flushCache() { serializationMethodInvoker.flushCache(); } private Object readResolve() { serializationMethodInvoker = new SerializationMethodInvoker(); return this; } public static class DuplicateFieldException extends ConversionException { public DuplicateFieldException(String msg) { super(msg); add("duplicate-field", msg); } } public static class UnknownFieldException extends ConversionException { public UnknownFieldException(String type, String field) { super("No such field " + type + "." + field); add("field", field); } } private static class FieldInfo { final String fieldName; final Class type; final Class definedIn; final Object value; FieldInfo(String fieldName, Class type, Class definedIn, Object value) { this.fieldName = fieldName; this.type = type; this.definedIn = definedIn; this.value = value; } } private static class ArraysList extends ArrayList { final Class physicalFieldType; ArraysList(Class physicalFieldType) { this.physicalFieldType = physicalFieldType; } Object toPhysicalArray() { Object[] objects = toArray(); Object array = Array.newInstance(physicalFieldType.getComponentType(), objects.length); if (physicalFieldType.getComponentType().isPrimitive()) { for (int i = 0; i < objects.length; ++i) { Array.set(array, i, Array.get(objects, i)); } } else { System.arraycopy(objects, 0, array, 0, objects.length); } return array; } } private class MappingList extends AbstractList { private final Map map; private final String keyFieldName; private final Map fieldCache = new HashMap(); public MappingList(Map map, String keyFieldName) { this.map = map; this.keyFieldName = keyFieldName; } public boolean add(Object object) { if (object == null) { boolean containsNull = !map.containsKey(null); map.put(null, null); return containsNull; } Class itemType = object.getClass(); if (keyFieldName != null) { Field field = (Field)fieldCache.get(itemType); if (field == null) { field = reflectionProvider.getField(itemType, keyFieldName); fieldCache.put(itemType, field); } if (field != null) { try { Object key = field.get(object); return map.put(key, object) == null; } catch (IllegalArgumentException e) { throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e); } catch (IllegalAccessException e) { throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e); } } } else if (object instanceof Map.Entry) { final Map.Entry entry = (Map.Entry)object; return map.put(entry.getKey(), entry.getValue()) == null; } throw new ConversionException("Element of type " + object.getClass().getName() + " is not defined as entry for map of type " + map.getClass().getName()); } public Object get(int index) { throw new UnsupportedOperationException(); } public int size() { return map.size(); } } }