JDK-6614943 : XMLEncoder never encodes property that is an immutable bean
  • Type: Bug
  • Component: client-libs
  • Sub-Component: java.beans
  • Affected Version: 6
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: linux
  • CPU: x86
  • Submitted: 2007-10-10
  • Updated: 2021-07-13
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdUnresolved
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.6.0"
Java(TM) SE Runtime Environment (build 1.6.0-b105)
Java HotSpot(TM) Client VM (build 1.6.0-b105, mixed mode, sharing)


ADDITIONAL OS VERSION INFORMATION :
Linux itis-asus-desktop 2.6.20-16-generic #2 SMP Thu Jun 7 20:19:32 UTC 2007 i686 GNU/Linux


A DESCRIPTION OF THE PROBLEM :
Incorrect XML is produced for a particulary set of beans:
ParentBean having one property "bean", an ImmutableBean
ImmutableBean having one property "a", an int. This property is immutable - it is set in the constructor, and has a getter only. A DefaultPersistenceDelegate is registered, and this bean will encode correctly when it is encoded directly rather than as a property of ParentBean.

When ParentBean has a default constructor giving "bean" property a default value, any instance of ParentBean will be encoded with default "bean" property in the XML. That is, the XML will just create a default ParentBean, regardless of what was encoded.

If ParentBean gives "bean" property the value null in default constructor, it will be encoded correctly

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the ParentTest class, check output to console and user.dir

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Should serialise with the ImmutableBean encoded, producing the following output:

ImmutableBean a value, should be 254: 254
Default parent.getBean().getA() value: 1
parent.getBean().getA() value after calling parent.setBean(): 47
parent.getBean().getA() value after XML encoding and decoding: 47

And the following XML in user directory:

ParentBean.xml:

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.6.0" class="java.beans.XMLDecoder">
 <object class="com.itis.proto.ParentBean">
  <void property="bean">
   <object class="com.itis.proto.ImmutableBean">
    <int>47</int>
   </object>
  </void>
 </object>
</java>


ImmutableBean.xml:

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.6.0" class="java.beans.XMLDecoder">
 <object class="com.itis.proto.ImmutableBean">
  <int>254</int>
 </object>
</java>

ACTUAL -
Actually serialises with ImmutableBean missing (assumed to be default), producing the following output:

ImmutableBean a value, should be 254: 254
Default parent.getBean().getA() value: 1
parent.getBean().getA() value after calling parent.setBean(): 47
parent.getBean().getA() value after XML encoding and decoding: 1

And the following XML in user directory:

ParentBean.xml:

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.6.0" class="java.beans.XMLDecoder">
 <object class="com.itis.proto.ParentBean"/>
</java>

ImmutableBean a value, should be 254: 254
Default parent.getBean().getA() value: 1
parent.getBean().getA() value after calling parent.setBean(): 47
parent.getBean().getA() value after XML encoding and decoding: 47

And the following XML in user directory:

ParentBean.xml:

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.6.0" class="java.beans.XMLDecoder">
 <object class="com.itis.proto.ParentBean">
  <void property="bean">
   <object class="com.itis.proto.ImmutableBean">
    <int>47</int>
   </object>
  </void>
 </object>
</java>

ImmutableBean.xml:


<?xml version="1.0" encoding="UTF-8"?>
<java version="1.6.0" class="java.beans.XMLDecoder">
 <object class="com.itis.proto.ImmutableBean">
  <int>254</int>
 </object>
</java>


ERROR MESSAGES/STACK TRACES THAT OCCUR :
No exceptions - listeners were registered on the encoder and decoder to print stack traces of any exceptions.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
//(please note there are 3 classes, for the two beans and the test).


import java.beans.BeanInfo;
import java.beans.DefaultPersistenceDelegate;
import java.beans.IntrospectionException;
import java.beans.Introspector;
  
public class ImmutableBean {
  
    //Set up XML persistence delegate
    static {
        try {
            BeanInfo info = Introspector.getBeanInfo(ImmutableBean.class);
            info.getBeanDescriptor().setValue("persistenceDelegate",
                    new DefaultPersistenceDelegate(
                            new String[]{
                                    "a"
                                    })
                    );
            } catch (IntrospectionException exception) {
            throw new RuntimeException("Unable to persist ImmutableBean", exception);
        }
    }
      
    int a;
      
    public ImmutableBean(int a) {
        super();
        this.a = a;
    }
    public int getA() {
        return a;
    }
}  
  
  
  
  
public class ParentBean {
  
    ImmutableBean bean;
      
    public ParentBean() {
        super();
        //Change this line to the commented version to make
        //the encoding work. If default value of "bean" property
        //in this bean is null, then XMLEncoder will bother to encode
        //the actual property, otherwise it will always consider any
        //value of the "bean" property to be the same as the default,
        //and not encode it
        this.bean = new ImmutableBean(1);
        //this.bean = null;
    }
      
    public ImmutableBean getBean() {
        return bean;
    }
    public void setBean(ImmutableBean bean) {
        this.bean = bean;
    }
}  
  
  
  
  
import java.beans.ExceptionListener;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
  
public class ParentBeanTest {
  
    public final static void main(String[] args) {
  
        try {
              
            //First encode an ImmutableBean, just to show it works on its own
            encodeAndDecodeImmutableBean();
  
            //Now encode/decode the parent bean
            encodeAndDecodeParentBean();
              
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  
    public static void encodeAndDecodeParentBean() throws FileNotFoundException {
        ParentBean parent = new ParentBean();
          
        if (parent.getBean() != null) System.out.println("Default parent.getBean().getA() value: " + parent.getBean().getA());
          
        ImmutableBean newBigBean = new ImmutableBean(47);
          
        parent.setBean(newBigBean);
  
        System.out.println("parent.getBean().getA() value after calling parent.setBean(): " + parent.getBean().getA());
          
        File xmlFile = new File(System.getProperty("user.home"), "ParentBean.xml");
        ParentBean clone = (ParentBean)encodeAndDecode(parent, xmlFile);
          
        System.out.println("parent.getBean().getA() value after XML encoding and decoding: " + clone.getBean().getA());
    }
      
    public static void encodeAndDecodeImmutableBean() throws FileNotFoundException {
        ImmutableBean immutable = new ImmutableBean(254);
        File xmlFile = new File(System.getProperty("user.home"), "ImmutableBean.xml");
        ImmutableBean clone = (ImmutableBean)encodeAndDecode(immutable, xmlFile);
        System.out.println("ImmutableBean a value, should be 254: " + clone.getA());
    }
      
    public static Object encodeAndDecode(Object bean, File file) throws FileNotFoundException {
        XMLEncoder encoder = new XMLEncoder(new FileOutputStream(file));
          
        //Make sure we see any encoder exceptions
        encoder.setExceptionListener(new ExceptionListener() {
            public void exceptionThrown(Exception e) {
                e.printStackTrace();
            }
        });
        encoder.writeObject(bean);
        encoder.close();
          
        //Make sure we see any decoder exceptions
        XMLDecoder decoder = new XMLDecoder(new FileInputStream(file), null,
                new ExceptionListener() {
                    public void exceptionThrown(Exception e) {
                        e.printStackTrace();
                    }
                });
          
        return decoder.readObject();
    }
}  
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Setting the default value of the "bean" property in ParentBean default constructor to null causes XMLEncoder to notice the "bean" property has changed. Presumably a custom persistence delegate for ImmutableBean and/or ParentBean could also work.

Comments
WORK AROUND The persistence delegate for the ImmutableBean class should override the "mutatesTo" method in the DefaultPersistenceDelegate class if it is impossible to override the "equals" method in the ImmutableBean class: class ImmutableBeanPersistenceDelegate extends DefaultPersistenceDelegate { ImmutableBeanPersistenceDelegate() { super(new String[] {"a"}); } @Override protected boolean mutatesTo(Object oldInstance, Object newInstance) { if (super.mutatesTo(oldInstance, newInstance)) { ImmutableBean oldBean = (ImmutableBean) oldInstance; ImmutableBean newBean = (ImmutableBean) newInstance; return oldBean.getA() == newBean.getA(); } return false; } }
19-11-2007

EVALUATION It is known issue in the current implementation. I think it is dangerous to change such behavior, but we should investigate it closely.
19-11-2007

WORK AROUND The ImmutableBean class should override the "equals" method: public boolean equals(Object object) { if (object instanceof ImmutableBean) { ImmutableBean bean = (ImmutableBean) object; return bean.a == this.a; } return false; } because the DefaultPersistenceDelegate class assumes that the bean is immutable only if it have some constructor property names and the bean class declares the "equals" method.
19-11-2007