Log Message
ExternalizableConverter does not respect writeReplace and readResolve (XSTR-755).
Modified Paths
Diff
Modified: trunk/xstream/src/java/com/thoughtworks/xstream/converters/reflection/ExternalizableConverter.java (2278 => 2279)
--- trunk/xstream/src/java/com/thoughtworks/xstream/converters/reflection/ExternalizableConverter.java 2014-04-05 19:43:05 UTC (rev 2278)
+++ trunk/xstream/src/java/com/thoughtworks/xstream/converters/reflection/ExternalizableConverter.java 2014-04-08 20:26:49 UTC (rev 2279)
@@ -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;
@@ -25,6 +25,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;
@@ -37,17 +38,18 @@
/**
* Converts any object that implements the {@link Externalizable} interface, allowing compatibility with native Java
* serialization.
- *
+ *
* @author Joe Walnes
*/
public class ExternalizableConverter implements Converter {
private final 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
@@ -55,6 +57,7 @@
public ExternalizableConverter(final Mapper mapper, final ClassLoaderReference classLoaderReference) {
this.mapper = mapper;
this.classLoaderReference = classLoaderReference;
+ serializationMethodInvoker = new SerializationMethodInvoker();
}
/**
@@ -79,51 +82,64 @@
}
@Override
- public void marshal(final Object source, final HierarchicalStreamWriter writer, final MarshallingContext context) {
- try {
- final Externalizable externalizable = (Externalizable)source;
- final CustomObjectOutputStream.StreamCallback callback = new CustomObjectOutputStream.StreamCallback() {
- @Override
- 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 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 {
+ final Externalizable externalizable = (Externalizable)source;
+ final CustomObjectOutputStream.StreamCallback callback = new CustomObjectOutputStream.StreamCallback() {
+ @Override
+ 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();
+ }
}
- }
- @Override
- public void writeFieldsToStream(final Map<String, Object> fields) {
- throw new UnsupportedOperationException();
- }
+ @Override
+ public void writeFieldsToStream(final Map<String, Object> fields) {
+ throw new UnsupportedOperationException();
+ }
- @Override
- public void defaultWriteObject() {
- throw new UnsupportedOperationException();
- }
+ @Override
+ public void defaultWriteObject() {
+ throw new UnsupportedOperationException();
+ }
- @Override
- public void flush() {
- writer.flush();
- }
+ @Override
+ public void flush() {
+ writer.flush();
+ }
- @Override
- public void close() {
- throw new UnsupportedOperationException(
- "Objects are not allowed to call ObjectOutput.close() from writeExternal()");
- }
- };
- @SuppressWarnings("resource")
- final CustomObjectOutputStream objectOutput = CustomObjectOutputStream.getInstance(context, callback);
- externalizable.writeExternal(objectOutput);
- objectOutput.popCallback();
- } catch (final IOException e) {
- throw new ConversionException("Cannot serialize " + source.getClass().getName() + " using Externalization",
- e);
+ @Override
+ public void close() {
+ throw new UnsupportedOperationException(
+ "Objects are not allowed to call ObjectOutput.close() from writeExternal()");
+ }
+ };
+ @SuppressWarnings("resource")
+ final CustomObjectOutputStream objectOutput = CustomObjectOutputStream.getInstance(context, callback);
+ externalizable.writeExternal(objectOutput);
+ objectOutput.popCallback();
+ } catch (final IOException e) {
+ throw new ConversionException("Cannot serialize "
+ + source.getClass().getName()
+ + " using Externalization", e);
+ }
}
}
@@ -166,7 +182,7 @@
@Override
public void close() {
throw new UnsupportedOperationException(
- "Objects are not allowed to call ObjectInput.close() from readExternal()");
+ "Objects are not allowed to call ObjectInput.close() from readExternal()");
}
};
{
@@ -176,7 +192,7 @@
externalizable.readExternal(objectInput);
objectInput.popCallback();
}
- return externalizable;
+ return serializationMethodInvoker.callReadResolve(externalizable);
} catch (final NoSuchMethodException e) {
throw new ConversionException("Cannot construct " + type.getClass() + ", missing default constructor", e);
} catch (final InvocationTargetException e) {
@@ -191,4 +207,9 @@
throw new ConversionException("Cannot externalize " + type.getClass(), e);
}
}
+
+ private Object readResolve() {
+ serializationMethodInvoker = new SerializationMethodInvoker();
+ return this;
+ }
}
Modified: trunk/xstream/src/test/com/thoughtworks/acceptance/WriteReplaceTest.java (2278 => 2279)
--- trunk/xstream/src/test/com/thoughtworks/acceptance/WriteReplaceTest.java 2014-04-05 19:43:05 UTC (rev 2278)
+++ trunk/xstream/src/test/com/thoughtworks/acceptance/WriteReplaceTest.java 2014-04-08 20:26:49 UTC (rev 2279)
@@ -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: trunk/xstream-distribution/src/content/changes.html (2278 => 2279)
--- trunk/xstream-distribution/src/content/changes.html 2014-04-05 19:43:05 UTC (rev 2278)
+++ trunk/xstream-distribution/src/content/changes.html 2014-04-08 20:26:49 UTC (rev 2279)
@@ -60,6 +60,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:
