1 /*
   2  * Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  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 String[] constructor;
  62     private Boolean definesEquals;
  63 
  64     /**
  65      * Creates a persistence delegate for a class with a nullary constructor.
  66      *
  67      * @see #DefaultPersistenceDelegate(java.lang.String[])
  68      */
  69     public DefaultPersistenceDelegate() {
  70         this(new String[0]);
  71     }
  72 
  73     /**
  74      * Creates a default persistence delegate for a class with a
  75      * constructor whose arguments are the values of the property
  76      * names as specified by <code>constructorPropertyNames</code>.
  77      * The constructor arguments are created by
  78      * evaluating the property names in the order they are supplied.
  79      * To use this class to specify a single preferred constructor for use
  80      * in the serialization of a particular type, we state the
  81      * names of the properties that make up the constructor's
  82      * arguments. For example, the <code>Font</code> class which
  83      * does not define a nullary constructor can be handled
  84      * with the following persistence delegate:
  85      *
  86      * <pre>
  87      *     new DefaultPersistenceDelegate(new String[]{"name", "style", "size"});
  88      * </pre>
  89      *
  90      * @param  constructorPropertyNames The property names for the arguments of this constructor.
  91      *
  92      * @see #instantiate
  93      */
  94     public DefaultPersistenceDelegate(String[] constructorPropertyNames) {
  95         this.constructor = constructorPropertyNames;
  96     }
  97 
  98     private static boolean definesEquals(Class type) {
  99         try {
 100             return type == type.getMethod("equals", Object.class).getDeclaringClass();
 101         }
 102         catch(NoSuchMethodException e) {
 103             return false;
 104         }
 105     }
 106 
 107     private boolean definesEquals(Object instance) {
 108         if (definesEquals != null) {
 109             return (definesEquals == Boolean.TRUE);
 110         }
 111         else {
 112             boolean result = definesEquals(instance.getClass());
 113             definesEquals = result ? Boolean.TRUE : Boolean.FALSE;
 114             return result;
 115         }
 116     }
 117 
 118     /**
 119      * If the number of arguments in the specified constructor is non-zero and
 120      * the class of <code>oldInstance</code> explicitly declares an "equals" method
 121      * this method returns the value of <code>oldInstance.equals(newInstance)</code>.
 122      * Otherwise, this method uses the superclass's definition which returns true if the
 123      * classes of the two instances are equal.
 124      *
 125      * @param oldInstance The instance to be copied.
 126      * @param newInstance The instance that is to be modified.
 127      * @return True if an equivalent copy of <code>newInstance</code> may be
 128      *         created by applying a series of mutations to <code>oldInstance</code>.
 129      *
 130      * @see #DefaultPersistenceDelegate(String[])
 131      */
 132     protected boolean mutatesTo(Object oldInstance, Object newInstance) {
 133         // Assume the instance is either mutable or a singleton
 134         // if it has a nullary constructor.
 135         return (constructor.length == 0) || !definesEquals(oldInstance) ?
 136             super.mutatesTo(oldInstance, newInstance) :
 137             oldInstance.equals(newInstance);
 138     }
 139 
 140     /**
 141      * This default implementation of the <code>instantiate</code> method returns
 142      * an expression containing the predefined method name "new" which denotes a
 143      * call to a constructor with the arguments as specified in
 144      * the <code>DefaultPersistenceDelegate</code>'s constructor.
 145      *
 146      * @param  oldInstance The instance to be instantiated.
 147      * @param  out The code output stream.
 148      * @return An expression whose value is <code>oldInstance</code>.
 149      *
 150      * @throws NullPointerException if {@code out} is {@code null}
 151      *
 152      * @see #DefaultPersistenceDelegate(String[])
 153      */
 154     protected Expression instantiate(Object oldInstance, Encoder out) {
 155         int nArgs = constructor.length;
 156         Class type = oldInstance.getClass();
 157         Object[] constructorArgs = new Object[nArgs];
 158         for(int i = 0; i < nArgs; i++) {
 159             try {
 160                 Method method = findMethod(type, this.constructor[i]);
 161                 constructorArgs[i] = MethodUtil.invoke(method, oldInstance, new Object[0]);
 162             }
 163             catch (Exception e) {
 164                 out.getExceptionListener().exceptionThrown(e);
 165             }
 166         }
 167         return new Expression(oldInstance, oldInstance.getClass(), "new", constructorArgs);
 168     }
 169 
 170     private Method findMethod(Class type, String property) {
 171         if (property == null) {
 172             throw new IllegalArgumentException("Property name is null");
 173         }
 174         PropertyDescriptor pd = getPropertyDescriptor(type, property);
 175         if (pd == null) {
 176             throw new IllegalStateException("Could not find property by the name " + property);
 177         }
 178         Method method = pd.getReadMethod();
 179         if (method == null) {
 180             throw new IllegalStateException("Could not find getter for the property " + property);
 181         }
 182         return method;
 183     }
 184 
 185     private void doProperty(Class type, PropertyDescriptor pd, Object oldInstance, Object newInstance, Encoder out) throws Exception {
 186         Method getter = pd.getReadMethod();
 187         Method setter = pd.getWriteMethod();
 188 
 189         if (getter != null && setter != null) {
 190             Expression oldGetExp = new Expression(oldInstance, getter.getName(), new Object[]{});
 191             Expression newGetExp = new Expression(newInstance, getter.getName(), new Object[]{});
 192             Object oldValue = oldGetExp.getValue();
 193             Object newValue = newGetExp.getValue();
 194             out.writeExpression(oldGetExp);
 195             if (!Objects.equals(newValue, out.get(oldValue))) {
 196                 // Search for a static constant with this value;
 197                 Object e = (Object[])pd.getValue("enumerationValues");
 198                 if (e instanceof Object[] && Array.getLength(e) % 3 == 0) {
 199                     Object[] a = (Object[])e;
 200                     for(int i = 0; i < a.length; i = i + 3) {
 201                         try {
 202                            Field f = type.getField((String)a[i]);
 203                            if (f.get(null).equals(oldValue)) {
 204                                out.remove(oldValue);
 205                                out.writeExpression(new Expression(oldValue, f, "get", new Object[]{null}));
 206                            }
 207                         }
 208                         catch (Exception ex) {}
 209                     }
 210                 }
 211                 invokeStatement(oldInstance, setter.getName(), new Object[]{oldValue}, out);
 212             }
 213         }
 214     }
 215 
 216     static void invokeStatement(Object instance, String methodName, Object[] args, Encoder out) {
 217         out.writeStatement(new Statement(instance, methodName, args));
 218     }
 219 
 220     // Write out the properties of this instance.
 221     private void initBean(Class type, Object oldInstance, Object newInstance, Encoder out) {
 222         for (Field field : type.getFields()) {
 223             int mod = field.getModifiers();
 224             if (Modifier.isFinal(mod) || Modifier.isStatic(mod) || Modifier.isTransient(mod)) {
 225                 continue;
 226             }
 227             try {
 228                 Expression oldGetExp = new Expression(field, "get", new Object[] { oldInstance });
 229                 Expression newGetExp = new Expression(field, "get", new Object[] { newInstance });
 230                 Object oldValue = oldGetExp.getValue();
 231                 Object newValue = newGetExp.getValue();
 232                 out.writeExpression(oldGetExp);
 233                 if (!Objects.equals(newValue, out.get(oldValue))) {
 234                     out.writeStatement(new Statement(field, "set", new Object[] { oldInstance, oldValue }));
 235                 }
 236             }
 237             catch (Exception exception) {
 238                 out.getExceptionListener().exceptionThrown(exception);
 239             }
 240         }
 241         BeanInfo info;
 242         try {
 243             info = Introspector.getBeanInfo(type);
 244         } catch (IntrospectionException exception) {
 245             return;
 246         }
 247         // Properties
 248         for (PropertyDescriptor d : info.getPropertyDescriptors()) {
 249             if (d.isTransient()) {
 250                 continue;
 251             }
 252             try {
 253                 doProperty(type, d, oldInstance, newInstance, out);
 254             }
 255             catch (Exception e) {
 256                 out.getExceptionListener().exceptionThrown(e);
 257             }
 258         }
 259 
 260         // Listeners
 261         /*
 262         Pending(milne). There is a general problem with the archival of
 263         listeners which is unresolved as of 1.4. Many of the methods
 264         which install one object inside another (typically "add" methods
 265         or setters) automatically install a listener on the "child" object
 266         so that its "parent" may respond to changes that are made to it.
 267         For example the JTable:setModel() method automatically adds a
 268         TableModelListener (the JTable itself in this case) to the supplied
 269         table model.
 270 
 271         We do not need to explictly add these listeners to the model in an
 272         archive as they will be added automatically by, in the above case,
 273         the JTable's "setModel" method. In some cases, we must specifically
 274         avoid trying to do this since the listener may be an inner class
 275         that cannot be instantiated using public API.
 276 
 277         No general mechanism currently
 278         exists for differentiating between these kind of listeners and
 279         those which were added explicitly by the user. A mechanism must
 280         be created to provide a general means to differentiate these
 281         special cases so as to provide reliable persistence of listeners
 282         for the general case.
 283         */
 284         if (!java.awt.Component.class.isAssignableFrom(type)) {
 285             return; // Just handle the listeners of Components for now.
 286         }
 287         for (EventSetDescriptor d : info.getEventSetDescriptors()) {
 288             if (d.isTransient()) {
 289                 continue;
 290             }
 291             Class listenerType = d.getListenerType();
 292 
 293 
 294             // The ComponentListener is added automatically, when
 295             // Contatiner:add is called on the parent.
 296             if (listenerType == java.awt.event.ComponentListener.class) {
 297                 continue;
 298             }
 299 
 300             // JMenuItems have a change listener added to them in
 301             // their "add" methods to enable accessibility support -
 302             // see the add method in JMenuItem for details. We cannot
 303             // instantiate this instance as it is a private inner class
 304             // and do not need to do this anyway since it will be created
 305             // and installed by the "add" method. Special case this for now,
 306             // ignoring all change listeners on JMenuItems.
 307             if (listenerType == javax.swing.event.ChangeListener.class &&
 308                 type == javax.swing.JMenuItem.class) {
 309                 continue;
 310             }
 311 
 312             EventListener[] oldL = new EventListener[0];
 313             EventListener[] newL = new EventListener[0];
 314             try {
 315                 Method m = d.getGetListenerMethod();
 316                 oldL = (EventListener[])MethodUtil.invoke(m, oldInstance, new Object[]{});
 317                 newL = (EventListener[])MethodUtil.invoke(m, newInstance, new Object[]{});
 318             }
 319             catch (Exception e2) {
 320                 try {
 321                     Method m = type.getMethod("getListeners", new Class[]{Class.class});
 322                     oldL = (EventListener[])MethodUtil.invoke(m, oldInstance, new Object[]{listenerType});
 323                     newL = (EventListener[])MethodUtil.invoke(m, newInstance, new Object[]{listenerType});
 324                 }
 325                 catch (Exception e3) {
 326                     return;
 327                 }
 328             }
 329 
 330             // Asssume the listeners are in the same order and that there are no gaps.
 331             // Eventually, this may need to do true differencing.
 332             String addListenerMethodName = d.getAddListenerMethod().getName();
 333             for (int i = newL.length; i < oldL.length; i++) {
 334                 // System.out.println("Adding listener: " + addListenerMethodName + oldL[i]);
 335                 invokeStatement(oldInstance, addListenerMethodName, new Object[]{oldL[i]}, out);
 336             }
 337 
 338             String removeListenerMethodName = d.getRemoveListenerMethod().getName();
 339             for (int i = oldL.length; i < newL.length; i++) {
 340                 invokeStatement(oldInstance, removeListenerMethodName, new Object[]{newL[i]}, out);
 341             }
 342         }
 343     }
 344 
 345     /**
 346      * This default implementation of the <code>initialize</code> method assumes
 347      * all state held in objects of this type is exposed via the
 348      * matching pairs of "setter" and "getter" methods in the order
 349      * they are returned by the Introspector. If a property descriptor
 350      * defines a "transient" attribute with a value equal to
 351      * <code>Boolean.TRUE</code> the property is ignored by this
 352      * default implementation. Note that this use of the word
 353      * "transient" is quite independent of the field modifier
 354      * that is used by the <code>ObjectOutputStream</code>.
 355      * <p>
 356      * For each non-transient property, an expression is created
 357      * in which the nullary "getter" method is applied
 358      * to the <code>oldInstance</code>. The value of this
 359      * expression is the value of the property in the instance that is
 360      * being serialized. If the value of this expression
 361      * in the cloned environment <code>mutatesTo</code> the
 362      * target value, the new value is initialized to make it
 363      * equivalent to the old value. In this case, because
 364      * the property value has not changed there is no need to
 365      * call the corresponding "setter" method and no statement
 366      * is emitted. If not however, the expression for this value
 367      * is replaced with another expression (normally a constructor)
 368      * and the corresponding "setter" method is called to install
 369      * the new property value in the object. This scheme removes
 370      * default information from the output produced by streams
 371      * using this delegate.
 372      * <p>
 373      * In passing these statements to the output stream, where they
 374      * will be executed, side effects are made to the <code>newInstance</code>.
 375      * In most cases this allows the problem of properties
 376      * whose values depend on each other to actually help the
 377      * serialization process by making the number of statements
 378      * that need to be written to the output smaller. In general,
 379      * the problem of handling interdependent properties is reduced to
 380      * that of finding an order for the properties in
 381      * a class such that no property value depends on the value of
 382      * a subsequent property.
 383      *
 384      * @param oldInstance The instance to be copied.
 385      * @param newInstance The instance that is to be modified.
 386      * @param out The stream to which any initialization statements should be written.
 387      *
 388      * @throws NullPointerException if {@code out} is {@code null}
 389      *
 390      * @see java.beans.Introspector#getBeanInfo
 391      * @see java.beans.PropertyDescriptor
 392      */
 393     protected void initialize(Class<?> type,
 394                               Object oldInstance, Object newInstance,
 395                               Encoder out)
 396     {
 397         // System.out.println("DefulatPD:initialize" + type);
 398         super.initialize(type, oldInstance, newInstance, out);
 399         if (oldInstance.getClass() == type) { // !type.isInterface()) {
 400             initBean(type, oldInstance, newInstance, out);
 401         }
 402     }
 403 
 404     private static PropertyDescriptor getPropertyDescriptor(Class type, String property) {
 405         try {
 406             for (PropertyDescriptor pd : Introspector.getBeanInfo(type).getPropertyDescriptors()) {
 407                 if (property.equals(pd.getName()))
 408                     return pd;
 409             }
 410         } catch (IntrospectionException exception) {
 411         }
 412         return null;
 413     }
 414 }