|
Description
|
FULL PRODUCT VERSION :
All 1.4.x releases.
FULL OS VERSION :
Applicable to all.
A DESCRIPTION OF THE PROBLEM :
The DefaultPersistenceDelegate does not correctly search for boolean properties if they are in a superclass. The following code (line 162 in 1.4.2) illustrates the problem:
constructorArgs[i] = (f != null && !Modifier.isStatic(f.getModifiers())) ?
f.get(oldInstance) :
type.getMethod("get"+capitalize(name), new Class[0]).invoke(oldInstance, new Object[0]);
In the case that type == boolean/Boolean, the code should check for "is"+capatalize(name) and then fall back on "get" if that fails.
Section 8.3.2 of the JavaBeans Specification at http://java.sun.com/products/javabeans/docs/spec.html describes the "is<PropertyName>" naming pattern for boolean properties.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the test case.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import java.beans.XMLEncoder;
import java.beans.DefaultPersistenceDelegate;
import java.io.ByteArrayOutputStream;
public class DefaultPersistenceDelegateBug {
public static class SuperBean {
private boolean condition;
public SuperBean(boolean condition) {
this.condition = condition;
}
public boolean isCondition() {
return condition;
}
}
public static class SubBean extends SuperBean {
public SubBean(boolean condition) {
super(condition);
}
}
public static void main(String[] args) {
XMLEncoder encoder = new XMLEncoder(new ByteArrayOutputStream());
encoder.setPersistenceDelegate(SubBean.class, new DefaultPersistenceDelegate(new String[]{"condition"}));
encoder.writeObject(new SubBean(true));
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
If you have access to your beans, then you can add/change your boolean getter to be in "get" pattern instead of the "is" pattern.
(Incident Review ID: 207880)
======================================================================
DESCRIPTION OF THE PROBLEM :
Below is a test case. I have two classes that I want to persist.
Test1 has a boolean value that is set by the constructor and can be accessed by isSomething().
Test2 extends Test1 and adds some more behaviour.
To persist objects of both classes I create an XMLEncoder and add DefaultPersistenceDelegate's for both classes, telling them that they should get the value of the something-attribute.
I can persist objects of class Test1. However if I try to persist objects of class Test2 I get "java.lang.NoSuchMethodException: Test2.getSomething()"
According to the JavaBeans specification boolean attributes can be accessed either by getXXX or by isXXX. DefaultPersistenceDelegate should support both methods.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
DefaultPersistenceDelegate should use the field itself or either method "getSomething" or "isSomething".
ACTUAL -
DefaultPersistenceDelegate uses the field itself or just "getSomething".
---------- BEGIN SOURCE ----------
import java.beans.DefaultPersistenceDelegate;
import java.beans.XMLEncoder;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
XMLEncoder e = new XMLEncoder(
new BufferedOutputStream(
new FileOutputStream("Test.xml")));
e.setPersistenceDelegate(Test1.class,
new DefaultPersistenceDelegate(new String[]{"something"}));
e.setPersistenceDelegate(Test2.class, new DefaultPersistenceDelegate(new String[]{"something"}));
e.writeObject(new Test1(true));
e.writeObject(new Test2(true));
e.flush();
e.close();
}
}
public class Test1 {
private boolean something;
public Test1(boolean something) {
this.something = something;
}
public boolean isSomething() {
return something;
}
}
public class Test2 extends Test1 {
public Test2(boolean something) {
super(something);
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
If you may modify either Test1 or Test2 then add a method getSomething().
Otherwise create your own PersistenceDelegate that reads all attributes.
(Review ID: 209682)
======================================================================
Posted Date : 2005-10-06 07:30:35.0
|
|
Comments
|
Submitted On 05-JAN-2004
peastman
Just to cast my vote: given how widely the isX() syntax is
used by Swing (isEnabled(), isEditable(), isVisible(),
etc.), this is absolutely a showstopper. It makes XML
serialization completely useless for serializing Swing UIs.
Submitted On 13-JAN-2007
mgrev
Bump...
Submitted On 14-JAN-2007
philip@pmilne.net
I have fixed this in a local copy of DefaultPersistenceDelegate with the following implementation of instantiate:
protected Expression instantiate(Object oldInstance, Encoder out) {
int nArgs = constructor.length;
Class type = oldInstance.getClass();
// System.out.println("writeObject: " + oldInstance);
Object[] constructorArgs = new Object[nArgs];
for (int i = 0; i < nArgs; i++) {
/*
1.2 introduces "public double getX()" et al. which return values
which cannot be used in the constructors (they are the wrong type).
In constructors, use public fields in preference to getters
when they are defined.
*/
String name = constructor[i];
try {
Field f = type.getField(name);
if (!Modifier.isStatic(f.getModifiers())) {
constructorArgs[i] = f.get(oldInstance);
continue;
}
} catch (NoSuchFieldException e) {
} catch (IllegalAccessException e) {
}
try {
Method read = Introspector.getBeanInfo(type).getPropertyDescriptor(name).getReadMethod();
constructorArgs[i] = read.invoke(oldInstance, new Object[0]);
} catch (Exception e) {
throw new RuntimeException("DefaultPersistenceDelegate couldn't retrieve the value of the \'" + name + "\' property for " + oldInstance.getClass() + " constructor. ", e);
}
}
return new Expression(oldInstance, oldInstance.getClass(), "new", constructorArgs);
}
This implementation uses a field in preference to a method only if it is public and because getField() returns inherited fields this avoids the inheritence problem that this bug report uses a subclass to demonstrate). When no public field is found the method looks up the PropertyDescriptor with the given name - that will return an isFoo() method in the case of booleans and, of course, may return a non-standard method in the presence of specific BeanInfo information. The method above fixes the bug in this report.
Submitted On 14-JAN-2007
philip@pmilne.net
I forgot to mention that the suggestion above requires several changes to the MetaData.class as the current version (6.0) still relies on the access to private fields in some of its classes. The bulk of the changes are in the Border classes that have been made persistence-friendly since these versions of the persistence classes appeared. All now allow access to their state via public methods and so can be supported with DefaultPersistenceDelegate. The names of the properties are slightly different to their fields - the properties have been given names that use the normal AWT conventions - that the private fields do not. The new data for the Border classes is available on request (its too long to submit here).
The other two classes that have been affected are BoxLayout and OverlayLayout both of which now require a special persistence delegate to access their "target" fields since there is no public means of access. Suggested fix - add getTarget() to BoXLayout - or, better still, give both these layout managers a nullary constructor. This requires some reorganisation to BoxLayout internals (checkParent() etc.) which needlessly makes BoxLayout a special case amongst the AWT/swing layout managers. Pending fixes to these classes two new delegates are required in MetaData:
class javax_swing_BoxLayout_PersistenceDelegate extends PersistenceDelegate {
protected Expression instantiate(Object oldInstance, Encoder out) {
Object f1 = Utilities.getPrivateField(oldInstance, "target");
Object f2 = Utilities.getPrivateField(oldInstance, "axis");
return new Expression(oldInstance, oldInstance.getClass(), "new", new Object[]{f1, f2});
}
}
class javax_swing_OverlayLayout_PersistenceDelegate extends PersistenceDelegate {
protected Expression instantiate(Object oldInstance, Encoder out) {
return new Expression(oldInstance, oldInstance.getClass(), "new",
new Object[]{Utilities.getPrivateField(oldInstance, "target")});
}
}
With these changes the instantiate method proposed above passed all my tests.
These cover swing/AWT persistence pretty exhaustively and are extensions of the original tests suites for the persistence framework.
PLEASE NOTE: JDK6 is formerly known as Project Mustang
|