Log Message
Merge changes for XSTR-755 from trunk.
Modified Paths
- branches/v-1.4.x/xstream/src/java/com/thoughtworks/xstream/converters/reflection/ExternalizableConverter.java
- branches/v-1.4.x/xstream/src/test/com/thoughtworks/acceptance/WriteReplaceTest.java
- branches/v-1.4.x/xstream-distribution/src/content/changes.html
Property Changed
Diff
Property changes: branches/v-1.4.x
Modified: svn:mergeinfo
Modified: branches/v-1.4.x/xstream/src/java/com/thoughtworks/xstream/converters/reflection/ExternalizableConverter.java (2280 => 2281)
--- branches/v-1.4.x/xstream/src/java/com/thoughtworks/xstream/converters/reflection/ExternalizableConverter.java 2014-04-08 20:32:23 UTC (rev 2280)
+++ branches/v-1.4.x/xstream/src/java/com/thoughtworks/xstream/converters/reflection/ExternalizableConverter.java 2014-04-08 20:43:59 UTC (rev 2281)
@@ -6,7 +6,7 @@
* The software in this package is published under the terms of the BSD
* style license a copy of which has been included with this distribution in
* the LICENSE.txt file.
- *
+ *
* Created on 24. August 2004 by Joe Walnes
*/
package com.thoughtworks.xstream.converters.reflection;
@@ -17,6 +17,7 @@
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.core.ClassLoaderReference;
import com.thoughtworks.xstream.core.JVM;
+import com.thoughtworks.xstream.core.ReferencingMarshallingContext;
import com.thoughtworks.xstream.core.util.CustomObjectInputStream;
import com.thoughtworks.xstream.core.util.CustomObjectOutputStream;
import com.thoughtworks.xstream.core.util.HierarchicalStreams;
@@ -43,9 +44,11 @@
private Mapper mapper;
private final ClassLoaderReference classLoaderReference;
+ private transient SerializationMethodInvoker serializationMethodInvoker;
/**
* Construct an ExternalizableConverter.
+ *
* @param mapper the Mapper chain
* @param classLoaderReference the reference to XStream's {@link ClassLoader} instance
* @since 1.4.5
@@ -53,6 +56,7 @@
public ExternalizableConverter(Mapper mapper, ClassLoaderReference classLoaderReference) {
this.mapper = mapper;
this.classLoaderReference = classLoaderReference;
+ serializationMethodInvoker = new SerializationMethodInvoker();
}
/**
@@ -73,42 +77,54 @@
return JVM.canCreateDerivedObjectOutputStream() && Externalizable.class.isAssignableFrom(type);
}
- public void marshal(Object source, final HierarchicalStreamWriter writer, final MarshallingContext context) {
- try {
- Externalizable externalizable = (Externalizable) source;
- CustomObjectOutputStream.StreamCallback callback = new CustomObjectOutputStream.StreamCallback() {
- public void writeToStream(Object object) {
- if (object == null) {
- writer.startNode("null");
- writer.endNode();
- } else {
- ExtendedHierarchicalStreamWriterHelper.startNode(writer, mapper.serializedClass(object.getClass()), object.getClass());
- context.convertAnother(object);
- writer.endNode();
+ public void marshal(final Object original, final HierarchicalStreamWriter writer, final MarshallingContext context) {
+ final Object source = serializationMethodInvoker.callWriteReplace(original);
+ if (source != original && context instanceof ReferencingMarshallingContext) {
+ ((ReferencingMarshallingContext)context).replace(original, source);
+ }
+ if (source.getClass() != original.getClass()) {
+ final String attributeName = mapper.aliasForSystemAttribute("resolves-to");
+ if (attributeName != null) {
+ writer.addAttribute(attributeName, mapper.serializedClass(source.getClass()));
+ }
+ context.convertAnother(source);
+ } else {
+ try {
+ Externalizable externalizable = (Externalizable)source;
+ CustomObjectOutputStream.StreamCallback callback = new CustomObjectOutputStream.StreamCallback() {
+ public void writeToStream(final Object object) {
+ if (object == null) {
+ writer.startNode("null");
+ writer.endNode();
+ } else {
+ ExtendedHierarchicalStreamWriterHelper.startNode(writer, mapper.serializedClass(object.getClass()), object.getClass());
+ context.convertAnother(object);
+ writer.endNode();
+ }
}
- }
- public void writeFieldsToStream(Map fields) {
- throw new UnsupportedOperationException();
- }
+ public void writeFieldsToStream(final Map fields) {
+ throw new UnsupportedOperationException();
+ }
- public void defaultWriteObject() {
- throw new UnsupportedOperationException();
- }
+ public void defaultWriteObject() {
+ throw new UnsupportedOperationException();
+ }
- public void flush() {
- writer.flush();
- }
+ public void flush() {
+ writer.flush();
+ }
- public void close() {
- throw new UnsupportedOperationException("Objects are not allowed to call ObjectOutput.close() from writeExternal()");
- }
- };
- CustomObjectOutputStream objectOutput = CustomObjectOutputStream.getInstance(context, callback);
- externalizable.writeExternal(objectOutput);
- objectOutput.popCallback();
- } catch (IOException e) {
- throw new ConversionException("Cannot serialize " + source.getClass().getName() + " using Externalization", e);
+ public void close() {
+ throw new UnsupportedOperationException("Objects are not allowed to call ObjectOutput.close() from writeExternal()");
+ }
+ };
+ final CustomObjectOutputStream objectOutput = CustomObjectOutputStream.getInstance(context, callback);
+ externalizable.writeExternal(objectOutput);
+ objectOutput.popCallback();
+ } catch (IOException e) {
+ throw new ConversionException("Cannot serialize " + source.getClass().getName() + " using Externalization", e);
+ }
}
}
@@ -149,7 +165,7 @@
CustomObjectInputStream objectInput = CustomObjectInputStream.getInstance(context, callback, classLoaderReference);
externalizable.readExternal(objectInput);
objectInput.popCallback();
- return externalizable;
+ return serializationMethodInvoker.callReadResolve(externalizable);
} catch (NoSuchMethodException e) {
throw new ConversionException("Cannot construct " + type.getClass() + ", missing default constructor", e);
} catch (InvocationTargetException e) {
@@ -164,4 +180,9 @@
throw new ConversionException("Cannot externalize " + type.getClass(), e);
}
}
+
+ private Object readResolve() {
+ serializationMethodInvoker = new SerializationMethodInvoker();
+ return this;
+ }
}
Modified: branches/v-1.4.x/xstream/src/test/com/thoughtworks/acceptance/WriteReplaceTest.java (2280 => 2281)
--- branches/v-1.4.x/xstream/src/test/com/thoughtworks/acceptance/WriteReplaceTest.java 2014-04-08 20:32:23 UTC (rev 2280)
+++ branches/v-1.4.x/xstream/src/test/com/thoughtworks/acceptance/WriteReplaceTest.java 2014-04-08 20:43:59 UTC (rev 2281)
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2004, 2005 Joe Walnes.
- * Copyright (C) 2006, 2007, 2008, 2009 XStream Committers.
+ * Copyright (C) 2006, 2007, 2008, 2009, 2014 XStream Committers.
* All rights reserved.
*
* The software in this package is published under the terms of the BSD
@@ -27,6 +27,8 @@
import java.util.ArrayList;
import java.util.List;
+import org.apache.commons.lang.StringUtils;
+
public class WriteReplaceTest extends AbstractAcceptanceTest {
public static class Thing extends StandardObject implements Serializable {
@@ -283,4 +285,64 @@
assertBothWays(in, expectedXml);
}
+
+ public static class OriginalExternalizable extends StandardObject implements Externalizable {
+ String originalValue;
+
+ public OriginalExternalizable() {
+ }
+
+ public OriginalExternalizable(String originalValue) {
+ this.originalValue = originalValue;
+ }
+
+ private Object writeReplace() {
+ return new ReplacedExternalizable(originalValue.toUpperCase());
+ }
+
+ public void writeExternal(ObjectOutput out) throws IOException {
+ out.writeObject(originalValue);
+ }
+
+ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+ originalValue = (String)in.readObject();
+ }
+ }
+
+ public static class ReplacedExternalizable extends StandardObject implements Externalizable {
+ String replacedValue;
+
+ public ReplacedExternalizable() {
+ }
+
+ public ReplacedExternalizable(String replacedValue) {
+ this.replacedValue = replacedValue;
+ }
+
+ private Object readResolve() {
+ return new OriginalExternalizable(replacedValue.toLowerCase());
+ }
+
+ public void writeExternal(ObjectOutput out) throws IOException {
+ out.writeObject(StringUtils.reverse(replacedValue));
+ }
+
+ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+ replacedValue = StringUtils.reverse((String)in.readObject());
+ }
+ }
+
+ public void testAllowsDifferentTypeToBeSubstitutedForCustomExternalizableObjects() {
+ xstream.alias("original-externalizable-class", OriginalExternalizable.class);
+ xstream.alias("replaced-externalizable-class", ReplacedExternalizable.class);
+
+ OriginalExternalizable in = new OriginalExternalizable("hello world");
+
+ String expectedXml = ""
+ + "<original-externalizable-class resolves-to=\"replaced-externalizable-class\">\n"
+ + " <string>DLROW OLLEH</string>\n"
+ + "</original-externalizable-class>";
+
+ assertBothWays(in, expectedXml);
+ }
}
Modified: branches/v-1.4.x/xstream-distribution/src/content/changes.html (2280 => 2281)
--- branches/v-1.4.x/xstream-distribution/src/content/changes.html 2014-04-08 20:32:23 UTC (rev 2280)
+++ branches/v-1.4.x/xstream-distribution/src/content/changes.html 2014-04-08 20:43:59 UTC (rev 2281)
@@ -35,6 +35,7 @@
<h2>Minor changes</h2>
<ul>
+ <li>XSTR-755: ExternalizableConverter does not respect writeReplace and readResolve.</li>
<li>Fix: DateConverter ignores provided locale.</li>
<li>Fix: WeakCache.entrySet().iterator().next.setValue(value) returns the reference instead of the old value.</li>
<li>Fix: SqlTimestampConverter throws IllegalArgumentException instead of ConversionException on fromString().</li>
To unsubscribe from this list please visit:
