< prev index next >

src/java.desktop/share/classes/java/beans/DefaultPersistenceDelegate.java

Print this page




  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package java.beans;
  26 
  27 import java.util.*;
  28 import java.lang.reflect.*;
  29 import java.util.Objects;
  30 import sun.reflect.misc.*;
  31 
  32 
  33 /**
  34  * The <code>DefaultPersistenceDelegate</code> is a concrete implementation of
  35  * the abstract <code>PersistenceDelegate</code> class and
  36  * is the delegate used by default for classes about
  37  * which no information is available. The <code>DefaultPersistenceDelegate</code>
  38  * provides, version resilient, public API-based persistence for
  39  * classes that follow the JavaBeans&trade; conventions without any class specific
  40  * configuration.
  41  * <p>
  42  * The key assumptions are that the class has a nullary constructor
  43  * and that its state is accurately represented by matching pairs
  44  * of "setter" and "getter" methods in the order they are returned
  45  * by the Introspector.
  46  * In addition to providing code-free persistence for JavaBeans,
  47  * the <code>DefaultPersistenceDelegate</code> provides a convenient means
  48  * to effect persistent storage for classes that have a constructor
  49  * that, while not nullary, simply requires some property values
  50  * as arguments.
  51  *
  52  * @see #DefaultPersistenceDelegate(String[])
  53  * @see java.beans.Introspector
  54  *
  55  * @since 1.4
  56  *
  57  * @author Philip Milne
  58  */
  59 
  60 public class DefaultPersistenceDelegate extends PersistenceDelegate {
  61     private static final String[] EMPTY = {};
  62     private final String[] constructor;
  63     private Boolean definesEquals;
  64 
  65     /**
  66      * Creates a persistence delegate for a class with a nullary constructor.
  67      *
  68      * @see #DefaultPersistenceDelegate(java.lang.String[])
  69      */
  70     public DefaultPersistenceDelegate() {
  71         this.constructor = EMPTY;
  72     }
  73 
  74     /**
  75      * Creates a default persistence delegate for a class with a
  76      * constructor whose arguments are the values of the property
  77      * names as specified by <code>constructorPropertyNames</code>.
  78      * The constructor arguments are created by
  79      * evaluating the property names in the order they are supplied.
  80      * To use this class to specify a single preferred constructor for use
  81      * in the serialization of a particular type, we state the
  82      * names of the properties that make up the constructor's
  83      * arguments. For example, the <code>Font</code> class which
  84      * does not define a nullary constructor can be handled
  85      * with the following persistence delegate:
  86      *
  87      * <pre>
  88      *     new DefaultPersistenceDelegate(new String[]{"name", "style", "size"});
  89      * </pre>
  90      *
  91      * @param  constructorPropertyNames The property names for the arguments of this constructor.
  92      *
  93      * @see #instantiate
  94      */
  95     public DefaultPersistenceDelegate(String[] constructorPropertyNames) {
  96         this.constructor = (constructorPropertyNames == null) ? EMPTY : constructorPropertyNames.clone();
  97     }
  98 
  99     private static boolean definesEquals(Class<?> type) {
 100         try {
 101             return type == type.getMethod("equals", Object.class).getDeclaringClass();
 102         }
 103         catch(NoSuchMethodException e) {
 104             return false;
 105         }
 106     }
 107 
 108     private boolean definesEquals(Object instance) {
 109         if (definesEquals != null) {
 110             return (definesEquals == Boolean.TRUE);
 111         }
 112         else {
 113             boolean result = definesEquals(instance.getClass());
 114             definesEquals = result ? Boolean.TRUE : Boolean.FALSE;
 115             return result;
 116         }
 117     }
 118 
 119     /**
 120      * If the number of arguments in the specified constructor is non-zero and
 121      * the class of <code>oldInstance</code> explicitly declares an "equals" method
 122      * this method returns the value of <code>oldInstance.equals(newInstance)</code>.
 123      * Otherwise, this method uses the superclass's definition which returns true if the
 124      * classes of the two instances are equal.
 125      *
 126      * @param oldInstance The instance to be copied.
 127      * @param newInstance The instance that is to be modified.
 128      * @return True if an equivalent copy of <code>newInstance</code> may be
 129      *         created by applying a series of mutations to <code>oldInstance</code>.
 130      *
 131      * @see #DefaultPersistenceDelegate(String[])
 132      */
 133     protected boolean mutatesTo(Object oldInstance, Object newInstance) {
 134         // Assume the instance is either mutable or a singleton
 135         // if it has a nullary constructor.
 136         return (constructor.length == 0) || !definesEquals(oldInstance) ?
 137             super.mutatesTo(oldInstance, newInstance) :
 138             oldInstance.equals(newInstance);
 139     }
 140 
 141     /**
 142      * This default implementation of the <code>instantiate</code> method returns
 143      * an expression containing the predefined method name "new" which denotes a
 144      * call to a constructor with the arguments as specified in
 145      * the <code>DefaultPersistenceDelegate</code>'s constructor.
 146      *
 147      * @param  oldInstance The instance to be instantiated.
 148      * @param  out The code output stream.
 149      * @return An expression whose value is <code>oldInstance</code>.
 150      *
 151      * @throws NullPointerException if {@code out} is {@code null}
 152      *                              and this value is used in the method
 153      *
 154      * @see #DefaultPersistenceDelegate(String[])
 155      */
 156     protected Expression instantiate(Object oldInstance, Encoder out) {
 157         int nArgs = constructor.length;
 158         Class<?> type = oldInstance.getClass();
 159         Object[] constructorArgs = new Object[nArgs];
 160         for(int i = 0; i < nArgs; i++) {
 161             try {
 162                 Method method = findMethod(type, this.constructor[i]);
 163                 constructorArgs[i] = MethodUtil.invoke(method, oldInstance, new Object[0]);
 164             }
 165             catch (Exception e) {
 166                 out.getExceptionListener().exceptionThrown(e);
 167             }
 168         }
 169         return new Expression(oldInstance, oldInstance.getClass(), "new", constructorArgs);


 331                     return;
 332                 }
 333             }
 334 
 335             // Asssume the listeners are in the same order and that there are no gaps.
 336             // Eventually, this may need to do true differencing.
 337             String addListenerMethodName = d.getAddListenerMethod().getName();
 338             for (int i = newL.length; i < oldL.length; i++) {
 339                 // System.out.println("Adding listener: " + addListenerMethodName + oldL[i]);
 340                 invokeStatement(oldInstance, addListenerMethodName, new Object[]{oldL[i]}, out);
 341             }
 342 
 343             String removeListenerMethodName = d.getRemoveListenerMethod().getName();
 344             for (int i = oldL.length; i < newL.length; i++) {
 345                 invokeStatement(oldInstance, removeListenerMethodName, new Object[]{newL[i]}, out);
 346             }
 347         }
 348     }
 349 
 350     /**
 351      * This default implementation of the <code>initialize</code> method assumes
 352      * all state held in objects of this type is exposed via the
 353      * matching pairs of "setter" and "getter" methods in the order
 354      * they are returned by the Introspector. If a property descriptor
 355      * defines a "transient" attribute with a value equal to
 356      * <code>Boolean.TRUE</code> the property is ignored by this
 357      * default implementation. Note that this use of the word
 358      * "transient" is quite independent of the field modifier
 359      * that is used by the <code>ObjectOutputStream</code>.
 360      * <p>
 361      * For each non-transient property, an expression is created
 362      * in which the nullary "getter" method is applied
 363      * to the <code>oldInstance</code>. The value of this
 364      * expression is the value of the property in the instance that is
 365      * being serialized. If the value of this expression
 366      * in the cloned environment <code>mutatesTo</code> the
 367      * target value, the new value is initialized to make it
 368      * equivalent to the old value. In this case, because
 369      * the property value has not changed there is no need to
 370      * call the corresponding "setter" method and no statement
 371      * is emitted. If not however, the expression for this value
 372      * is replaced with another expression (normally a constructor)
 373      * and the corresponding "setter" method is called to install
 374      * the new property value in the object. This scheme removes
 375      * default information from the output produced by streams
 376      * using this delegate.
 377      * <p>
 378      * In passing these statements to the output stream, where they
 379      * will be executed, side effects are made to the <code>newInstance</code>.
 380      * In most cases this allows the problem of properties
 381      * whose values depend on each other to actually help the
 382      * serialization process by making the number of statements
 383      * that need to be written to the output smaller. In general,
 384      * the problem of handling interdependent properties is reduced to
 385      * that of finding an order for the properties in
 386      * a class such that no property value depends on the value of
 387      * a subsequent property.
 388      *
 389      * @param type the type of the instances
 390      * @param oldInstance The instance to be copied.
 391      * @param newInstance The instance that is to be modified.
 392      * @param out The stream to which any initialization statements should be written.
 393      *
 394      * @throws NullPointerException if {@code out} is {@code null}
 395      *
 396      * @see java.beans.Introspector#getBeanInfo
 397      * @see java.beans.PropertyDescriptor
 398      */
 399     protected void initialize(Class<?> type,




  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package java.beans;
  26 
  27 import java.util.*;
  28 import java.lang.reflect.*;
  29 import java.util.Objects;
  30 import sun.reflect.misc.*;
  31 
  32 
  33 /**
  34  * The {@code DefaultPersistenceDelegate} is a concrete implementation of
  35  * the abstract {@code PersistenceDelegate} class and
  36  * is the delegate used by default for classes about
  37  * which no information is available. The {@code DefaultPersistenceDelegate}
  38  * provides, version resilient, public API-based persistence for
  39  * classes that follow the JavaBeans&trade; conventions without any class specific
  40  * configuration.
  41  * <p>
  42  * The key assumptions are that the class has a nullary constructor
  43  * and that its state is accurately represented by matching pairs
  44  * of "setter" and "getter" methods in the order they are returned
  45  * by the Introspector.
  46  * In addition to providing code-free persistence for JavaBeans,
  47  * the {@code DefaultPersistenceDelegate} provides a convenient means
  48  * to effect persistent storage for classes that have a constructor
  49  * that, while not nullary, simply requires some property values
  50  * as arguments.
  51  *
  52  * @see #DefaultPersistenceDelegate(String[])
  53  * @see java.beans.Introspector
  54  *
  55  * @since 1.4
  56  *
  57  * @author Philip Milne
  58  */
  59 
  60 public class DefaultPersistenceDelegate extends PersistenceDelegate {
  61     private static final String[] EMPTY = {};
  62     private final String[] constructor;
  63     private Boolean definesEquals;
  64 
  65     /**
  66      * Creates a persistence delegate for a class with a nullary constructor.
  67      *
  68      * @see #DefaultPersistenceDelegate(java.lang.String[])
  69      */
  70     public DefaultPersistenceDelegate() {
  71         this.constructor = EMPTY;
  72     }
  73 
  74     /**
  75      * Creates a default persistence delegate for a class with a
  76      * constructor whose arguments are the values of the property
  77      * names as specified by {@code constructorPropertyNames}.
  78      * The constructor arguments are created by
  79      * evaluating the property names in the order they are supplied.
  80      * To use this class to specify a single preferred constructor for use
  81      * in the serialization of a particular type, we state the
  82      * names of the properties that make up the constructor's
  83      * arguments. For example, the {@code Font} class which
  84      * does not define a nullary constructor can be handled
  85      * with the following persistence delegate:
  86      *
  87      * <pre>
  88      *     new DefaultPersistenceDelegate(new String[]{"name", "style", "size"});
  89      * </pre>
  90      *
  91      * @param  constructorPropertyNames The property names for the arguments of this constructor.
  92      *
  93      * @see #instantiate
  94      */
  95     public DefaultPersistenceDelegate(String[] constructorPropertyNames) {
  96         this.constructor = (constructorPropertyNames == null) ? EMPTY : constructorPropertyNames.clone();
  97     }
  98 
  99     private static boolean definesEquals(Class<?> type) {
 100         try {
 101             return type == type.getMethod("equals", Object.class).getDeclaringClass();
 102         }
 103         catch(NoSuchMethodException e) {
 104             return false;
 105         }
 106     }
 107 
 108     private boolean definesEquals(Object instance) {
 109         if (definesEquals != null) {
 110             return (definesEquals == Boolean.TRUE);
 111         }
 112         else {
 113             boolean result = definesEquals(instance.getClass());
 114             definesEquals = result ? Boolean.TRUE : Boolean.FALSE;
 115             return result;
 116         }
 117     }
 118 
 119     /**
 120      * If the number of arguments in the specified constructor is non-zero and
 121      * the class of {@code oldInstance} explicitly declares an "equals" method
 122      * this method returns the value of {@code oldInstance.equals(newInstance)}.
 123      * Otherwise, this method uses the superclass's definition which returns true if the
 124      * classes of the two instances are equal.
 125      *
 126      * @param oldInstance The instance to be copied.
 127      * @param newInstance The instance that is to be modified.
 128      * @return True if an equivalent copy of {@code newInstance} may be
 129      *         created by applying a series of mutations to {@code oldInstance}.
 130      *
 131      * @see #DefaultPersistenceDelegate(String[])
 132      */
 133     protected boolean mutatesTo(Object oldInstance, Object newInstance) {
 134         // Assume the instance is either mutable or a singleton
 135         // if it has a nullary constructor.
 136         return (constructor.length == 0) || !definesEquals(oldInstance) ?
 137             super.mutatesTo(oldInstance, newInstance) :
 138             oldInstance.equals(newInstance);
 139     }
 140 
 141     /**
 142      * This default implementation of the {@code instantiate} method returns
 143      * an expression containing the predefined method name "new" which denotes a
 144      * call to a constructor with the arguments as specified in
 145      * the {@code DefaultPersistenceDelegate}'s constructor.
 146      *
 147      * @param  oldInstance The instance to be instantiated.
 148      * @param  out The code output stream.
 149      * @return An expression whose value is {@code oldInstance}.
 150      *
 151      * @throws NullPointerException if {@code out} is {@code null}
 152      *                              and this value is used in the method
 153      *
 154      * @see #DefaultPersistenceDelegate(String[])
 155      */
 156     protected Expression instantiate(Object oldInstance, Encoder out) {
 157         int nArgs = constructor.length;
 158         Class<?> type = oldInstance.getClass();
 159         Object[] constructorArgs = new Object[nArgs];
 160         for(int i = 0; i < nArgs; i++) {
 161             try {
 162                 Method method = findMethod(type, this.constructor[i]);
 163                 constructorArgs[i] = MethodUtil.invoke(method, oldInstance, new Object[0]);
 164             }
 165             catch (Exception e) {
 166                 out.getExceptionListener().exceptionThrown(e);
 167             }
 168         }
 169         return new Expression(oldInstance, oldInstance.getClass(), "new", constructorArgs);


 331                     return;
 332                 }
 333             }
 334 
 335             // Asssume the listeners are in the same order and that there are no gaps.
 336             // Eventually, this may need to do true differencing.
 337             String addListenerMethodName = d.getAddListenerMethod().getName();
 338             for (int i = newL.length; i < oldL.length; i++) {
 339                 // System.out.println("Adding listener: " + addListenerMethodName + oldL[i]);
 340                 invokeStatement(oldInstance, addListenerMethodName, new Object[]{oldL[i]}, out);
 341             }
 342 
 343             String removeListenerMethodName = d.getRemoveListenerMethod().getName();
 344             for (int i = oldL.length; i < newL.length; i++) {
 345                 invokeStatement(oldInstance, removeListenerMethodName, new Object[]{newL[i]}, out);
 346             }
 347         }
 348     }
 349 
 350     /**
 351      * This default implementation of the {@code initialize} method assumes
 352      * all state held in objects of this type is exposed via the
 353      * matching pairs of "setter" and "getter" methods in the order
 354      * they are returned by the Introspector. If a property descriptor
 355      * defines a "transient" attribute with a value equal to
 356      * {@code Boolean.TRUE} the property is ignored by this
 357      * default implementation. Note that this use of the word
 358      * "transient" is quite independent of the field modifier
 359      * that is used by the {@code ObjectOutputStream}.
 360      * <p>
 361      * For each non-transient property, an expression is created
 362      * in which the nullary "getter" method is applied
 363      * to the {@code oldInstance}. The value of this
 364      * expression is the value of the property in the instance that is
 365      * being serialized. If the value of this expression
 366      * in the cloned environment {@code mutatesTo} the
 367      * target value, the new value is initialized to make it
 368      * equivalent to the old value. In this case, because
 369      * the property value has not changed there is no need to
 370      * call the corresponding "setter" method and no statement
 371      * is emitted. If not however, the expression for this value
 372      * is replaced with another expression (normally a constructor)
 373      * and the corresponding "setter" method is called to install
 374      * the new property value in the object. This scheme removes
 375      * default information from the output produced by streams
 376      * using this delegate.
 377      * <p>
 378      * In passing these statements to the output stream, where they
 379      * will be executed, side effects are made to the {@code newInstance}.
 380      * In most cases this allows the problem of properties
 381      * whose values depend on each other to actually help the
 382      * serialization process by making the number of statements
 383      * that need to be written to the output smaller. In general,
 384      * the problem of handling interdependent properties is reduced to
 385      * that of finding an order for the properties in
 386      * a class such that no property value depends on the value of
 387      * a subsequent property.
 388      *
 389      * @param type the type of the instances
 390      * @param oldInstance The instance to be copied.
 391      * @param newInstance The instance that is to be modified.
 392      * @param out The stream to which any initialization statements should be written.
 393      *
 394      * @throws NullPointerException if {@code out} is {@code null}
 395      *
 396      * @see java.beans.Introspector#getBeanInfo
 397      * @see java.beans.PropertyDescriptor
 398      */
 399     protected void initialize(Class<?> type,


< prev index next >