JDK-6989223 : DefaultPersistenceDelegate not called when property initialised to non null
  • Type: Bug
  • Component: client-libs
  • Sub-Component: java.beans
  • Affected Version: 6u21
  • Priority: P4
  • Status: Resolved
  • Resolution: Duplicate
  • OS: windows_vista
  • CPU: x86
  • Submitted: 2010-10-04
  • Updated: 2013-05-28
  • Resolved: 2013-05-28
Related Reports
Duplicate :  
Relates :  
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.6.0_20"
Java(TM) SE Runtime Environment (build 1.6.0_20-b02-279-10M3065)
Java HotSpot(TM) 64-Bit Server VM (build 16.3-b01-279, mixed mode)

And other versions too


ADDITIONAL OS VERSION INFORMATION :
All operating systems

A DESCRIPTION OF THE PROBLEM :
I think there is a bug in the DefaultPersistenceDelegate class in the method:

   private static boolean definesEquals(Class type) {
   try {
       return type == type.getMethod("equals", Object.class).getDeclaringClass();
   }
   catch(NoSuchMethodException e) {
       return false;
   }
}

When used on a class like Rectangle2D.Double it returns false (because the equals() method is defined in the enclosing class).

This means that DefaultPersistenceDelegates may NOT get called if a bean property has been initialised to anything other than null and where the property equals method is not declared directly in the class.

Without documentation I cannot determine the intent of the existing code for definesEquals() , but if it is to return true if a class has
a suitable equals() method then this would seem to work:
	
private static boolean definesEquals(Class type) {
	try {
		// return true if type defines a method with signature: boolean equals(Object o)
		return boolean.class == type.getMethod("equals", Object.class).getReturnType();
	} catch (NoSuchMethodException e) {
		return false;
	}
}


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the test application twice setting THIS_WORKS to true ad then false.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
THIS_WORKS=true
bean as XML is :
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.6.0_20" class="java.beans.XMLDecoder">
 <object class="TestBean">
  <void property="bounds">
   <object class="java.awt.geom.Rectangle2D$Double">
    <double>1.0</double>
    <double>2.0</double>
    <double>3.0</double>
    <double>5.0</double>
   </object>
  </void>
 </object>
</java>

bean.getBounds() as XML is :
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.6.0_20" class="java.beans.XMLDecoder">
 <object class="java.awt.geom.Rectangle2D$Double">
  <double>1.0</double>
  <double>2.0</double>
  <double>3.0</double>
  <double>5.0</double>
 </object>
</java>

ACTUAL -
THIS_WORKS=false
bean as XML is :
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.6.0_20" class="java.beans.XMLDecoder">
 <object class="TestBean"/>
</java>

bean.getBounds() as XML is :
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.6.0_20" class="java.beans.XMLDecoder">
 <object class="java.awt.geom.Rectangle2D$Double">
  <double>1.0</double>
  <double>2.0</double>
  <double>3.0</double>
  <double>5.0</double>
 </object>
</java>


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.awt.geom.Rectangle2D;
import java.beans.DefaultPersistenceDelegate;
import java.beans.XMLEncoder;
import java.io.ByteArrayOutputStream;

/**
 *
 * Bug with XMLEncoder and DefaultPersistenceDelegate
 *
 * DefaultPersistenceDelegate is not called when a bean property has been
 * initialised to non null?
 *
 * This TestBean has a bounds property of type Rectangle2D.Double. The
 * XMLEncoder requires a persistenceDelegate in order to encode
 * Rectangle2D.Double.
 * <p>
 * However, if TestBean is initialised with its bounds property set to anything
 * other than null, the persistenceDelegate is not invoked for the bounds
 * property when writeObject is used on the TestBean. The persistenceDelegate
 * still gets called if you call writeObject on the bounds property rather than
 * the TestBean.
 * <p>
 *
 * Try running this program changing THIS_WORKS between true and false.
 *
 *
 * @author Michael Ellis.
 */
public class TestBean {

	private static boolean THIS_WORKS = false;
	Rectangle2D.Double bounds;

	/**
	 *
	 */
	public TestBean() {
		if (THIS_WORKS) {
			bounds = null; // THIS WORKS
		} else {
			bounds = new Rectangle2D.Double(1, 2, 3, 4); // THIS DOESN'T WORK
		}
	}

	public Rectangle2D.Double getBounds() {
		return bounds;
	}

	public void setBounds(Rectangle2D.Double bounds) {
		this.bounds = bounds;
	}

	public static void main(String[] args) {
		TestBean bean = new TestBean();
		bean.setBounds(new Rectangle2D.Double(1, 2, 3, 5));

		System.out.printf("THIS_WORKS=%b\n", THIS_WORKS);
		System.out.printf("bean as XML is :\n%s\n", objectToXML(bean));
		System.out.printf("bean.getBounds() as XML is :\n%s\n", objectToXML(bean.getBounds()));
	}

	static String objectToXML(Object o) {
		final ByteArrayOutputStream bos = new ByteArrayOutputStream();
		final XMLEncoder encoder = new XMLEncoder(bos);

		encoder.setPersistenceDelegate(Rectangle2D.Double.class, new DefaultPersistenceDelegate(
				new String[] { "x", "y", "width", "height" }));

		encoder.writeObject(o);
		encoder.close();
		return bos.toString();
	}

}

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

CUSTOMER SUBMITTED WORKAROUND :
Override the mutatesTo() method when using DefaultPersistenceDelegate, for example:

encoder.setPersistenceDelegate(Rectangle2D.Double.class, new DefaultPersistenceDelegate(
		new String[] { "x", "y", "width", "height" }) {
	@Override
	protected boolean mutatesTo(Object oldInstance, Object newInstance) {
		return (super.mutatesTo(oldInstance, newInstance) && oldInstance.equals(newInstance));
	}
});

Comments
Seems it is a duplicate of the 7169395 issue.
28-05-2013

EVALUATION XMLEncoder assumes that JavaBeans could validate itself to reduce a size of resulting XML. Also it helps to avoid StackOverflowException for those classes that does not have correct equals method. To fix the cause XMLEncoder/Encoder should be completely refactored.
08-10-2010