diff --git a/core/src/main/java/hudson/util/XStream2.java b/core/src/main/java/hudson/util/XStream2.java index ab59ec2a4d..e9e7dd3f7d 100644 --- a/core/src/main/java/hudson/util/XStream2.java +++ b/core/src/main/java/hudson/util/XStream2.java @@ -23,6 +23,7 @@ */ package hudson.util; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.mapper.AnnotationMapper; @@ -45,6 +46,7 @@ import jenkins.model.Jenkins; import hudson.model.Label; import hudson.model.Result; import hudson.model.Saveable; +import hudson.util.xstream.ImmutableListConverter; import hudson.util.xstream.ImmutableMapConverter; import hudson.util.xstream.MapperDelegate; @@ -107,6 +109,7 @@ public class XStream2 extends XStream { registerConverter(new RobustCollectionConverter(getMapper(),getReflectionProvider()),10); registerConverter(new ImmutableMapConverter(getMapper(),getReflectionProvider()),10); + registerConverter(new ImmutableListConverter(getMapper(),getReflectionProvider()),10); registerConverter(new ConcurrentHashMapConverter(getMapper(),getReflectionProvider()),10); registerConverter(new CopyOnWriteMap.Tree.ConverterImpl(getMapper()),10); // needs to override MapConverter registerConverter(new DescribableList.ConverterImpl(getMapper()),10); // explicitly added to handle subtypes @@ -124,6 +127,8 @@ public class XStream2 extends XStream { public String serializedClass(Class type) { if (type != null && ImmutableMap.class.isAssignableFrom(type)) return super.serializedClass(ImmutableMap.class); + else if (type != null && ImmutableList.class.isAssignableFrom(type)) + return super.serializedClass(ImmutableList.class); else return super.serializedClass(type); } diff --git a/core/src/main/java/hudson/util/xstream/ImmutableListConverter.java b/core/src/main/java/hudson/util/xstream/ImmutableListConverter.java new file mode 100644 index 0000000000..9774c7cdc8 --- /dev/null +++ b/core/src/main/java/hudson/util/xstream/ImmutableListConverter.java @@ -0,0 +1,71 @@ +/* + * The MIT License + * + * Copyright (c) 2010, InfraDNA, Inc. + * Copyright (c) 2011, Richard Mortimer + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package hudson.util.xstream; + +import com.google.common.collect.ImmutableList; +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.converters.collections.CollectionConverter; +import com.thoughtworks.xstream.converters.reflection.ReflectionProvider; +import com.thoughtworks.xstream.converters.reflection.SerializableConverter; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.mapper.Mapper; + +import java.util.ArrayList; +import java.util.List; + +/** + * {@link ImmutableList} should convert like a list, instead of via serialization. + * + * @author Kohsuke Kawaguchi + * @author Richard Mortimer + */ +public class ImmutableListConverter extends CollectionConverter { + private final SerializableConverter sc; + + public ImmutableListConverter(XStream xs) { + this(xs.getMapper(),xs.getReflectionProvider()); + } + + public ImmutableListConverter(Mapper mapper, ReflectionProvider reflectionProvider) { + super(mapper); + sc = new SerializableConverter(mapper,reflectionProvider); + } + + @Override + public boolean canConvert(Class type) { + return ImmutableList.class.isAssignableFrom(type); + } + + @Override + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + return ImmutableList.copyOf((List)super.unmarshal(reader, context)); + } + + @Override + protected Object createCollection(Class type) { + return new ArrayList(); + } +} diff --git a/core/src/test/java/hudson/util/XStream2Test.java b/core/src/test/java/hudson/util/XStream2Test.java index 96298f1325..b2b722299f 100644 --- a/core/src/test/java/hudson/util/XStream2Test.java +++ b/core/src/test/java/hudson/util/XStream2Test.java @@ -23,12 +23,16 @@ */ package hudson.util; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import junit.framework.TestCase; import hudson.model.Result; import hudson.model.Run; + import org.jvnet.hudson.test.Bug; +import java.io.InputStream; +import java.util.List; import java.util.Map; /** @@ -125,7 +129,6 @@ public class XStream2Test extends TestCase { Map m; } - public void testImmutableMap() { XStream2 xs = new XStream2(); @@ -166,6 +169,54 @@ public class XStream2Test extends TestCase { assertEquals(m,a.m); } + private static class ImmutableListHolder { + ImmutableList l; + } + + private static class ListHolder { + List l; + } + + public void testImmutableList() { + XStream2 xs = new XStream2(); + + roundtripImmutableList(xs, ImmutableList.of()); + roundtripImmutableList(xs, ImmutableList.of("abc")); + roundtripImmutableList(xs, ImmutableList.of("abc", "def")); + + roundtripImmutableListAsPlainList(xs, ImmutableList.of()); + roundtripImmutableListAsPlainList(xs, ImmutableList.of("abc")); + roundtripImmutableListAsPlainList(xs, ImmutableList.of("abc", "def")); + } + + /** + * Since the field type is {@link ImmutableList}, XML shouldn't contain a reference to the type name. + */ + private void roundtripImmutableList(XStream2 xs, ImmutableList l) { + ImmutableListHolder a = new ImmutableListHolder(); + a.l = l; + String xml = xs.toXML(a); + //System.out.println(xml); + assertFalse("shouldn't contain the class name",xml.contains("google")); + assertFalse("shouldn't contain the class name",xml.contains("class")); + a = (ImmutableListHolder)xs.fromXML(xml); + + assertSame(l.getClass(),a.l.getClass()); // should get back the exact same type, not just a random list + assertEquals(l,a.l); + } + + private void roundtripImmutableListAsPlainList(XStream2 xs, ImmutableList l) { + ListHolder a = new ListHolder(); + a.l = l; + String xml = xs.toXML(a); + //System.out.println(xml); + assertTrue("XML should mention the class name",xml.contains('\"'+ImmutableList.class.getName()+'\"')); + a = (ListHolder)xs.fromXML(xml); + + assertSame(l.getClass(),a.l.getClass()); // should get back the exact same type, not just a random list + assertEquals(l,a.l); + } + @Bug(8006) // Previously a null entry in an array caused NPE public void testEmptyStack() { assertEquals("",