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 */
25package java.beans;
26
27import java.util.*;
28import java.lang.reflect.*;
29import java.util.Objects;
30import 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
60public 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}
421