United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
Bug ID: JDK-6505888 LTP: Java 6 breaks XML encoding/decoding of immutable list member and "id" property
JDK-6505888 : LTP: Java 6 breaks XML encoding/decoding of immutable list member and "id" property

Details
Type:
Bug
Submit Date:
2006-12-19
Status:
Resolved
Updated Date:
2011-01-19
Project Name:
JDK
Resolved Date:
2007-03-28
Component:
client-libs
OS:
solaris_nevada
Sub-Component:
java.beans
CPU:
generic
Priority:
P3
Resolution:
Fixed
Affected Versions:
6
Fixed Versions:
6u2 (b01)

Related Reports
Backport:
Relates:
Relates:
Relates:

Sub Tasks

Description
Classes in the Java DTrace API that encode/decode successfully in Java 5 no longer encode/decode successfully in Java 6 (using XMLEncoder and XMLDecoder).

To demonstrate the behavior on Java 5:

; su root
...
; rm /usr/java
; ln -s /usr/jdk/jdk1.5.0_08 /usr/java
; java -version
java version "1.5.0_08"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_08-b03)
Java HotSpot(TM) Server VM (build 1.5.0_08-b03, mixed mode)

Then compile and run the attached TestBean.java in another window as non-root:

; javac TestBean.java
; java TestBean out
  serialized: TestBean[id = 1, list = [1, 2, 3]]
  deserialized: TestBean[id = 1, list = [1, 2, 3]]
  XML-encoded: TestBean[id = 1, list = [1, 2, 3]]
  XML-decoded: TestBean[id = 1, list = [1, 2, 3]]
;

The above test writes a TestBean with id 1 and list of integers (1,2,3) to a file named "out" then reads it back in using object serialization first then XML encoding. The output above represents a successful test result.

To demonstrate the failure on Java 6:

; rm /usr/java
; ln -s /usr/jdk/jdk1.6.0 /usr/java
; java -version
java version "1.6.0-rc"
Java(TM) SE Runtime Environment (build 1.6.0-rc-b100)
Java HotSpot(TM) Server VM (build 1.6.0-rc-b100, mixed mode)

Then re-compile and re-run the attached TestBean.java in another window as non-root:

; javac TestBean.java
; java TestBean out
  serialized: TestBean[id = 1, list = [1, 2, 3]]
  deserialized: TestBean[id = 1, list = [1, 2, 3]]
  XML-encoded: TestBean[id = 1, list = [1, 2, 3]]
java.lang.NoSuchMethodException: TestBean.getId
Continuing ...
java.lang.InstantiationException: java.util.Collections$UnmodifiableRandomAccessList
Continuing ...
java.lang.Exception: XMLEncoder: discarding statement XMLEncoder.writeObject(TestBean);
Continuing ...
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
        at com.sun.beans.ObjectHandler.dequeueResult(ObjectHandler.java:139)
        at java.beans.XMLDecoder.readObject(XMLDecoder.java:201)
        at TestBean.performBeanTest(TestBean.java:203)
        at TestBean.main(TestBean.java:243)

In Java 6, the "id" property no longer maps to the public getID() method:

            BeanInfo info = Introspector.getBeanInfo(TestBean.class);
            PersistenceDelegate persistenceDelegate =
                    new DefaultPersistenceDelegate(
                    new String[] {"id", "list"});
...
    public int
    getID()
    {
        return id;
    }

This problem is fixed by changing the spelling of the "id" property to "iD":

            PersistenceDelegate persistenceDelegate =
                    new DefaultPersistenceDelegate(
                    new String[] {"iD", "list"});

Still, this is inconsistent with the behavior of Java 5 and breaks existing classes in the Java DTrace library (source in ON gate: http://onnv/):

    /usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/Aggregation.java
    /usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/ProbeDescription.java

Since the TestBean class is immutable, the getList() method returns an immutable view of its internal list member:

    public List <Integer>
    getList()
    {
        return Collections. <Integer> unmodifiableList(list);
    }

In Java 5, the list property returned by this method is successfully encoded and decoded using XMLEncoder and XMLDecoder, but not in Java 6. This breaks another existing class in the Java DTrace API:

    /usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/PrintfRecord.java

                                    

Comments
EVALUATION

1. I can change the spelling from "id" to "ID" (XML encoding of the Java DTrace API on Solaris 10 update 4 will not work with Java 6 until that change is integrated).

2. To avoid breaking immutable classes, XML encoding should work for at least the following:
- Collections.unmodifiableList()
- Collections.unmodifiableSet()
- Collections.unmodifiableMap()
But shouldn't there be a custom persistence delegate for every class returned by any of the java.util.Collections methods?
                                     
2006-12-20
EVALUATION

Unfortunately XMLEncoder does not support non-public classes,
so I should create separate persistence delegate for all classes in Collections.
                                     
2006-12-20
SUGGESTED FIX

Add the following class to the file java/beans/MetaData.java:

class java_util_Collections {

    static final class EmptyList_PersistenceDelegate extends PersistenceDelegate {
        protected Expression instantiate(Object oldInstance, Encoder out) {
            return new Expression(oldInstance, Collections.class, "emptyList", null);
        }
    }

    static final class EmptySet_PersistenceDelegate extends PersistenceDelegate {
        protected Expression instantiate(Object oldInstance, Encoder out) {
            return new Expression(oldInstance, Collections.class, "emptySet", null);
        }
    }

    static final class EmptyMap_PersistenceDelegate extends PersistenceDelegate {
        protected Expression instantiate(Object oldInstance, Encoder out) {
            return new Expression(oldInstance, Collections.class, "emptyMap", null);
        }
    }

    static final class SingletonList_PersistenceDelegate extends PersistenceDelegate {
        protected Expression instantiate(Object oldInstance, Encoder out) {
            List list = (List) oldInstance;
            return new Expression(oldInstance, Collections.class, "singletonList", new Object[]{list.get(0)});
        }
    }

    static final class SingletonSet_PersistenceDelegate extends PersistenceDelegate {
        protected Expression instantiate(Object oldInstance, Encoder out) {
            Set set = (Set) oldInstance;
            return new Expression(oldInstance, Collections.class, "singletonSet", new Object[]{set.iterator().next()});
        }
    }

    static final class SingletonMap_PersistenceDelegate extends PersistenceDelegate {
        protected Expression instantiate(Object oldInstance, Encoder out) {
            Map map = (Map) oldInstance;
            Object key = map.keySet().iterator().next();
            return new Expression(oldInstance, Collections.class, "singletonMap", new Object[]{key, map.get(key)});
        }
    }

    static final class UnmodifiableCollection_PersistenceDelegate extends PersistenceDelegate {
        protected Expression instantiate(Object oldInstance, Encoder out) {
            List list = new ArrayList((Collection) oldInstance);
            return new Expression(oldInstance, Collections.class, "unmodifiableCollection", new Object[]{list});
        }
    }

    static final class UnmodifiableList_PersistenceDelegate extends PersistenceDelegate {
        protected Expression instantiate(Object oldInstance, Encoder out) {
            List list = new LinkedList((Collection) oldInstance);
            return new Expression(oldInstance, Collections.class, "unmodifiableList", new Object[]{list});
        }
    }

    static final class UnmodifiableRandomAccessList_PersistenceDelegate extends PersistenceDelegate {
        protected Expression instantiate(Object oldInstance, Encoder out) {
            List list = new ArrayList((Collection) oldInstance);
            return new Expression(oldInstance, Collections.class, "unmodifiableList", new Object[]{list});
        }
    }

    static final class UnmodifiableSet_PersistenceDelegate extends PersistenceDelegate {
        protected Expression instantiate(Object oldInstance, Encoder out) {
            Set set = new HashSet((Set) oldInstance);
            return new Expression(oldInstance, Collections.class, "unmodifiableSet", new Object[]{set});
        }
    }

    static final class UnmodifiableSortedSet_PersistenceDelegate extends PersistenceDelegate {
        protected Expression instantiate(Object oldInstance, Encoder out) {
            SortedSet set = new TreeSet((SortedSet) oldInstance);
            return new Expression(oldInstance, Collections.class, "unmodifiableSortedSet", new Object[]{set});
        }
    }

    static final class UnmodifiableMap_PersistenceDelegate extends PersistenceDelegate {
        protected Expression instantiate(Object oldInstance, Encoder out) {
            Map map = new HashMap((Map) oldInstance);
            return new Expression(oldInstance, Collections.class, "unmodifiableMap", new Object[]{map});
        }
    }

    static final class UnmodifiableSortedMap_PersistenceDelegate extends PersistenceDelegate {
        protected Expression instantiate(Object oldInstance, Encoder out) {
            SortedMap map = new TreeMap((SortedMap) oldInstance);
            return new Expression(oldInstance, Collections.class, "unmodifiableSortedMap", new Object[]{map});
        }
    }

    static final class SynchronizedCollection_PersistenceDelegate extends PersistenceDelegate {
        protected Expression instantiate(Object oldInstance, Encoder out) {
            List list = new ArrayList((Collection) oldInstance);
            return new Expression(oldInstance, Collections.class, "synchronizedCollection", new Object[]{list});
        }
    }

    static final class SynchronizedList_PersistenceDelegate extends PersistenceDelegate {
        protected Expression instantiate(Object oldInstance, Encoder out) {
            List list = new LinkedList((Collection) oldInstance);
            return new Expression(oldInstance, Collections.class, "synchronizedList", new Object[]{list});
        }
    }

    static final class SynchronizedRandomAccessList_PersistenceDelegate extends PersistenceDelegate {
        protected Expression instantiate(Object oldInstance, Encoder out) {
            List list = new ArrayList((Collection) oldInstance);
            return new Expression(oldInstance, Collections.class, "synchronizedList", new Object[]{list});
        }
    }

    static final class SynchronizedSet_PersistenceDelegate extends PersistenceDelegate {
        protected Expression instantiate(Object oldInstance, Encoder out) {
            Set set = new HashSet((Set) oldInstance);
            return new Expression(oldInstance, Collections.class, "synchronizedSet", new Object[]{set});
        }
    }

    static final class SynchronizedSortedSet_PersistenceDelegate extends PersistenceDelegate {
        protected Expression instantiate(Object oldInstance, Encoder out) {
            SortedSet set = new TreeSet((SortedSet) oldInstance);
            return new Expression(oldInstance, Collections.class, "synchronizedSortedSet", new Object[]{set});
        }
    }

    static final class SynchronizedMap_PersistenceDelegate extends PersistenceDelegate {
        protected Expression instantiate(Object oldInstance, Encoder out) {
            Map map = new HashMap((Map) oldInstance);
            return new Expression(oldInstance, Collections.class, "synchronizedMap", new Object[]{map});
        }
    }

    static final class SynchronizedSortedMap_PersistenceDelegate extends PersistenceDelegate {
        protected Expression instantiate(Object oldInstance, Encoder out) {
            SortedMap map = new TreeMap((SortedMap) oldInstance);
            return new Expression(oldInstance, Collections.class, "synchronizedSortedMap", new Object[]{map});
        }
    }

    static final class CheckedCollection_PersistenceDelegate extends PersistenceDelegate {
        protected Expression instantiate(Object oldInstance, Encoder out) {
            List list = new ArrayList((Collection) oldInstance);
            return new Expression(oldInstance, Collections.class, "checkedCollection", new Object[]{list});
        }
    }

    static final class CheckedList_PersistenceDelegate extends PersistenceDelegate {
        protected Expression instantiate(Object oldInstance, Encoder out) {
            List list = new LinkedList((Collection) oldInstance);
            return new Expression(oldInstance, Collections.class, "checkedList", new Object[]{list});
        }
    }

    static final class CheckedRandomAccessList_PersistenceDelegate extends PersistenceDelegate {
        protected Expression instantiate(Object oldInstance, Encoder out) {
            List list = new ArrayList((Collection) oldInstance);
            return new Expression(oldInstance, Collections.class, "checkedList", new Object[]{list});
        }
    }

    static final class CheckedSet_PersistenceDelegate extends PersistenceDelegate {
        protected Expression instantiate(Object oldInstance, Encoder out) {
            Set set = new HashSet((Set) oldInstance);
            return new Expression(oldInstance, Collections.class, "checkedSet", new Object[]{set});
        }
    }

    static final class CheckedSortedSet_PersistenceDelegate extends PersistenceDelegate {
        protected Expression instantiate(Object oldInstance, Encoder out) {
            SortedSet set = new TreeSet((SortedSet) oldInstance);
            return new Expression(oldInstance, Collections.class, "checkedSortedSet", new Object[]{set});
        }
    }

    static final class CheckedMap_PersistenceDelegate extends PersistenceDelegate {
        protected Expression instantiate(Object oldInstance, Encoder out) {
            Map map = new HashMap((Map) oldInstance);
            return new Expression(oldInstance, Collections.class, "checkedMap", new Object[]{map});
        }
    }

    static final class CheckedSortedMap_PersistenceDelegate extends PersistenceDelegate {
        protected Expression instantiate(Object oldInstance, Encoder out) {
            SortedMap map = new TreeMap((SortedMap) oldInstance);
            return new Expression(oldInstance, Collections.class, "checkedSortedMap", new Object[]{map});
        }
    }
}
                                     
2007-01-24
EVALUATION

This is regression after the fix of the bug 4741757.
Old version of DefaultPersistenceDelegate uses private fields instead properties.
We should not rollback the fix by security reason, but we should solve 2 problems here:

 1. Illegal property name
"public int getID()" means that property name is "ID" (not "id").
So user should follow the JavaBeans specification (section 8.8).
We can try to create a hack but it is a bad idea.

 2. Persistence for private classes like Collections.UnmodifiableList
We can create custom persistence delegates for classes in Collections,
but we can't create common solution for all such classes.
The field access (that was removed by the fix of the bug 4741757) can't fix this problem,
because it is possible to create user's bean with unmodifiable list.
                                     
2006-12-19



Hardware and Software, Engineered to Work Together