1 /*
   2  * Copyright (c) 2000, 2013, 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} 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 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);
 170     }
 171 
 172     private Method findMethod(Class<?> type, String property) {
 173         if (property == null) {
 174             throw new IllegalArgumentException("Property name is null");
 175         }
 176         PropertyDescriptor pd = getPropertyDescriptor(type, property);
 177         if (pd == null) {
 178             throw new IllegalStateException("Could not find property by the name " + property);
 179         }
 180         Method method = pd.getReadMethod();
 181         if (method == null) {
 182             throw new IllegalStateException("Could not find getter for the property " + property);
 183         }
 184         return method;
 185     }
 186 
 187     private void doProperty(Class<?> type, PropertyDescriptor pd, Object oldInstance, Object newInstance, Encoder out) throws Exception {
 188         Method getter = pd.getReadMethod();
 189         Method setter = pd.getWriteMethod();
 190 
 191         if (getter != null && setter != null) {
 192             Expression oldGetExp = new Expression(oldInstance, getter.getName(), new Object[]{});
 193             Expression newGetExp = new Expression(newInstance, getter.getName(), new Object[]{});
 194             Object oldValue = oldGetExp.getValue();
 195             Object newValue = newGetExp.getValue();
 196             out.writeExpression(oldGetExp);
 197             if (!Objects.equals(newValue, out.get(oldValue))) {
 198                 // Search for a static constant with this value;
 199                 Object e = (Object[])pd.getValue("enumerationValues");
 200                 if (e instanceof Object[] && Array.getLength(e) % 3 == 0) {
 201                     Object[] a = (Object[])e;
 202                     for(int i = 0; i < a.length; i = i + 3) {
 203                         try {
 204                            Field f = type.getField((String)a[i]);
 205                            if (f.get(null).equals(oldValue)) {
 206                                out.remove(oldValue);
 207                                out.writeExpression(new Expression(oldValue, f, "get", new Object[]{null}));
 208                            }
 209                         }
 210                         catch (Exception ex) {}
 211                     }
 212                 }
 213                 invokeStatement(oldInstance, setter.getName(), new Object[]{oldValue}, out);
 214             }
 215         }
 216     }
 217 
 218     static void invokeStatement(Object instance, String methodName, Object[] args, Encoder out) {
 219         out.writeStatement(new Statement(instance, methodName, args));
 220     }
 221 
 222     // Write out the properties of this instance.
 223     private void initBean(Class<?> type, Object oldInstance, Object newInstance, Encoder out) {
 224         for (Field field : type.getFields()) {
 225             if (!ReflectUtil.isPackageAccessible(field.getDeclaringClass())) {
 226                 continue;
 227             }
 228             int mod = field.getModifiers();
 229             if (Modifier.isFinal(mod) || Modifier.isStatic(mod) || Modifier.isTransient(mod)) {
 230                 continue;
 231             }
 232             try {
 233                 Expression oldGetExp = new Expression(field, "get", new Object[] { oldInstance });
 234                 Expression newGetExp = new Expression(field, "get", new Object[] { newInstance });
 235                 Object oldValue = oldGetExp.getValue();
 236                 Object newValue = newGetExp.getValue();
 237                 out.writeExpression(oldGetExp);
 238                 if (!Objects.equals(newValue, out.get(oldValue))) {
 239                     out.writeStatement(new Statement(field, "set", new Object[] { oldInstance, oldValue }));
 240                 }
 241             }
 242             catch (Exception exception) {
 243                 out.getExceptionListener().exceptionThrown(exception);
 244             }
 245         }
 246         BeanInfo info;
 247         try {
 248             info = Introspector.getBeanInfo(type);
 249         } catch (IntrospectionException exception) {
 250             return;
 251         }
 252         // Properties
 253         for (PropertyDescriptor d : info.getPropertyDescriptors()) {
 254             if (d.isTransient()) {
 255                 continue;
 256             }
 257             try {
 258                 doProperty(type, d, oldInstance, newInstance, out);
 259             }
 260             catch (Exception e) {
 261                 out.getExceptionListener().exceptionThrown(e);
 262             }
 263         }
 264 
 265         // Listeners
 266         /*
 267         Pending(milne). There is a general problem with the archival of
 268         listeners which is unresolved as of 1.4. Many of the methods
 269         which install one object inside another (typically "add" methods
 270         or setters) automatically install a listener on the "child" object
 271         so that its "parent" may respond to changes that are made to it.
 272         For example the JTable:setModel() method automatically adds a
 273         TableModelListener (the JTable itself in this case) to the supplied
 274         table model.
 275 
 276         We do not need to explicitly add these listeners to the model in an
 277         archive as they will be added automatically by, in the above case,
 278         the JTable's "setModel" method. In some cases, we must specifically
 279         avoid trying to do this since the listener may be an inner class
 280         that cannot be instantiated using public API.
 281 
 282         No general mechanism currently
 283         exists for differentiating between these kind of listeners and
 284         those which were added explicitly by the user. A mechanism must
 285         be created to provide a general means to differentiate these
 286         special cases so as to provide reliable persistence of listeners
 287         for the general case.
 288         */
 289         if (!java.awt.Component.class.isAssignableFrom(type)) {
 290             return; // Just handle the listeners of Components for now.
 291         }
 292         for (EventSetDescriptor d : info.getEventSetDescriptors()) {
 293             if (d.isTransient()) {
 294                 continue;
 295             }
 296             Class<?> listenerType = d.getListenerType();
 297 
 298 
 299             // The ComponentListener is added automatically, when
 300             // Contatiner:add is called on the parent.
 301             if (listenerType == java.awt.event.ComponentListener.class) {
 302                 continue;
 303             }
 304 
 305             // JMenuItems have a change listener added to them in
 306             // their "add" methods to enable accessibility support -
 307             // see the add method in JMenuItem for details. We cannot
 308             // instantiate this instance as it is a private inner class
 309             // and do not need to do this anyway since it will be created
 310             // and installed by the "add" method. Special case this for now,
 311             // ignoring all change listeners on JMenuItems.
 312             if (listenerType == javax.swing.event.ChangeListener.class &&
 313                 type == javax.swing.JMenuItem.class) {
 314                 continue;
 315             }
 316 
 317             EventListener[] oldL = new EventListener[0];
 318             EventListener[] newL = new EventListener[0];
 319             try {
 320                 Method m = d.getGetListenerMethod();
 321                 oldL = (EventListener[])MethodUtil.invoke(m, oldInstance, new Object[]{});
 322                 newL = (EventListener[])MethodUtil.invoke(m, newInstance, new Object[]{});
 323             }
 324             catch (Exception e2) {
 325                 try {
 326                     Method m = type.getMethod("getListeners", new Class<?>[]{Class.class});
 327                     oldL = (EventListener[])MethodUtil.invoke(m, oldInstance, new Object[]{listenerType});
 328                     newL = (EventListener[])MethodUtil.invoke(m, newInstance, new Object[]{listenerType});
 329                 }
 330                 catch (Exception e3) {
 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,
 400                               Object oldInstance, Object newInstance,
 401                               Encoder out)
 402     {
 403         // System.out.println("DefulatPD:initialize" + type);
 404         super.initialize(type, oldInstance, newInstance, out);
 405         if (oldInstance.getClass() == type) { // !type.isInterface()) {
 406             initBean(type, oldInstance, newInstance, out);
 407         }
 408     }
 409 
 410     private static PropertyDescriptor getPropertyDescriptor(Class<?> type, String property) {
 411         try {
 412             for (PropertyDescriptor pd : Introspector.getBeanInfo(type).getPropertyDescriptors()) {
 413                 if (property.equals(pd.getName()))
 414                     return pd;
 415             }
 416         } catch (IntrospectionException exception) {
 417         }
 418         return null;
 419     }
 420 }