United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
JDK-8027066 : XMLDecoder in java 7 cannot properly deserialize object arrays

Details
Type:
Bug
Submit Date:
2013-10-22
Status:
Resolved
Updated Date:
2014-04-16
Project Name:
JDK
Resolved Date:
2013-10-25
Component:
client-libs
OS:
generic
Sub-Component:
java.beans
CPU:
generic
Priority:
P4
Resolution:
Fixed
Affected Versions:
8
Fixed Versions:

Related Reports
Backport:
Backport:
Duplicate:

Sub Tasks

Description
XMLDecoder in Java 7 cannot properly deserialize object arrays.
In the following test case, the faulty object has an object array referenced
by two of its fields (in the example: messages and detailedMessages). As soon
as a reference is
declared over an array, the problem arises.

Note also that the serialized XML seems valid, and only the deserialization
behaves incorrectly.

Test case:

import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Arrays;

public class XMLEncoderTests {
  public static class Wrapper {
     private String[] messages;
     private String[] detailedMessages;

     public String[] getDetailedMessages() {
        return detailedMessages;
     }

     public void setDetailedMessages(String[] messages) {
        this.detailedMessages = messages;
     }

     public String[] getMessages() {
        return messages;
     }

     public void setMessages(String[] messages) {
        this.messages = messages;
     }

     @Override
     public String toString() {
        return "Wrapper - Messages {" + Arrays.deepToString(getMessages())
+ "}, Detailed Messages {"
              + Arrays.deepToString(getDetailedMessages()) + "}";
     }

     @Override
     public boolean equals(Object other) {
        if (other instanceof Wrapper) {
           Wrapper otherWrapper = (Wrapper) other;
           // Compare messages and otherWrapper messages.
           String[] otherMessages = otherWrapper.getMessages();
           String[] messages = this.getMessages();

           if (!Arrays.equals(messages, otherMessages)) {
              return false;
           }

           // And compare detailedMessage and otherWrapperdetailedMessages.
           otherMessages = otherWrapper.getDetailedMessages();
           messages = this.getDetailedMessages();

           if (!Arrays.equals(messages, otherMessages)) {
              return false;
           }

           return true;
        } else {
           return false;
        }
     }
  }

  public static void main(String[] args) {
     ByteArrayOutputStream stream = new ByteArrayOutputStream();
     XMLEncoder encoder = new XMLEncoder(stream);
     Wrapper wrapper = new Wrapper();
     String[] messages = new String[] { "First message", "Second message"
};

     wrapper.setMessages(messages);
     wrapper.setDetailedMessages(messages);

     encoder.writeObject(wrapper);
     encoder.close();

     String output = stream.toString();
     System.out.println(output);

     // now decode it.
     XMLDecoder decoder = new XMLDecoder(new ByteArrayInputStream
(output.getBytes()));
     Object obj = decoder.readObject();
     decoder.close();

     // Validate object are equals.
     if (!wrapper.equals(obj)) {
        System.out.println("FAILURE: Objects are not equals!");
     } else {
        System.out.println("Objects are equals.");
     }
     System.out.println("Original wrapper: " + wrapper);
     System.out.println("Deserialized wrapper: " + obj);
  }
}

When running the above code with JDK 6, the objects are equals (i.e.
The original object and the serialized/deserialized version are the same)
and there is no bug.  When running the above code with JDK 7, the objects are
not equals since the deserialized detailedMessages field is null.

Output running the above test case with JDK 7:
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.7.0_45" class="java.beans.XMLDecoder">
 <object class="XMLEncoderTests$Wrapper">
  <void property="detailedMessages">
   <array class="java.lang.String" length="2" id="StringArray0">
    <void index="0">
     <string>First message</string>
    </void>
    <void index="1">
     <string>Second message</string>
    </void>
   </array>
  </void>
  <void property="messages">
   <object idref="StringArray0"/>
  </void>
 </object>
</java>

FAILURE: Objects are not equals!
Original wrapper: Wrapper - Messages {[First message, Second message]},
Detailed Messages {[First message, Second message]}
Deserialized wrapper: Wrapper - Messages {[First message, Second message]},
Detailed Messages {null}

                                    

Comments
URL:   http://hg.openjdk.java.net/jdk8/jdk8/jdk/rev/6fe5443c3dde
User:  lana
Date:  2013-11-05 21:09:17 +0000

                                     
2013-11-05
URL:   http://hg.openjdk.java.net/jdk8/awt/jdk/rev/6fe5443c3dde
User:  alitvinov
Date:  2013-10-25 09:53:27 +0000

                                     
2013-10-25
The same situation might be reproduced with Objects instead of arrays, when i.e. the same object is referenced from different fields. The serialized form looks pretty the same (below), but is deserialized correctly. 

<java version="1.8.0-ea" class="java.beans.XMLDecoder">
 <object class="XMLEncoderTests$ObjectWrapper">
  <void property="o1">
   <object class="XMLEncoderTests$SomeClass" id="XMLEncoderTests$SomeClass0">
    <void property="i">
     <int>123</int>
    </void>
   </object>
  </void>
  <void property="o2">
   <object idref="XMLEncoderTests$SomeClass0"/>
  </void>
 </object>
</java>


The reason is that the ObjectElementHandler overrides the isArgument() method: 
    protected boolean isArgument() {
        return true;
    }

while the ArrayElementHandler doesn't. 

The solution is to override isArgument() in the ArrayElementHandler analogously to ObjectElementHandler. 
                                     
2013-10-23



Hardware and Software, Engineered to Work Together