Skip to content

Instantly share code, notes, and snippets.

@jkschoen
Created February 6, 2013 16:13
Show Gist options
  • Select an option

  • Save jkschoen/4723660 to your computer and use it in GitHub Desktop.

Select an option

Save jkschoen/4723660 to your computer and use it in GitHub Desktop.

Revisions

  1. jkschoen created this gist Feb 6, 2013.
    639 changes: 639 additions & 0 deletions CustomConverter.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,639 @@
    import java.lang.reflect.Array;
    import java.lang.reflect.Field;
    import java.lang.reflect.Modifier;
    import java.lang.reflect.ParameterizedType;
    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;

    import com.thoughtworks.xstream.converters.ConversionException;
    import com.thoughtworks.xstream.converters.MarshallingContext;
    import com.thoughtworks.xstream.converters.SingleValueConverter;
    import com.thoughtworks.xstream.converters.UnmarshallingContext;
    import com.thoughtworks.xstream.converters.reflection.ObjectAccessException;
    import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider;
    import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
    import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
    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;

    public abstract class CustomConverter<T> extends ReflectionConverter {

    private Map<String, String[]> customFields;
    private transient ReflectionProvider pureJavaReflectionProvider;

    public CustomConverter(Mapper mapper,
    ReflectionProvider reflectionProvider,
    Map<String, String[]> customFields) {
    super(mapper, reflectionProvider);
    this.customFields = customFields;
    if (this.customFields == null) {
    this.customFields = new HashMap<String, String[]>();
    }
    }

    public abstract void customMarshal(Object test,
    HierarchicalStreamWriter writer, MarshallingContext context);

    public abstract Object customUnmarshal(String nodeName,
    final Object result, HierarchicalStreamReader reader,
    UnmarshallingContext context, Set seenFields);

    public boolean canConvert(Class type) {
    ParameterizedType parameterizedType = (ParameterizedType) getClass()
    .getGenericSuperclass();
    Class<?> clazzz = (Class) parameterizedType.getActualTypeArguments()[0];
    return clazzz.isAssignableFrom(type);
    }

    // below is only slightly modified versions from
    // com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter

    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();
    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);
    }
    // changed from else to else if. Basically if it is a
    // field we want to handle,
    // igonore it
    else if (!customFields.keySet().contains(fieldName)) {
    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) {
    if (info.value != Collections.EMPTY_LIST
    && info.value != Collections.EMPTY_SET
    && info.value != Collections.EMPTY_MAP) {
    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,
    entry.getClass());
    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) {
    Class actualType = newObj != null ? newObj.getClass()
    : fieldType;
    ExtendedHierarchicalStreamWriterHelper
    .startNode(
    writer,
    aliasName != null ? aliasName : mapper
    .serializedMember(source.getClass(),
    fieldName), actualType);

    if (newObj != null) {
    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();
    }
    }
    };

    // Added this to call our custom part.
    customMarshal(source, writer, context);
    }

    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(((FastField) e).getName());
    }
    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();
    /*
    * Added below code to break out and do our own custom converstion
    * when we hit one of out custom fields.
    */
    boolean customField = false;
    for (String key : this.customFields.keySet()) {
    String[] nodeNames = this.customFields.get(key);
    for (String node : nodeNames) {
    if (node.equals(originalNodeName)) {
    customField = true;
    break;
    }
    }
    if (customField) {
    break;
    }
    }

    if (customField) {
    customUnmarshal(originalNodeName, result, reader, context,
    seenFields);
    } else {
    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(
    field.getDeclaringClass(), 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;
    }

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

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

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

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

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

    protected 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();
    }
    }
    }