Title: [2279] trunk: ExternalizableConverter does not respect writeReplace and readResolve (XSTR-755).
Revision
2279
Author
joehni
Date
2014-04-08 15:26:49 -0500 (Tue, 08 Apr 2014)

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:

http://xircles.codehaus.org/manage_email

Reply via email to