Index: src/test/com/thoughtworks/acceptance/FieldAliasTest.java =================================================================== --- src/test/com/thoughtworks/acceptance/FieldAliasTest.java (revision 0) +++ src/test/com/thoughtworks/acceptance/FieldAliasTest.java (revision 0) @@ -0,0 +1,81 @@ +package com.thoughtworks.acceptance; + +import com.thoughtworks.acceptance.objects.Software; +import com.thoughtworks.acceptance.objects.OpenSourceSoftware; + +import java.util.ArrayList; + +/** + * @author David Blevins + */ +public class FieldAliasTest extends AbstractAcceptanceTest { + + public void testAllowsIndividualFieldsToBeAliased() { + Software in = new Software("ms", "word"); + xstream.alias("software", Software.class); + xstream.aliasField("CUSTOM-VENDOR", Software.class, "vendor"); + xstream.aliasField("CUSTOM-NAME", Software.class, "name"); + + String expectedXml = "" + + "\n" + + " ms\n" + + " word\n" + + ""; + + assertBothWays(in, expectedXml); + } + + + public void testInheritsFieldAliasFromSuperclass() { + ArrayList software = new ArrayList(); + software.add(new Software("ms", "word")); + software.add(new OpenSourceSoftware("apache", "geronimo", "asl 2.0")); + + xstream.alias("oss", OpenSourceSoftware.class); + xstream.alias("software", Software.class); + xstream.aliasField("CUSTOM-VENDOR", Software.class, "vendor"); + xstream.aliasField("CUSTOM-NAME", Software.class, "name"); + + String expectedXml = "" + + "\n" + + " \n" + + " ms\n" + + " word\n" + + " \n" + + " \n" + + " asl 2.0\n" + + " apache\n" + + " geronimo\n" + + " \n" + + ""; + + assertBothWays(software, expectedXml); + } + + public void testAllowsSubclassToOverrideFieldAliasInSuperclass() { + ArrayList software = new ArrayList(); + software.add(new Software("ms", "word")); + software.add(new OpenSourceSoftware("apache", "geronimo", "asl 2.0")); + + xstream.alias("oss", OpenSourceSoftware.class); + xstream.aliasField("ORGANIZATION", OpenSourceSoftware.class, "vendor"); + xstream.alias("software", Software.class); + xstream.aliasField("CUSTOM-VENDOR", Software.class, "vendor"); + xstream.aliasField("CUSTOM-NAME", Software.class, "name"); + + String expectedXml = "" + + "\n" + + " \n" + + " ms\n" + + " word\n" + + " \n" + + " \n" + + " asl 2.0\n" + + " apache\n" + + " geronimo\n" + + " \n" + + ""; + + assertBothWays(software, expectedXml); + } +} Index: src/java/com/thoughtworks/xstream/mapper/FieldAliasingMapper.java =================================================================== --- src/java/com/thoughtworks/xstream/mapper/FieldAliasingMapper.java (revision 847) +++ src/java/com/thoughtworks/xstream/mapper/FieldAliasingMapper.java (working copy) @@ -12,6 +12,7 @@ * entirely. * * @author Joe Walnes + * @author David Blevins */ public class FieldAliasingMapper extends MapperWrapper { @@ -35,12 +36,12 @@ aliasToFieldMap.put(key(type, alias), fieldName); } - private Object key(Class type, String value) { - return type.getName() + '.' + value; + private Key key(Class type, String value) { + return new Key(type, value); } public String serializedMember(Class type, String memberName) { - String alias = (String) fieldToAliasMap.get(key(type, memberName)); + String alias = get(key(type, memberName), fieldToAliasMap); if (alias == null) { return super.serializedMember(type, memberName); } else { @@ -49,7 +50,7 @@ } public String realMember(Class type, String serialized) { - String real = (String) aliasToFieldMap.get(key(type, serialized)); + String real = get(key(type, serialized), aliasToFieldMap); if (real == null) { return super.realMember(type, serialized); } else { @@ -64,4 +65,66 @@ public void omitField(Class type, String fieldName) { fieldsToOmit.add(key(type, fieldName)); } + + private String get(Key key, Map map){ + String value = (String) map.get(key); + if (value != null){ + return value; + } + + Class superclass = key.getType().getSuperclass(); + + if (superclass == null || superclass == Object.class){ + return null; + } + + value = get(new Key(superclass, key.getName()), map); + + if (value != null){ + map.put(key, value); + } + + return value; + } + + private static class Key { + private final Class type; + private final String name; + + public Key(Class type, String name) { + this.type = type; + this.name = name; + } + + public Class getType() { + return type; + } + + public String getName() { + return name; + } + + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final Key key = (Key) o; + + if (name != null ? !name.equals(key.name) : key.name != null) return false; + if (type != null ? !type.equals(key.type) : key.type != null) return false; + + return true; + } + + public int hashCode() { + int result; + result = (type != null ? type.hashCode() : 0); + result = 29 * result + (name != null ? name.hashCode() : 0); + return result; + } + + public String toString() { + return "key("+type.getName()+", "+name+")"; + } + } } Index: src/java/com/thoughtworks/xstream/converters/reflection/AbstractReflectionConverter.java =================================================================== --- src/java/com/thoughtworks/xstream/converters/reflection/AbstractReflectionConverter.java (revision 847) +++ src/java/com/thoughtworks/xstream/converters/reflection/AbstractReflectionConverter.java (working copy) @@ -71,13 +71,13 @@ Collection list = (Collection) newObj; for (Iterator iter = list.iterator(); iter.hasNext();) { Object obj = iter.next(); - writeField(fieldName, mapping.getItemFieldName(), mapping.getItemType(), definedIn, obj); + writeField(fieldName, mapping.getItemFieldName(), mapping.getItemType(), source.getClass(), obj); } } else { context.convertAnother(newObj); } } else { - writeField(fieldName, fieldName, fieldType, definedIn, newObj); + writeField(fieldName, fieldName, fieldType, source.getClass(), newObj); seenFields.add(fieldName); } }