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.