JDK-6187118 : LTP: XMLEncoder creates valid but wrong archives for immutable objects
  • Type: Bug
  • Component: client-libs
  • Sub-Component: java.beans
  • Affected Version: 5.0
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: windows_2000
  • CPU: x86
  • Submitted: 2004-10-29
  • Updated: 2011-03-12
  • Resolved: 2007-07-19
Related Reports
Duplicate :  
Description
FULL PRODUCT VERSION :
java version "1.5.0-rc"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0-rc-b63)
Java HotSpot(TM) Client VM (build 1.5.0-rc-b63, mixed mode, sharing)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows 2000 [Version 5.00.2195]

A DESCRIPTION OF THE PROBLEM :
Persistence for objects that can only be created by consecutively creating "intermediate" objects and apply operations on them. The XML mechanism can have 1 of such "recursive" steps in order to create the final object, but fails to have 2 or more intermediate recursive steps, which is shown in this example. The XML archives created are valid, but do not represent just the stored object, but instead several objects, because falseley also the intermediate objects are manifested.

Additionally I provide a manually modified XML file which is valid and yields the desired object after creating several intermediate ones.


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1.
On Windows put the XML given in "Expected Result" to "c:\\PersistencyTest.xml".
If the file is stored somewhere else, e.g. on a non-Windows system, please correct the file location in method readFromFile() from "c:\\PersistencyTest.xml" to the appropriate location.

2. Compile and execute the Java file.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
I expect the created archive to be the same or equivalent to this one:

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.5.0-rc" class="java.beans.XMLDecoder">
 <void class="XMLPersistenceBugExample$ImmutableList">
  <void id="PersistencyTest$ImmutableList1" method="add">
   <string>test object 1</string>
   <void id="PersistencyTest$ImmutableList2" method="add">
    <string>test object 2</string>
    <void id="PersistencyTest$ImmutableList0" method="add">
     <string>test object 3</string>
    </void>
   </void>
  </void>
 </void>
 <object idref="PersistencyTest$ImmutableList0"/>
</java>

Furthermore I expect that in the output from the java test after archiving and reading-in the list 2 has 2 entries: [test object 1, test object 2] and the list 3 has 3 entries: [test object 1, test object 2, test object 3].
ACTUAL -
Emtpy list: []

XML file from persistence delegate:
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.5.0-rc" class="java.beans.XMLDecoder">
 <object class="de.carstenlanger.test.util.dataobjects.XMLPersistenceBugExample$ImmutableList"/>
</java>

Emtpy list is still fine after XML en-/decoding: []


List with 1 entry: [test object 1]

XML file from persistence delegate:
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.5.0-rc" class="java.beans.XMLDecoder">
 <void class="de.carstenlanger.test.util.dataobjects.XMLPersistenceBugExample$ImmutableList">
  <void id="XMLPersistenceBugExample$ImmutableList0" method="add">
   <string>test object 1</string>
  </void>
 </void>
 <object idref="XMLPersistenceBugExample$ImmutableList0"/>
</java>

List with 1 entry is still fine after XML en-/decoding: [test object 1]


List with 2 entries: [test object 1, test object 2]

XML file from persistence delegate:
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.5.0-rc" class="java.beans.XMLDecoder">
 <void class="de.carstenlanger.test.util.dataobjects.XMLPersistenceBugExample$ImmutableList">
  <void id="XMLPersistenceBugExample$ImmutableList1" method="add">
   <string>test object 1</string>
   <void id="XMLPersistenceBugExample$ImmutableList0" method="add">
    <string>test object 2</string>
   </void>
  </void>
 </void>
 <object idref="XMLPersistenceBugExample$ImmutableList1"/>
 <object idref="XMLPersistenceBugExample$ImmutableList0"/>
</java>

List with 2 entries is wrongly encoded and thus can't be decoded correctly: [test object 1]


List with 3 entries: [test object 1, test object 2, test object 3]

XML file from persistence delegate:
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.5.0-rc" class="java.beans.XMLDecoder">
 <void class="de.carstenlanger.test.util.dataobjects.XMLPersistenceBugExample$ImmutableList">
  <void id="XMLPersistenceBugExample$ImmutableList1" method="add">
   <string>test object 1</string>
   <void id="XMLPersistenceBugExample$ImmutableList2" method="add">
    <string>test object 2</string>
    <void id="XMLPersistenceBugExample$ImmutableList0" method="add">
     <string>test object 3</string>
    </void>
   </void>
  </void>
 </void>
 <object idref="XMLPersistenceBugExample$ImmutableList1"/>
 <object idref="XMLPersistenceBugExample$ImmutableList2"/>
 <object idref="XMLPersistenceBugExample$ImmutableList0"/>
</java>

List with 3 entries is wrongly encoded and thus can't be decoded correctly: [test object 1]


Instead, if the list with 3 entries had been encoded as in the given file, it would have been decoded correctly: [test object 1, test object 2, test object 3]


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.beans.Encoder;
import java.beans.Expression;
import java.beans.PersistenceDelegate;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;


/**
 * Test class to demonstrate a bug in the XML persistence mechanism. Persistence
 * for objects that can only be created by consecutively creating "intermediate"
 * objects and apply operations on them. The XML mechanism can have 1 of such
 * "recursive" steps in order to create the final object, but fails to have 2 or
 * more intermediate recursive steps, which is shown in this example. The XML
 * archives created are valid, but do not represent just the stored object, but
 * instead several objects, because falseley also the intermediate objects are
 * manifested. Additionally I provide a manually modified XML file which is
 * valid and yields the desired object after creating several intermediate ones.
 */
public class XMLPersistenceBugExample {

    //~ Methods ----------------------------------------------------------------

    public static void main(String[] args) throws FileNotFoundException {
        final ImmutableList list0 = new ImmutableList();
        System.out.println("Emtpy list: " + list0 + "\n");
        System.out.println("Emtpy list is still fine after XML en-/decoding: "
                           + writeRead(list0) + "\n\n");

        final Object object1 = "test object 1";
        final ImmutableList list1 = list0.add(object1);
        System.out.println("List with 1 entry: " + list1 + "\n");
        System.out.println("List with 1 entry is still fine after XML en-/decoding: "
                           + writeRead(list1) + "\n\n");

        final Object object2 = "test object 2";
        final ImmutableList list2 = list1.add(object2);
        System.out.println("List with 2 entries: " + list2 + "\n");
        System.out.println("List with 2 entries is wrongly encoded and thus can't be decoded correctly: "
                           + writeRead(list2) + "\n\n");

        final Object object3 = "test object 3";
        final ImmutableList list3 = list2.add(object3);
        System.out.println("List with 3 entries: " + list3 + "\n");
        System.out.println("List with 3 entries is wrongly encoded and thus can't be decoded correctly: "
                           + writeRead(list3) + "\n\n");

        System.out.println("Instead, if the list with 3 entries had been encoded as in the given file, it would have been decoded correctly: "
                           + readFromFile());
    }

    /**
     * A {@link PersistenceDelegate} for the class {@link ImmutableList}.
     * Persistence is achieved by using the constructor {@link
     * ImmutableList#ImmutableList()} to create a new emtpy list and then {@link
     * ImmutableList#add(Object)}ing the entries of the given immutable list.
     */
    private static void addPersistenceDelegate(final Encoder encoder) {
        encoder.setPersistenceDelegate(ImmutableList.class,
                                       new PersistenceDelegate() {
                protected Expression instantiate(final Object oldInstance,
                                                 final Encoder out) {
                    final ImmutableList list = (ImmutableList) oldInstance;
                    if (list.hasEntries()) {
                        final Object object = list.retrieveLast();
                        final ImmutableList shortenedList = list.removeLast();
                        return new Expression(oldInstance,
                                              shortenedList,
                                              "add",
                                              new Object[] {object});
                    } else {
                        return new Expression(oldInstance,
                                              ImmutableList.class,
                                              "new",
                                              new Object[] {});
                    }
                }

                protected boolean mutatesTo(Object oldInstance,
                                            Object newInstance) {
                    return oldInstance.equals(newInstance);
                }
            });
    }

    /** Read in from manually created XML persistence file. */
    private static Object readFromFile() throws FileNotFoundException {
        final XMLDecoder decoder =
            new XMLDecoder(new FileInputStream("c:\\PersistencyTest.xml"));
        return decoder.readObject();
    }

    /** Write out to and read in from XML persistency. */
    private static Object writeRead(final Object object) {
        /* Write out. */
        final ByteArrayOutputStream byteArrayOutputStream =
            new ByteArrayOutputStream();
        final XMLEncoder encoder = new XMLEncoder(byteArrayOutputStream);
        addPersistenceDelegate(encoder);
        encoder.writeObject(object);
        encoder.close();

        /* Print out for debugging. */
        System.out.println("XML file from persistence delegate:");
        System.out.println(byteArrayOutputStream);

        /* Read in. */
        final XMLDecoder decoder =
            new XMLDecoder(new ByteArrayInputStream(byteArrayOutputStream
                                                    .toByteArray()));
        return decoder.readObject();
    }

    //~ Inner Classes ----------------------------------------------------------

    /**
     * An example of a given immutable data object, much simplified for this bug
     * report. The important thing is that you need to add/remove data from the
     * data object, you effectively need to create a new data object.
     */
    public static class ImmutableList implements Iterable {

        //~ Instance fields ----------------------------------------------------

        private final List list = new ArrayList();

        //~ Constructors -------------------------------------------------------

        public ImmutableList() {
        }

        public ImmutableList(Iterable iterable) {
            for (Object o : iterable) {
                list.add(o);
            }
        }

        //~ Methods ------------------------------------------------------------

        public ImmutableList add(Object object) {
            final ImmutableList newList = new ImmutableList(list);
            newList.list.add(object);
            return newList;
        }

        public boolean equals(Object object) {
            if (!(object instanceof ImmutableList)) {
                return false;
            }
            return list.equals(((ImmutableList) object).list);
        }

        public boolean hasEntries() {
            return list.size() > 0;
        }

        public int hashCode() {
            return 0; // just to fulfill the contract ...
        }

        public Iterator iterator() {
            return Collections.unmodifiableList(list)
                              .iterator();
        }

        public ImmutableList removeLast() {
            final ImmutableList newList = new ImmutableList(list);
            if (newList.list.size() > 0) {
                newList.list.remove(newList.list.size() - 1);
            }
            return newList;
        }

        public Object retrieveLast() {
            return hasEntries() ? list.get(list.size() - 1)
                                : null;
        }

        public String toString() {
            return list.toString();
        }
    }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Create a PersitenceDelegate that does:
- get all entries from the immutable object, store it is some other form, e.g. an ArrayList
- have a static factory method that can transform the chosen form e.g. the List back into the immutable object by consecutively adding one entry after the other thereby creating intermediate objects.
- store the instance of the immutable object to the archive using a method call to the static factory method with the e.g. ArrayList as a parameters.
Source Code available on request.
###@###.### 10/29/04 22:45 GMT

Comments
EVALUATION Looks like it is the same problem with references that was solved in 5023559.
19-07-2007