Index: src/test/com/thoughtworks/xstream/mapper/ConditionalConversionMapperTest.java
===================================================================
--- src/test/com/thoughtworks/xstream/mapper/ConditionalConversionMapperTest.java (revision 0)
+++ src/test/com/thoughtworks/xstream/mapper/ConditionalConversionMapperTest.java (revision 0)
@@ -0,0 +1,99 @@
+package com.thoughtworks.xstream.mapper;
+
+import junit.framework.TestCase;
+import com.thoughtworks.xstream.core.DefaultConverterLookup;
+import com.thoughtworks.xstream.core.util.CompositeClassLoader;
+import com.thoughtworks.xstream.converters.basic.NullConverter;
+import com.thoughtworks.xstream.converters.basic.IntConverter;
+import com.thoughtworks.xstream.converters.basic.FloatConverter;
+import com.thoughtworks.xstream.converters.basic.DoubleConverter;
+import com.thoughtworks.xstream.converters.basic.LongConverter;
+import com.thoughtworks.xstream.converters.basic.ShortConverter;
+import com.thoughtworks.xstream.converters.basic.CharConverter;
+import com.thoughtworks.xstream.converters.basic.BooleanConverter;
+import com.thoughtworks.xstream.converters.basic.ByteConverter;
+import com.thoughtworks.xstream.converters.basic.StringConverter;
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.SingleValueConverter;
+import com.thoughtworks.xstream.converters.SingleValueConverterWrapper;
+import com.thoughtworks.acceptance.objects.Software;
+
+import java.lang.reflect.Field;
+import java.util.regex.Pattern;
+
+/**
+ * @author David Blevins
+ */
+public class ConditionalConversionMapperTest extends TestCase {
+ private ConditionalConversionMapper mapper;
+ private DefaultConverterLookup converterLookup;
+ private static final int PRIORITY_NORMAL = 1;
+ private static final int PRIORITY_VERY_HIGH = 2;
+
+ protected void setUp() throws Exception {
+ mapper = new ConditionalConversionMapper(new DefaultMapper(new CompositeClassLoader()));
+ converterLookup = new DefaultConverterLookup(mapper);
+ registerConverter(new NullConverter(), PRIORITY_VERY_HIGH);
+ registerConverter(new IntConverter(), PRIORITY_NORMAL);
+ registerConverter(new FloatConverter(), PRIORITY_NORMAL);
+ registerConverter(new DoubleConverter(), PRIORITY_NORMAL);
+ registerConverter(new LongConverter(), PRIORITY_NORMAL);
+ registerConverter(new ShortConverter(), PRIORITY_NORMAL);
+ registerConverter(new CharConverter(), PRIORITY_NORMAL);
+ registerConverter(new BooleanConverter(), PRIORITY_NORMAL);
+ registerConverter(new ByteConverter(), PRIORITY_NORMAL);
+
+ registerConverter(new StringConverter(), PRIORITY_NORMAL);
+ mapper.setConverterLookup(converterLookup);
+ }
+
+ public void registerConverter(Converter converter, int priority) {
+ converterLookup.registerConverter(converter, priority);
+ }
+ public void registerConverter(SingleValueConverter converter, int priority) {
+ converterLookup.registerConverter(new SingleValueConverterWrapper(converter), priority);
+ }
+
+ public void testWillConvert() throws Exception {
+ Software software = new Software("microsoft", "outlook");
+ assertTrue("shouldSerialize",willSerialize(software, "vendor"));
+ }
+
+ public static class FilterStringConverter extends StringConverter {
+ private final Pattern pattern;
+
+ public FilterStringConverter(String regex) {
+ this.pattern = Pattern.compile(regex);
+ }
+
+ public boolean shouldConvert(Class type, Object value) {
+ String str = (String) value;
+ return !pattern.matcher(str).matches();
+ }
+ }
+
+ public void testWillNotConvert() throws Exception {
+ registerConverter(new FilterStringConverter(".*"), PRIORITY_NORMAL);
+
+ Software software = new Software("microsoft", "outlook");
+ assertTrue("shouldSerialize", !willSerialize(software, "vendor"));
+
+ }
+
+ public void testWillConvertSome() throws Exception {
+ registerConverter(new FilterStringConverter("micro.*"), PRIORITY_NORMAL);
+
+ Software software = new Software("microsoft", "outlook");
+
+ assertTrue("dont convert", !willSerialize(software, "vendor"));
+
+ software = new Software("scansoft", "omnipage");
+ assertTrue("do convert", willSerialize(software, "vendor"));
+ }
+
+ private boolean willSerialize(Object object, String fieldName) throws NoSuchFieldException, IllegalAccessException {
+ Field field = object.getClass().getField(fieldName);
+ return mapper.shouldSerializeMember(field.getDeclaringClass(), field.getName(), field.getType(), field.get(object));
+ }
+
+}
Index: src/test/com/thoughtworks/acceptance/ConditionalConverterTest.java
===================================================================
--- src/test/com/thoughtworks/acceptance/ConditionalConverterTest.java (revision 0)
+++ src/test/com/thoughtworks/acceptance/ConditionalConverterTest.java (revision 0)
@@ -0,0 +1,261 @@
+package com.thoughtworks.acceptance;
+
+import com.thoughtworks.xstream.converters.ConditionalConverter;
+import com.thoughtworks.xstream.converters.collections.CollectionConverter;
+import com.thoughtworks.xstream.converters.collections.MapConverter;
+import com.thoughtworks.xstream.converters.basic.IntConverter;
+import com.thoughtworks.xstream.converters.basic.StringConverter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+import java.util.HashMap;
+
+/**
+ * @author David Blevins
+ */
+public class ConditionalConverterTest extends AbstractAcceptanceTest {
+ private List people;
+
+ public static class Person extends StandardObject {
+ public String name;
+ public String hairColor;
+ public String eyeColor;
+ public int age;
+ public int weight;
+
+ public Person(String name, String hairColor, String eyeColor, int age, int weight) {
+ this.name = name;
+ this.hairColor = hairColor;
+ this.eyeColor = eyeColor;
+ this.age = age;
+ this.weight = weight;
+
+ }
+ }
+
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ people = new ArrayList();
+ people.add(new Person("Susan Snarky", "red", "green", 37, 150));
+ people.add(new Person("Mac Daddy", "brown", "brown", 23, 180));
+ people.add(new Person("Debbie Herbig", "brown", "brown", 56, 190));
+ people.add(new Person("Kenny Little", "blond", "blue", 12, 115));
+
+ xstream.alias("person", Person.class);
+ }
+
+ public void testNormalConversion() {
+ String expectedXml = ""+
+ "\n" +
+ " \n" +
+ " Susan Snarky\n" +
+ " red\n" +
+ " green\n" +
+ " 37\n" +
+ " 150\n" +
+ " \n" +
+ " \n" +
+ " Mac Daddy\n" +
+ " brown\n" +
+ " brown\n" +
+ " 23\n" +
+ " 180\n" +
+ " \n" +
+ " \n" +
+ " Debbie Herbig\n" +
+ " brown\n" +
+ " brown\n" +
+ " 56\n" +
+ " 190\n" +
+ " \n" +
+ " \n" +
+ " Kenny Little\n" +
+ " blond\n" +
+ " blue\n" +
+ " 12\n" +
+ " 115\n" +
+ " \n" +
+ "
";
+
+ assertBothWays(people, expectedXml);
+ }
+
+
+ public static class SkipIntConverter extends IntConverter {
+ public boolean shouldConvert(Class type, Object value) {
+ return false;
+ }
+ }
+
+ public void testConditionBasedOnType() {
+ xstream.registerConverter(new SkipIntConverter());
+
+ String expectedXml = ""+
+ "\n" +
+ " \n" +
+ " Susan Snarky\n" +
+ " red\n" +
+ " green\n" +
+ " \n" +
+ " \n" +
+ " Mac Daddy\n" +
+ " brown\n" +
+ " brown\n" +
+ " \n" +
+ " \n" +
+ " Debbie Herbig\n" +
+ " brown\n" +
+ " brown\n" +
+ " \n" +
+ " \n" +
+ " Kenny Little\n" +
+ " blond\n" +
+ " blue\n" +
+ " \n" +
+ "
";
+
+ assertBothWays(people, expectedXml);
+ }
+
+
+ public static class SkipSomeIntsConverter extends IntConverter {
+ public boolean shouldConvert(Class type, Object value) {
+ int i = ((Integer) value).intValue();
+ return (i > 0 && i < 50) || (i > 100 && i < 160);
+ }
+ }
+
+ public void testConditionBasedOnValue() {
+ xstream.registerConverter(new SkipSomeIntsConverter());
+
+ String expectedXml = ""+
+ "\n" +
+ " \n" +
+ " Susan Snarky\n" +
+ " red\n" +
+ " green\n" +
+ " 37\n" +
+ " 150\n" +
+ " \n" +
+ " \n" +
+ " Mac Daddy\n" +
+ " brown\n" +
+ " brown\n" +
+ " 23\n" +
+ " \n" +
+ " \n" +
+ " Debbie Herbig\n" +
+ " brown\n" +
+ " brown\n" +
+ " \n" +
+ " \n" +
+ " Kenny Little\n" +
+ " blond\n" +
+ " blue\n" +
+ " 12\n" +
+ " 115\n" +
+ " \n" +
+ "
";
+
+ assertBothWays(people, expectedXml);
+
+ }
+
+ public static class SkipEmptyCollectionConverter extends CollectionConverter implements ConditionalConverter {
+ public SkipEmptyCollectionConverter(Mapper mapper) {
+ super(mapper);
+ }
+
+ public boolean shouldConvert(Class type, Object value) {
+ return (value != null && ((Collection) value).size() != 0);
+ }
+ }
+
+ public static class SkipEmptyMapConverter extends MapConverter implements ConditionalConverter {
+ public SkipEmptyMapConverter(Mapper mapper) {
+ super(mapper);
+ }
+
+ public boolean shouldConvert(Class type, Object value) {
+ return (value != null && ((Map) value).size() != 0);
+ }
+ }
+
+ public static class SkipEmptyStringConverter extends StringConverter {
+ public boolean shouldConvert(Class type, Object value) {
+ return (value != null && ((String)value).length() != 0);
+ }
+ }
+
+ public static class Contact extends StandardObject {
+ public String name;
+ public String company;
+ public List emailAddresses = new ArrayList();
+ public Map addresses = new HashMap();
+ public Map telephone = new HashMap();
+ public String notes;
+
+ public Contact(String name) {
+ this.name = name;
+ }
+ }
+
+ public void testSkipNonSimpleTypes() {
+ List addressBook = new ArrayList();
+ Contact wallace = new Contact("Wallace");
+ wallace.company = "Window Washing Inventors, Inc.";
+ wallace.addresses.put("home", "62 West Wallaby Street");
+ addressBook.add(wallace);
+
+ Contact gromit = new Contact("Gromit");
+ gromit.emailAddresses.add("gromit@genious-k9s.net");
+ addressBook.add(gromit);
+
+ Contact jenny = new Contact("Jenny");
+ jenny.telephone.put("home", "867-5309");
+ jenny.notes = "Found this number on the bathroom wall.";
+ addressBook.add(jenny);
+
+ xstream.alias("contact", Contact.class);
+ xstream.registerConverter(new SkipEmptyCollectionConverter(xstream.getMapper()));
+ xstream.registerConverter(new SkipEmptyMapConverter(xstream.getMapper()));
+ xstream.registerConverter(new SkipEmptyStringConverter());
+
+ String expectedXml = ""+
+ "\n" +
+ " \n" +
+ " Wallace\n" +
+ " Window Washing Inventors, Inc.\n" +
+ " \n" +
+ " \n" +
+ " home\n" +
+ " 62 West Wallaby Street\n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " Gromit\n" +
+ " \n" +
+ " gromit@genious-k9s.net\n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " Jenny\n" +
+ " \n" +
+ " \n" +
+ " home\n" +
+ " 867-5309\n" +
+ " \n" +
+ " \n" +
+ " Found this number on the bathroom wall.\n" +
+ " \n" +
+ "
";
+
+ assertBothWays(addressBook, expectedXml);
+ }
+
+}
Index: src/java/com/thoughtworks/xstream/XStream.java
===================================================================
--- src/java/com/thoughtworks/xstream/XStream.java (revision 847)
+++ src/java/com/thoughtworks/xstream/XStream.java (working copy)
@@ -121,6 +121,7 @@
import com.thoughtworks.xstream.mapper.MapperWrapper;
import com.thoughtworks.xstream.mapper.OuterClassMapper;
import com.thoughtworks.xstream.mapper.XmlFriendlyMapper;
+import com.thoughtworks.xstream.mapper.ConditionalConversionMapper;
/**
@@ -368,6 +369,7 @@
if ( useXmlFriendlyMapper ){
mapper = new XmlFriendlyMapper(mapper);
}
+ mapper = new ConditionalConversionMapper(mapper);
mapper = new ClassAliasingMapper(mapper);
mapper = new FieldAliasingMapper(mapper);
mapper = new AttributeAliasingMapper(mapper);
@@ -426,6 +428,12 @@
if (attributeMapper != null) {
attributeMapper.setConverterLookup(converterLookup);
}
+
+ ConditionalConversionMapper conditionalConversionMapper = (ConditionalConversionMapper) this.mapper
+ .lookupMapperOfType(ConditionalConversionMapper.class);
+ if (conditionalConversionMapper != null) {
+ conditionalConversionMapper.setConverterLookup(converterLookup);
+ }
}
protected void setupAliases() {
Index: src/java/com/thoughtworks/xstream/mapper/ConditionalConversionMapper.java
===================================================================
--- src/java/com/thoughtworks/xstream/mapper/ConditionalConversionMapper.java (revision 0)
+++ src/java/com/thoughtworks/xstream/mapper/ConditionalConversionMapper.java (revision 0)
@@ -0,0 +1,39 @@
+package com.thoughtworks.xstream.mapper;
+
+import com.thoughtworks.xstream.converters.ConverterLookup;
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.ConditionalConverter;
+
+/**
+ * @author David Blevins
+ */
+public class ConditionalConversionMapper extends MapperWrapper {
+
+ private ConverterLookup converterLookup;
+
+ // TODO: Remove this - JS
+ public ConditionalConversionMapper(Mapper wrapped) {
+ this(wrapped, null);
+ }
+
+ public ConditionalConversionMapper(Mapper wrapped, ConverterLookup converterLookup) {
+ super(wrapped);
+ this.converterLookup = converterLookup;
+ }
+
+ // TODO: Is this needed now that it injected in ctor? - MT
+ // YES, but I want to remove it. In the ctor NULL is injected!!! - JS
+ public void setConverterLookup(ConverterLookup converterLookup) {
+ this.converterLookup = converterLookup;
+ }
+
+ public boolean shouldSerializeMember(Class definedIn, String fieldName, Class fieldType, Object value) {
+ Converter converter = converterLookup.lookupConverterForType(fieldType);
+ if (converter instanceof ConditionalConverter) {
+ ConditionalConverter conditionalConverter = (ConditionalConverter) converter;
+ return conditionalConverter.shouldConvert(fieldType, value);
+ }
+
+ return super.shouldSerializeMember(definedIn, fieldName, fieldType, value);
+ }
+}
Index: src/java/com/thoughtworks/xstream/mapper/Mapper.java
===================================================================
--- src/java/com/thoughtworks/xstream/mapper/Mapper.java (revision 847)
+++ src/java/com/thoughtworks/xstream/mapper/Mapper.java (working copy)
@@ -92,7 +92,7 @@
*
* @since 1.1.3
*/
- boolean shouldSerializeMember(Class definedIn, String fieldName);
+ boolean shouldSerializeMember(Class definedIn, String fieldName, Class fieldType, Object value);
interface ImplicitCollectionMapping {
String getFieldName();
Index: src/java/com/thoughtworks/xstream/mapper/MapperWrapper.java
===================================================================
--- src/java/com/thoughtworks/xstream/mapper/MapperWrapper.java (revision 847)
+++ src/java/com/thoughtworks/xstream/mapper/MapperWrapper.java (working copy)
@@ -78,8 +78,8 @@
return wrapped.getImplicitCollectionDefForFieldName(itemType, fieldName);
}
- public boolean shouldSerializeMember(Class definedIn, String fieldName) {
- return wrapped.shouldSerializeMember(definedIn, fieldName);
+ public boolean shouldSerializeMember(Class definedIn, String fieldName, Class fieldType, Object value) {
+ return wrapped.shouldSerializeMember(definedIn, fieldName, fieldType, value);
}
public SingleValueConverter getConverterFromItemType(String fieldName, Class type) {
Index: src/java/com/thoughtworks/xstream/mapper/DefaultMapper.java
===================================================================
--- src/java/com/thoughtworks/xstream/mapper/DefaultMapper.java (revision 847)
+++ src/java/com/thoughtworks/xstream/mapper/DefaultMapper.java (working copy)
@@ -106,7 +106,7 @@
return null;
}
- public boolean shouldSerializeMember(Class definedIn, String fieldName) {
+ public boolean shouldSerializeMember(Class definedIn, String fieldName, Class fieldType, Object value) {
return true;
}
Index: src/java/com/thoughtworks/xstream/converters/basic/AbstractSingleValueConverter.java
===================================================================
--- src/java/com/thoughtworks/xstream/converters/basic/AbstractSingleValueConverter.java (revision 847)
+++ src/java/com/thoughtworks/xstream/converters/basic/AbstractSingleValueConverter.java (working copy)
@@ -1,6 +1,7 @@
package com.thoughtworks.xstream.converters.basic;
import com.thoughtworks.xstream.converters.SingleValueConverter;
+import com.thoughtworks.xstream.converters.ConditionalConverter;
/**
* Base abstract implementation of {@link com.thoughtworks.xstream.converters.SingleValueConverter}.
@@ -12,7 +13,7 @@
* @author Mauro Talevi
* @see com.thoughtworks.xstream.converters.SingleValueConverter
*/
-public abstract class AbstractSingleValueConverter implements SingleValueConverter {
+public abstract class AbstractSingleValueConverter implements SingleValueConverter, ConditionalConverter {
public abstract boolean canConvert(Class type);
@@ -22,4 +23,7 @@
public abstract Object fromString(String str);
+ public boolean shouldConvert(Class type, Object value) {
+ return true;
+ }
}
Index: src/java/com/thoughtworks/xstream/converters/SingleValueConverterWrapper.java
===================================================================
--- src/java/com/thoughtworks/xstream/converters/SingleValueConverterWrapper.java (revision 847)
+++ src/java/com/thoughtworks/xstream/converters/SingleValueConverterWrapper.java (working copy)
@@ -11,7 +11,7 @@
* @see com.thoughtworks.xstream.converters.Converter
* @see com.thoughtworks.xstream.converters.SingleValueConverter
*/
-public class SingleValueConverterWrapper implements Converter, SingleValueConverter {
+public class SingleValueConverterWrapper implements Converter, SingleValueConverter, ConditionalConverter {
private final SingleValueConverter wrapped;
@@ -39,4 +39,10 @@
return fromString(reader.getValue());
}
+ public boolean shouldConvert(Class type, Object value) {
+ if (wrapped instanceof ConditionalConverter) {
+ return ((ConditionalConverter) wrapped).shouldConvert(type, value);
+ }
+ return true;
+ }
}
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)
@@ -84,7 +84,7 @@
}
private void writeField(String fieldName, String aliasName, Class fieldType, Class definedIn, Object newObj) {
- if (!mapper.shouldSerializeMember(definedIn, aliasName)) {
+ if (!mapper.shouldSerializeMember(definedIn, aliasName, fieldType, newObj)) {
return;
}
writer.startNode(mapper.serializedMember(definedIn, aliasName));
Index: src/java/com/thoughtworks/xstream/converters/ConditionalConverter.java
===================================================================
--- src/java/com/thoughtworks/xstream/converters/ConditionalConverter.java (revision 0)
+++ src/java/com/thoughtworks/xstream/converters/ConditionalConverter.java (revision 0)
@@ -0,0 +1,23 @@
+package com.thoughtworks.xstream.converters;
+
+/**
+ * @author David Blevins
+ */
+public interface ConditionalConverter extends ConverterMatcher {
+
+ /**
+ * Allows the converter for a specific type to conditionally
+ * have values (and their enclosing field element) skipped
+ * when marshalling.
+ *
+ * The value.getClass() can usually be used to determin the exact
+ * type of the object to be converted. The 'type' parameter is
+ * included in case 'value' may be null.
+ *
+ * @param type the type as specified by the field or by an implicit collection mapping
+ * @param value the value of the field, may be null
+ * @return true if the value should be converted and the enclosing element written
+ */
+
+ boolean shouldConvert(Class type, Object value);
+}