Index: /home/guilherme/workspace/xstreamold/jdk-1.5-specific.txt =================================================================== --- /home/guilherme/workspace/xstreamold/jdk-1.5-specific.txt (revision 704) +++ /home/guilherme/workspace/xstreamold/jdk-1.5-specific.txt (working copy) @@ -3,3 +3,6 @@ com/thoughtworks/xstream/converters/enums/** com/thoughtworks/xstream/converters/reflection/PureJavaReflectionProvider15Test.* +com/thoughtworks/xstream/annotations/AnnotationProvider +com/thoughtworks/xstream/annotations/Java15ReflectionConverter + Index: /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/XStream.java =================================================================== --- /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/XStream.java (revision 704) +++ /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/XStream.java (working copy) @@ -1,5 +1,43 @@ package com.thoughtworks.xstream; +import java.io.EOFException; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.NotActiveException; +import java.io.ObjectInputStream; +import java.io.ObjectInputValidation; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.URL; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.Vector; + import com.thoughtworks.xstream.alias.ClassMapper; import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.converters.ConverterLookup; @@ -77,44 +115,6 @@ import com.thoughtworks.xstream.mapper.OuterClassMapper; import com.thoughtworks.xstream.mapper.XmlFriendlyMapper; -import java.io.EOFException; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.NotActiveException; -import java.io.ObjectInputStream; -import java.io.ObjectInputValidation; -import java.io.ObjectOutputStream; -import java.io.OutputStream; -import java.io.Reader; -import java.io.StringReader; -import java.io.StringWriter; -import java.io.Writer; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.net.URL; -import java.sql.Time; -import java.sql.Timestamp; -import java.util.ArrayList; -import java.util.BitSet; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Hashtable; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Properties; -import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; -import java.util.Vector; - /** * Simple facade to XStream library, a Java-XML serialization tool. *

@@ -391,10 +391,10 @@ } protected void setupConverters() { - ReflectionConverter reflectionConverter = new ReflectionConverter(mapper, reflectionProvider); + ReflectionConverter reflectionConverter = jvm.getReflectionConverter(mapper, reflectionProvider); registerConverter(reflectionConverter, PRIORITY_VERY_LOW); - registerConverter(new SerializableConverter(mapper, reflectionProvider), PRIORITY_LOW); + registerConverter(new SerializableConverter(mapper, reflectionProvider, jvm), PRIORITY_LOW); registerConverter(new ExternalizableConverter(mapper), PRIORITY_LOW); registerConverter(new NullConverter(), PRIORITY_VERY_HIGH); Index: /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/annotations/AnnotationProvider.java =================================================================== --- /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/annotations/AnnotationProvider.java (revision 0) +++ /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/annotations/AnnotationProvider.java (revision 0) @@ -0,0 +1,29 @@ +package com.thoughtworks.xstream.annotations; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; + +/** + * A pure java annotation provider. + * + * @author Guilherme Silveira + */ +public class AnnotationProvider { + + /** + * Returns a field annotation based on an annotation type + * + * @param + * annotation type + * @param field + * field annotation + * @param annotationClass + * annotation class + * @return + */ + public T getAnnotation(Field field, + Class annotationClass) { + return (T) field.getAnnotation(annotationClass); + } + +} Index: /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/annotations/Java15ReflectionConverter.java =================================================================== --- /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/annotations/Java15ReflectionConverter.java (revision 0) +++ /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/annotations/Java15ReflectionConverter.java (revision 0) @@ -0,0 +1,52 @@ +package com.thoughtworks.xstream.annotations; + +import java.lang.reflect.Field; + +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.converters.reflection.ReflectionConverter; +import com.thoughtworks.xstream.converters.reflection.ReflectionProvider; +import com.thoughtworks.xstream.mapper.Mapper; + +public class Java15ReflectionConverter extends ReflectionConverter { + + private final AnnotationProvider annotationProvider; + + public Java15ReflectionConverter(Mapper mapper, + ReflectionProvider reflectionProvider, + AnnotationProvider annotationProvider) { + super(mapper, reflectionProvider); + // should I keep a copy of reflectionprovider here? both ways are not + // cute, I believe I should... I didn't think about any other simple + // 'callback' way to implement it, only creating another class... + this.annotationProvider = annotationProvider; + } + + protected void fieldMarshall(final MarshallingContext context, + Object newObj, Field field, ReflectionProvider reflectionProvider) { + XStreamConverter annotation = (XStreamConverter) annotationProvider + .getAnnotation(field, XStreamConverter.class); + if (annotation != null) { + context.convertAnother(newObj, (Converter) reflectionProvider + .newInstance(annotation.value())); + } else { + context.convertAnother(newObj); + } + } + + protected Object fieldUnmarshall(final UnmarshallingContext context, + final Object result, Class type, Field field, + ReflectionProvider reflectionProvider) { + XStreamConverter annotation = (XStreamConverter) annotationProvider + .getAnnotation(field, XStreamConverter.class); + if (annotation != null) { + return context.convertAnother(result, type, + (Converter) reflectionProvider.newInstance(annotation + .value())); + } else { + return context.convertAnother(result, type); + } + } + +} Index: /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/annotations/XStreamConverter.java =================================================================== --- /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/annotations/XStreamConverter.java (revision 704) +++ /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/annotations/XStreamConverter.java (working copy) @@ -1,5 +1,6 @@ package com.thoughtworks.xstream.annotations; +import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -12,7 +13,8 @@ * @author Chung-Onn Cheong */ @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) +@Target({ElementType.TYPE, ElementType.FIELD}) +@Documented public @interface XStreamConverter { Class value(); } Index: /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/converters/MarshallingContext.java =================================================================== --- /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/converters/MarshallingContext.java (revision 704) +++ /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/converters/MarshallingContext.java (working copy) @@ -1,8 +1,18 @@ package com.thoughtworks.xstream.converters; - public interface MarshallingContext extends DataHolder { + /** + * Converts another object searching for the default converter + * @param nextItem the next item to convert + */ void convertAnother(Object nextItem); + + /** + * Converts another object using the specified converter + * @param nextItem the next item to convert + * @param converter the converter to use + */ + void convertAnother(Object nextItem, Converter converter); } Index: /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/converters/UnmarshallingContext.java =================================================================== --- /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/converters/UnmarshallingContext.java (revision 704) +++ /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/converters/UnmarshallingContext.java (working copy) @@ -4,6 +4,8 @@ Object convertAnother(Object current, Class type); + Object convertAnother(Object current, Class type, Converter converter); + Object currentObject(); Class getRequiredType(); Index: /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/converters/reflection/FieldDictionary.java =================================================================== --- /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/converters/reflection/FieldDictionary.java (revision 704) +++ /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/converters/reflection/FieldDictionary.java (working copy) @@ -9,6 +9,10 @@ import java.util.Iterator; import java.util.Map; +/** + * A field dictionary instance caches information about classes fields. + * + */ public class FieldDictionary { private final Map keyedByFieldNameCache = Collections.synchronizedMap(new HashMap()); @@ -14,6 +18,11 @@ private final Map keyedByFieldNameCache = Collections.synchronizedMap(new HashMap()); private final Map keyedByFieldKeyCache = Collections.synchronizedMap(new HashMap()); + /** + * Returns an iterator for all serializable fields for some class + * @param cls the class you are interested on + * @return an iterator for its serializable fields + */ public Iterator serializableFieldsFor(Class cls) { return buildMap(cls, true).values().iterator(); } @@ -18,6 +27,15 @@ return buildMap(cls, true).values().iterator(); } + /** + * Returns an specific field of some class. If definedIn is null, it searchs for the field named 'name' inside the class cls. + * If definedIn is different than null, tries to find the specified field name in the specified class cls which should be defined in + * class definedIn (either equals cls or a one of it's superclasses) + * @param cls the class where the field is to be searched + * @param name the field name + * @param definedIn the superclass (or the class itself) of cls where the field was defined + * @return the field itself + */ public Field field(Class cls, String name, Class definedIn) { Map fields = buildMap(cls, definedIn != null); Field field = (Field) fields.get(definedIn != null ? (Object) new FieldKey(name, definedIn, 0) : (Object) name); Index: /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/converters/reflection/PureJavaReflectionProvider.java =================================================================== --- /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/converters/reflection/PureJavaReflectionProvider.java (revision 704) +++ /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/converters/reflection/PureJavaReflectionProvider.java (working copy) @@ -1,7 +1,5 @@ package com.thoughtworks.xstream.converters.reflection; -import com.thoughtworks.xstream.core.JVM; - import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; @@ -19,6 +17,8 @@ import java.util.Iterator; import java.util.Map; +import com.thoughtworks.xstream.core.JVM; + /** * Pure Java ObjectFactory that instantiates objects using standard Java reflection, however the types of objects * that can be constructed are limited. @@ -32,7 +32,7 @@ public class PureJavaReflectionProvider implements ReflectionProvider { private final Map serializedDataCache = Collections.synchronizedMap(new HashMap()); - + protected FieldDictionary fieldDictionary = new FieldDictionary(); public Object newInstance(Class type) { @@ -157,4 +157,8 @@ } } + public Field getField(Class definedIn, String fieldName) { + return fieldDictionary.field(definedIn, fieldName, null); + } + } Index: /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/converters/reflection/ReflectionConverter.java =================================================================== --- /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/converters/reflection/ReflectionConverter.java (revision 704) +++ /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/converters/reflection/ReflectionConverter.java (working copy) @@ -1,5 +1,6 @@ package com.thoughtworks.xstream.converters.reflection; +import java.lang.reflect.Field; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; @@ -64,7 +65,7 @@ Collection list = (Collection) newObj; for (Iterator iter = list.iterator(); iter.hasNext();) { Object obj = iter.next(); - writeField(mapping.getItemFieldName(), mapping.getItemType(), definedIn, obj); + writeField(fieldName, mapping.getItemFieldName(), mapping.getItemType(), definedIn, obj); } } else { context.convertAnother(newObj); @@ -70,7 +71,7 @@ context.convertAnother(newObj); } } else { - writeField(fieldName, fieldType, definedIn, newObj); + writeField(fieldName, fieldName, fieldType, definedIn, newObj); seenFields.add(fieldName); } } @@ -76,11 +77,11 @@ } } - private void writeField(String fieldName, Class fieldType, Class definedIn, Object newObj) { - if (!mapper.shouldSerializeMember(definedIn, fieldName)) { + private void writeField(String fieldName, String aliasName, Class fieldType, Class definedIn, Object newObj) { + if (!mapper.shouldSerializeMember(definedIn, aliasName)) { return; } - writer.startNode(mapper.serializedMember(definedIn, fieldName)); + writer.startNode(mapper.serializedMember(definedIn, aliasName)); Class actualType = newObj.getClass(); @@ -89,7 +90,7 @@ writer.addAttribute(mapper.attributeForImplementationClass(), mapper.serializedClass(actualType)); } - if (seenFields.contains(fieldName)) { + if (seenFields.contains(aliasName)) { writer.addAttribute(mapper.attributeForClassDefiningField(), mapper.serializedClass(definedIn)); } @@ -94,7 +95,8 @@ } if (source != newObj) { - context.convertAnother(newObj); + Field field = reflectionProvider.getField(definedIn,fieldName); + fieldMarshall(context, newObj, field, reflectionProvider); } else { writer.addAttribute("self", ""); } @@ -104,7 +106,11 @@ }); } - public Object unmarshal(final HierarchicalStreamReader reader, final UnmarshallingContext context) { + protected void fieldMarshall(final MarshallingContext context, Object newObj, Field field, ReflectionProvider reflectionProvider) { + context.convertAnother(newObj); + } + + public Object unmarshal(final HierarchicalStreamReader reader, final UnmarshallingContext context) { final Object result = instantiateNewInstance(context, reader.getAttribute(mapper.attributeForReadResolveField())); final SeenFields seenFields = new SeenFields(); Iterator it = reader.getAttributeNames(); @@ -137,7 +143,12 @@ final Object value; String self = reader.getAttribute("self"); if (self == null) { - value = context.convertAnother(result, type); + if (fieldExistsInClass) { + Field field = reflectionProvider.getField(result.getClass(),fieldName); + value = fieldUnmarshall(context, result, type, field, reflectionProvider); + } else { + value = context.convertAnother(result, type); + } } else { value = result; } @@ -155,6 +166,9 @@ return serializationMethodInvoker.callReadResolve(result); } + protected Object fieldUnmarshall(final UnmarshallingContext context, final Object result, Class type, Field field, ReflectionProvider reflectionProvider) { + return context.convertAnother(result, type); + } private Map writeValueToImplicitCollection(UnmarshallingContext context, Object value, Map implicitCollections, Object result, String itemFieldName) { String fieldName = mapper.getFieldNameForItemTypeAndName(context.getRequiredType(), value.getClass(), itemFieldName); Index: /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/converters/reflection/ReflectionProvider.java =================================================================== --- /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/converters/reflection/ReflectionProvider.java (revision 704) +++ /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/converters/reflection/ReflectionProvider.java (working copy) @@ -1,5 +1,7 @@ package com.thoughtworks.xstream.converters.reflection; +import java.lang.reflect.Field; + /** * Provides core reflection services. */ @@ -5,6 +7,11 @@ */ public interface ReflectionProvider { + /** + * Creates a new instance of the specified type using the default (null) constructor. + * @param type the type to instantiate + * @return a new instance of this type + */ Object newInstance(Class type); void visitSerializableFields(Object object, Visitor visitor); @@ -15,7 +22,28 @@ boolean fieldDefinedInClass(String fieldName, Class type); + /** + * A visitor interface for serializable fields defined in a class. + * + */ interface Visitor { + + /** + * Callback for each visit + * @param name field name + * @param type field type + * @param definedIn where the field was defined + * @param value field value + */ void visit(String name, Class type, Class definedIn, Object value); } + + /** + * Returns a field defined in some class. + * @param definedIn class where the field was defined + * @param fieldName field name + * @return the field itself + */ + Field getField(Class definedIn, String fieldName); + } Index: /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/converters/reflection/SerializableConverter.java =================================================================== --- /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/converters/reflection/SerializableConverter.java (revision 704) +++ /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/converters/reflection/SerializableConverter.java (working copy) @@ -1,15 +1,5 @@ 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.UnmarshallingContext; -import com.thoughtworks.xstream.core.util.CustomObjectInputStream; -import com.thoughtworks.xstream.core.util.CustomObjectOutputStream; -import com.thoughtworks.xstream.io.HierarchicalStreamReader; -import com.thoughtworks.xstream.io.HierarchicalStreamWriter; -import com.thoughtworks.xstream.mapper.Mapper; - import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; @@ -26,6 +16,17 @@ import java.util.List; import java.util.Map; +import com.thoughtworks.xstream.converters.ConversionException; +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.core.JVM; +import com.thoughtworks.xstream.core.util.CustomObjectInputStream; +import com.thoughtworks.xstream.core.util.CustomObjectOutputStream; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; +import com.thoughtworks.xstream.mapper.Mapper; + /** * Emulates the mechanism used by standard Java Serialization for classes that implement java.io.Serializable AND * implement a custom readObject()/writeObject() method. @@ -63,10 +64,10 @@ private static final String ELEMENT_FIELD = "field"; private static final String ATTRIBUTE_NAME = "name"; - public SerializableConverter(Mapper mapper, ReflectionProvider reflectionProvider) { + public SerializableConverter(Mapper mapper, ReflectionProvider reflectionProvider, JVM jvm) { this.mapper = mapper; this.reflectionProvider = reflectionProvider; - this.reflectionConverter = new ReflectionConverter(mapper, new UnserializableParentsReflectionProvider(reflectionProvider)); + this.reflectionConverter = jvm.getReflectionConverter(mapper, new UnserializableParentsReflectionProvider(reflectionProvider)); } public boolean canConvert(Class type) { @@ -407,6 +408,10 @@ return context.keys(); } + public Object convertAnother(Object current, Class type, Converter converter) { + return context.convertAnother(current, type, converter); + } + }); } else { currentType[0] = mapper.defaultImplementationOf(mapper.realClass(nodeName)); @@ -461,5 +466,9 @@ return reflectionProvider.fieldDefinedInClass(fieldName, type); } + public Field getField(Class definedIn, String fieldName) { + return reflectionProvider.getField(definedIn, fieldName); + } + } } Index: /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/core/JVM.java =================================================================== --- /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/core/JVM.java (revision 704) +++ /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/core/JVM.java (working copy) @@ -1,7 +1,11 @@ package com.thoughtworks.xstream.core; +import com.thoughtworks.xstream.annotations.AnnotationProvider; +import com.thoughtworks.xstream.annotations.Java15ReflectionConverter; 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.mapper.Mapper; import java.lang.reflect.Field; import java.security.AccessControlException; @@ -106,4 +110,19 @@ public static synchronized boolean reverseMemberDefinition() { return reverseMemberOrder; } + + /** + * Returns the a new reflection converter, either java 1.5 or 1.3 compatible. + * @param mapper the mapper to use + * @param provider the provider to use + * @return + */ + public ReflectionConverter getReflectionConverter(Mapper mapper, ReflectionProvider provider) { + if (JVM.is15()) { + // need a provider.newInstance(class, args) + return new Java15ReflectionConverter(mapper, provider, (AnnotationProvider) provider.newInstance(loadClass("com.thoughtworks.xstream.annotations.AnnotationProvider"))); + } else { + return new ReflectionConverter(mapper, provider); + } + } } Index: /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/core/ReferenceByIdMarshaller.java =================================================================== --- /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/core/ReferenceByIdMarshaller.java (revision 704) +++ /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/core/ReferenceByIdMarshaller.java (working copy) @@ -50,9 +50,7 @@ this(writer, converterLookup, classMapper, new SequenceGenerator(1)); } - public void convertAnother(Object item) { - Converter converter = converterLookup.lookupConverterForType(item.getClass()); - + public void convert(Object item, Converter converter) { if (getMapper().isImmutableValueType(item.getClass())) { // strings, ints, dates, etc... don't bother using references. converter.marshal(item, writer, this); Index: /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/core/ReferenceByIdUnmarshaller.java =================================================================== --- /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/core/ReferenceByIdUnmarshaller.java (revision 704) +++ /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/core/ReferenceByIdUnmarshaller.java (working copy) @@ -1,6 +1,10 @@ package com.thoughtworks.xstream.core; +import java.util.HashMap; +import java.util.Map; + import com.thoughtworks.xstream.alias.ClassMapper; +import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.converters.ConverterLookup; import com.thoughtworks.xstream.core.util.FastStack; import com.thoughtworks.xstream.io.HierarchicalStreamReader; @@ -6,9 +10,6 @@ import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.mapper.Mapper; -import java.util.HashMap; -import java.util.Map; - public class ReferenceByIdUnmarshaller extends TreeUnmarshaller { private Map values = new HashMap(); @@ -27,7 +28,7 @@ super(root, reader, converterLookup, classMapper); } - public Object convertAnother(Object parent, Class type) { + protected Object convert(Object parent, Class type, Converter converter) { if (parentIdStack.size() > 0) { // handles circular references Object parentId = parentIdStack.peek(); if (!values.containsKey(parentId)) { // see AbstractCircularReferenceTest.testWeirdCircularReference() @@ -40,7 +41,7 @@ } else { String currentId = reader.getAttribute("id"); parentIdStack.push(currentId); - Object result = super.convertAnother(parent, type); + Object result = super.convert(parent, type, converter); values.put(currentId, result); parentIdStack.popSilently(); return result; @@ -47,4 +48,5 @@ } } + } Index: /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/core/ReferenceByXPathMarshaller.java =================================================================== --- /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/core/ReferenceByXPathMarshaller.java (revision 704) +++ /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/core/ReferenceByXPathMarshaller.java (working copy) @@ -27,9 +27,7 @@ this(writer, converterLookup, (Mapper)classMapper); } - public void convertAnother(Object item) { - Converter converter = converterLookup.lookupConverterForType(item.getClass()); - + protected void convert(Object item, Converter converter) { if (getMapper().isImmutableValueType(item.getClass())) { // strings, ints, dates, etc... don't bother using references. converter.marshal(item, writer, this); @@ -45,5 +43,4 @@ } } } - } Index: /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/core/ReferenceByXPathUnmarshaller.java =================================================================== --- /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/core/ReferenceByXPathUnmarshaller.java (revision 704) +++ /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/core/ReferenceByXPathUnmarshaller.java (working copy) @@ -1,6 +1,10 @@ package com.thoughtworks.xstream.core; +import java.util.HashMap; +import java.util.Map; + import com.thoughtworks.xstream.alias.ClassMapper; +import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.converters.ConverterLookup; import com.thoughtworks.xstream.core.util.FastStack; import com.thoughtworks.xstream.io.HierarchicalStreamReader; @@ -9,9 +13,6 @@ import com.thoughtworks.xstream.io.path.PathTrackingReader; import com.thoughtworks.xstream.mapper.Mapper; -import java.util.HashMap; -import java.util.Map; - public class ReferenceByXPathUnmarshaller extends TreeUnmarshaller { private Map values = new HashMap(); @@ -32,7 +33,7 @@ this(root, reader, converterLookup, (Mapper)classMapper); } - public Object convertAnother(Object parent, Class type) { + protected Object convert(Object parent, Class type, Converter converter) { if (parentPathStack.size() > 0) { // handles circular references Object parentPath = parentPathStack.peek(); if (!values.containsKey(parentPath)) { // see AbstractCircularReferenceTest.testWeirdCircularReference() @@ -45,7 +46,7 @@ return values.get(currentPath.apply(new Path(relativePathOfReference))); } else { parentPathStack.push(currentPath); - Object result = super.convertAnother(parent, type); + Object result = super.convert(parent, type, converter); values.put(currentPath, result); parentPathStack.popSilently(); return result; Index: /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/core/TreeMarshaller.java =================================================================== --- /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/core/TreeMarshaller.java (revision 704) +++ /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/core/TreeMarshaller.java (working copy) @@ -45,6 +45,15 @@ } public void convertAnother(Object item) { + Converter converter = converterLookup.lookupConverterForType(item.getClass()); + convert(item, converter); + } + + public void convertAnother(Object item, Converter converter) { + convert(item, converter); + } + + protected void convert(Object item, Converter converter) { if (parentObjects.containsId(item)) { throw new CircularReferenceException(); } @@ -49,7 +58,6 @@ throw new CircularReferenceException(); } parentObjects.associateId(item, ""); - Converter converter = converterLookup.lookupConverterForType(item.getClass()); converter.marshal(item, writer, this); parentObjects.removeId(item); } Index: /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/core/TreeUnmarshaller.java =================================================================== --- /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/core/TreeUnmarshaller.java (revision 704) +++ /home/guilherme/workspace/xstreamold/src/java/com/thoughtworks/xstream/core/TreeUnmarshaller.java (working copy) @@ -40,9 +40,18 @@ this(root, reader, converterLookup, (Mapper)classMapper); } + public Object convertAnother(Object parent, Class type) { - try { - Converter converter = converterLookup.lookupConverterForType(type); + Converter converter = converterLookup.lookupConverterForType(type); + return convert(parent, type,converter); + } + + public Object convertAnother(Object parent, Class type, Converter converter) { + return convert(parent, type, converter); + } + + protected Object convert(Object parent, Class type, Converter converter) { + try { types.push(mapper.defaultImplementationOf(type)); Object result = converter.unmarshal(reader, this); types.popSilently(); @@ -55,7 +64,7 @@ addInformationTo(conversionException, type); throw conversionException; } - } + } private void addInformationTo(ErrorWriter errorWriter, Class type) { errorWriter.add("class", type.getName()); Index: /home/guilherme/workspace/xstreamold/src/test/com/thoughtworks/acceptance/annotations/AnnotationsTest.java =================================================================== --- /home/guilherme/workspace/xstreamold/src/test/com/thoughtworks/acceptance/annotations/AnnotationsTest.java (revision 704) +++ /home/guilherme/workspace/xstreamold/src/test/com/thoughtworks/acceptance/annotations/AnnotationsTest.java (working copy) @@ -17,6 +17,7 @@ import com.thoughtworks.xstream.io.HierarchicalStreamWriter; /** + * Simple tests for class annotations * * @author Chung-Onn Cheong * @author Mauro Talevi @@ -23,7 +24,7 @@ */ public class AnnotationsTest extends AbstractAcceptanceTest { - public void testAnnotations() { + public void testAnnotations() { Annotations.configureAliases(xstream, Person.class, AddressBookInfo.class); Map map = new HashMap(); map.put("first person", new Person("john doe")); Index: /home/guilherme/workspace/xstreamold/src/test/com/thoughtworks/acceptance/annotations/DepthOverridingTest.java =================================================================== --- /home/guilherme/workspace/xstreamold/src/test/com/thoughtworks/acceptance/annotations/DepthOverridingTest.java (revision 0) +++ /home/guilherme/workspace/xstreamold/src/test/com/thoughtworks/acceptance/annotations/DepthOverridingTest.java (revision 0) @@ -0,0 +1,83 @@ +package com.thoughtworks.acceptance.annotations; + +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; + +import com.thoughtworks.acceptance.AbstractAcceptanceTest; +import com.thoughtworks.xstream.annotations.XStreamConverter; +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +/** + * Tests for depth converter overriding + * + * @author Guilherme Silveira + */ +public class DepthOverridingTest extends AbstractAcceptanceTest { + + public static class Task { + + @XStreamConverter(GregorianTimeConverter.class) + private Calendar date; + + @XStreamConverter(GregorianTimeConverter.class) + private Calendar time; + + public Task(Calendar date, Calendar time) { + this.date = date; + this.time = time; + } + + @Override + public boolean equals(Object obj) { + return obj != null && Task.class.equals(obj.getClass()) + && ((Task) obj).date.equals(date) + && ((Task) obj).time.equals(time); + } + + } + + public void testConverterOverriding() { + Task task = new Task(new GregorianCalendar(1981, 9, 18), + new GregorianCalendar(0, 0, 0, 30, 20)); + String xml = "\n" + + " \n" + + " 372222000000\n" + + " \n" + + " \n" + + ""; + assertBothWays(task, xml); + } + + public static class GregorianTimeConverter implements Converter { + + public void marshal(Object source, HierarchicalStreamWriter writer, + MarshallingContext context) { + Calendar calendar = (Calendar) source; + writer.startNode("cal"); + writer.setValue(String.valueOf(calendar.getTime().getTime())); + writer.endNode(); + } + + public Object unmarshal(HierarchicalStreamReader reader, + UnmarshallingContext context) { + reader.moveDown(); + GregorianCalendar calendar = new GregorianCalendar(); + calendar.setTime(new Date(Long.parseLong(reader.getValue()))); + reader.moveUp(); + return calendar; + } + + public boolean canConvert(Class type) { + return type.equals(Calendar.class); + } + + } + +} Index: /home/guilherme/workspace/xstreamold/src/test/com/thoughtworks/acceptance/annotations/SimpleOverridingTest.java =================================================================== --- /home/guilherme/workspace/xstreamold/src/test/com/thoughtworks/acceptance/annotations/SimpleOverridingTest.java (revision 0) +++ /home/guilherme/workspace/xstreamold/src/test/com/thoughtworks/acceptance/annotations/SimpleOverridingTest.java (revision 0) @@ -0,0 +1,75 @@ +package com.thoughtworks.acceptance.annotations; + +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; + +import com.thoughtworks.acceptance.AbstractAcceptanceTest; +import com.thoughtworks.xstream.annotations.XStreamConverter; +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +/** + * Tests for converter overriding + * + * @author Guilherme Silveira + */ +public class SimpleOverridingTest extends AbstractAcceptanceTest { + + public static class Task { + + @XStreamConverter(GregorianTimeConverter.class) + private Calendar date; + + @XStreamConverter(GregorianTimeConverter.class) + private Calendar time; + + public Task(Calendar date, Calendar time) { + this.date = date; + this.time = time; + } + + @Override + public boolean equals(Object obj) { + return obj != null && Task.class.equals(obj.getClass()) + && ((Task) obj).date.equals(date) + && ((Task) obj).time.equals(time); + } + + } + + public void testConverterOverriding() { + Task task = new Task(new GregorianCalendar(1981, 9, 18), + new GregorianCalendar(0, 0, 0, 30, 20)); + String xml = "\n" + + " 372222000000\n" + + " \n" + + ""; + assertBothWays(task, xml); + } + + public static class GregorianTimeConverter implements Converter { + + public void marshal(Object source, HierarchicalStreamWriter writer, + MarshallingContext context) { + Calendar calendar = (Calendar) source; + writer.setValue(String.valueOf(calendar.getTime().getTime())); + } + + public Object unmarshal(HierarchicalStreamReader reader, + UnmarshallingContext context) { + GregorianCalendar calendar = new GregorianCalendar(); + calendar.setTime(new Date(Long.parseLong(reader.getValue()))); + return calendar; + } + + public boolean canConvert(Class type) { + return type.equals(Calendar.class); + } + + } + +}