KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > java > beans > DefaultPersistenceDelegate


1 /*
2  * @(#)DefaultPersistenceDelegate.java 1.18 05/08/26
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7 package java.beans;
8
9 import java.util.*;
10 import java.lang.reflect.*;
11 import java.beans.*;
12 import java.io.*;
13 import sun.reflect.misc.*;
14
15
16 /**
17  * The <code>DefaultPersistenceDelegate</code> is a concrete implementation of
18  * the abstract <code>PersistenceDelegate</code> class and
19  * is the delegate used by default for classes about
20  * which no information is available. The <code>DefaultPersistenceDelegate</code>
21  * provides, version resilient, public API-based persistence for
22  * classes that follow the JavaBeans conventions without any class specific
23  * configuration.
24  * <p>
25  * The key assumptions are that the class has a nullary constructor
26  * and that its state is accurately represented by matching pairs
27  * of "setter" and "getter" methods in the order they are returned
28  * by the Introspector.
29  * In addition to providing code-free persistence for JavaBeans,
30  * the <code>DefaultPersistenceDelegate</code> provides a convenient means
31  * to effect persistent storage for classes that have a constructor
32  * that, while not nullary, simply requires some property values
33  * as arguments.
34  *
35  * @see #DefaultPersistenceDelegate(String[])
36  * @see java.beans.Introspector
37  *
38  * @since 1.4
39  *
40  * @version 1.18 08/26/05
41  * @author Philip Milne
42  */

43
44 public class DefaultPersistenceDelegate extends PersistenceDelegate JavaDoc {
45     private String JavaDoc[] constructor;
46     private Boolean JavaDoc definesEquals;
47
48     /**
49      * Creates a persistence delegate for a class with a nullary constructor.
50      *
51      * @see #DefaultPersistenceDelegate(java.lang.String[])
52      */

53     public DefaultPersistenceDelegate() {
54         this(new String JavaDoc[0]);
55     }
56
57     /**
58      * Creates a default persistence delegate for a class with a
59      * constructor whose arguments are the values of the property
60      * names as specified by <code>constructorPropertyNames</code>.
61      * The constructor arguments are created by
62      * evaluating the property names in the order they are supplied.
63      * To use this class to specify a single preferred constructor for use
64      * in the serialization of a particular type, we state the
65      * names of the properties that make up the constructor's
66      * arguments. For example, the <code>Font</code> class which
67      * does not define a nullary constructor can be handled
68      * with the following persistence delegate:
69      *
70      * <pre>
71      * new DefaultPersistenceDelegate(new String[]{"name", "style", "size"});
72      * </pre>
73      *
74      * @param constructorPropertyNames The property names for the arguments of this constructor.
75      *
76      * @see #instantiate
77      */

78     public DefaultPersistenceDelegate(String JavaDoc[] constructorPropertyNames) {
79         this.constructor = constructorPropertyNames;
80     }
81
82     private static boolean definesEquals(Class JavaDoc type) {
83         try {
84             type.getDeclaredMethod("equals", new Class JavaDoc[]{Object JavaDoc.class});
85             return true;
86         }
87         catch(NoSuchMethodException JavaDoc e) {
88             return false;
89         }
90     }
91
92     private boolean definesEquals(Object JavaDoc instance) {
93         if (definesEquals != null) {
94             return (definesEquals == Boolean.TRUE);
95         }
96         else {
97             boolean result = definesEquals(instance.getClass());
98             definesEquals = result ? Boolean.TRUE : Boolean.FALSE;
99             return result;
100         }
101     }
102
103     /**
104      * If the number of arguments in the specified constructor is non-zero and
105      * the class of <code>oldInstance</code> explicitly declares an "equals" method
106      * this method returns the value of <code>oldInstance.equals(newInstance)</code>.
107      * Otherwise, this method uses the superclass's definition which returns true if the
108      * classes of the two instances are equal.
109      *
110      * @param oldInstance The instance to be copied.
111      * @param newInstance The instance that is to be modified.
112      * @return True if an equivalent copy of <code>newInstance</code> may be
113      * created by applying a series of mutations to <code>oldInstance</code>.
114      *
115      * @see #DefaultPersistenceDelegate(String[])
116      */

117     protected boolean mutatesTo(Object JavaDoc oldInstance, Object JavaDoc newInstance) {
118         // Assume the instance is either mutable or a singleton
119
// if it has a nullary constructor.
120
return (constructor.length == 0) || !definesEquals(oldInstance) ?
121             super.mutatesTo(oldInstance, newInstance) :
122             oldInstance.equals(newInstance);
123     }
124
125     /**
126      * This default implementation of the <code>instantiate</code> method returns
127      * an expression containing the predefined method name "new" which denotes a
128      * call to a constructor with the arguments as specified in
129      * the <code>DefaultPersistenceDelegate</code>'s constructor.
130      *
131      * @param oldInstance The instance to be instantiated.
132      * @param out The code output stream.
133      * @return An expression whose value is <code>oldInstance</code>.
134      *
135      * @see #DefaultPersistenceDelegate(String[])
136      */

137     protected Expression JavaDoc instantiate(Object JavaDoc oldInstance, Encoder JavaDoc out) {
138         int nArgs = constructor.length;
139         Class JavaDoc type = oldInstance.getClass();
140         // System.out.println("writeObject: " + oldInstance);
141
Object JavaDoc[] constructorArgs = new Object JavaDoc[nArgs];
142         for(int i = 0; i < nArgs; i++) {
143             /*
144             1.2 introduces "public double getX()" et al. which return values
145             which cannot be used in the constructors (they are the wrong type).
146             In constructors, use public fields in preference to getters
147             when they are defined.
148             */

149             String JavaDoc name = constructor[i];
150
151             Field f = null;
152             try {
153                 // System.out.println("Trying field " + name + " in " + type);
154
f = type.getDeclaredField(name);
155                 f.setAccessible(true);
156             }
157             catch (NoSuchFieldException JavaDoc e) {}
158             try {
159                 constructorArgs[i] = (f != null && !Modifier.isStatic(f.getModifiers())) ?
160                     f.get(oldInstance) :
161                     MethodUtil.invoke(ReflectionUtils.getPublicMethod(type, "get" + NameGenerator.capitalize(name),
162                    new Class JavaDoc[0]), oldInstance, new Object JavaDoc[0]);
163             }
164             catch (Exception JavaDoc e) {
165                 out.getExceptionListener().exceptionThrown(e);
166             }
167         }
168         return new Expression JavaDoc(oldInstance, oldInstance.getClass(), "new", constructorArgs);
169     }
170
171     // This is a workaround for a bug in the introspector.
172
// PropertyDescriptors are not shared amongst subclasses.
173
private boolean isTransient(Class JavaDoc type, PropertyDescriptor JavaDoc pd) {
174         if (type == null) {
175             return false;
176         }
177         // This code was mistakenly deleted - it may be fine and
178
// is more efficient than the code below. This should
179
// all disappear anyway when property descriptors are shared
180
// by the introspector.
181
/*
182         Method getter = pd.getReadMethod();
183         Class declaringClass = getter.getDeclaringClass();
184         if (declaringClass == type) {
185             return Boolean.TRUE.equals(pd.getValue("transient"));
186         }
187         */

188         String JavaDoc pName = pd.getName();
189         BeanInfo JavaDoc info = MetaData.getBeanInfo(type);
190         PropertyDescriptor JavaDoc[] propertyDescriptors = info.getPropertyDescriptors();
191         for (int i = 0; i < propertyDescriptors.length; ++i ) {
192             PropertyDescriptor JavaDoc pd2 = propertyDescriptors[i];
193             if (pName.equals(pd2.getName())) {
194                 Object JavaDoc value = pd2.getValue("transient");
195                 if (value != null) {
196                     return Boolean.TRUE.equals(value);
197                 }
198             }
199         }
200         return isTransient(type.getSuperclass(), pd);
201     }
202
203     private static boolean equals(Object JavaDoc o1, Object JavaDoc o2) {
204         return (o1 == null) ? (o2 == null) : o1.equals(o2);
205     }
206
207     private void doProperty(Class JavaDoc type, PropertyDescriptor JavaDoc pd, Object JavaDoc oldInstance, Object JavaDoc newInstance, Encoder JavaDoc out) throws Exception JavaDoc {
208         Method getter = pd.getReadMethod();
209         Method setter = pd.getWriteMethod();
210
211         if (getter != null && setter != null && !isTransient(type, pd)) {
212             Expression JavaDoc oldGetExp = new Expression JavaDoc(oldInstance, getter.getName(), new Object JavaDoc[]{});
213             Expression JavaDoc newGetExp = new Expression JavaDoc(newInstance, getter.getName(), new Object JavaDoc[]{});
214             Object JavaDoc oldValue = oldGetExp.getValue();
215             Object JavaDoc newValue = newGetExp.getValue();
216             out.writeExpression(oldGetExp);
217             if (!equals(newValue, out.get(oldValue))) {
218                 // Search for a static constant with this value;
219
Object JavaDoc e = (Object JavaDoc[])pd.getValue("enumerationValues");
220                 if (e instanceof Object JavaDoc[] && Array.getLength(e) % 3 == 0) {
221                     Object JavaDoc[] a = (Object JavaDoc[])e;
222                     for(int i = 0; i < a.length; i = i + 3) {
223                         try {
224                            Field f = type.getField((String JavaDoc)a[i]);
225                            if (f.get(null).equals(oldValue)) {
226                                out.remove(oldValue);
227                                out.writeExpression(new Expression JavaDoc(oldValue, f, "get", new Object JavaDoc[]{null}));
228                            }
229                         }
230                         catch (Exception JavaDoc ex) {}
231                     }
232                 }
233                 invokeStatement(oldInstance, setter.getName(), new Object JavaDoc[]{oldValue}, out);
234             }
235         }
236     }
237
238     static void invokeStatement(Object JavaDoc instance, String JavaDoc methodName, Object JavaDoc[] args, Encoder JavaDoc out) {
239         out.writeStatement(new Statement JavaDoc(instance, methodName, args));
240     }
241
242     // Write out the properties of this instance.
243
private void initBean(Class JavaDoc type, Object JavaDoc oldInstance, Object JavaDoc newInstance, Encoder JavaDoc out) {
244         // System.out.println("initBean: " + oldInstance);
245
BeanInfo JavaDoc info = MetaData.getBeanInfo(type);
246
247         // Properties
248
PropertyDescriptor JavaDoc[] propertyDescriptors = info.getPropertyDescriptors();
249         for (int i = 0; i < propertyDescriptors.length; ++i ) {
250             try {
251                 doProperty(type, propertyDescriptors[i], oldInstance, newInstance, out);
252             }
253             catch (Exception JavaDoc e) {
254                 out.getExceptionListener().exceptionThrown(e);
255             }
256         }
257
258         // Listeners
259
/*
260         Pending(milne). There is a general problem with the archival of
261         listeners which is unresolved as of 1.4. Many of the methods
262         which install one object inside another (typically "add" methods
263         or setters) automatically install a listener on the "child" object
264         so that its "parent" may respond to changes that are made to it.
265         For example the JTable:setModel() method automatically adds a
266         TableModelListener (the JTable itself in this case) to the supplied
267         table model.
268
269         We do not need to explictly add these listeners to the model in an
270         archive as they will be added automatically by, in the above case,
271         the JTable's "setModel" method. In some cases, we must specifically
272         avoid trying to do this since the listener may be an inner class
273     that cannot be instantiated using public API.
274     
275     No general mechanism currently
276         exists for differentiating between these kind of listeners and
277         those which were added explicitly by the user. A mechanism must
278         be created to provide a general means to differentiate these
279         special cases so as to provide reliable persistence of listeners
280         for the general case.
281         */

282         if (!java.awt.Component JavaDoc.class.isAssignableFrom(type)) {
283             return; // Just handle the listeners of Components for now.
284
}
285         EventSetDescriptor JavaDoc[] eventSetDescriptors = info.getEventSetDescriptors();
286         for (int e = 0; e < eventSetDescriptors.length; e++) {
287             EventSetDescriptor JavaDoc d = eventSetDescriptors[e];
288             Class JavaDoc listenerType = d.getListenerType();
289
290
291             // The ComponentListener is added automatically, when
292
// Contatiner:add is called on the parent.
293
if (listenerType == java.awt.event.ComponentListener JavaDoc.class) {
294                 continue;
295             }
296
297             // JMenuItems have a change listener added to them in
298
// their "add" methods to enable accessibility support -
299
// see the add method in JMenuItem for details. We cannot
300
// instantiate this instance as it is a private inner class
301
// and do not need to do this anyway since it will be created
302
// and installed by the "add" method. Special case this for now,
303
// ignoring all change listeners on JMenuItems.
304
if (listenerType == javax.swing.event.ChangeListener JavaDoc.class &&
305                 type == javax.swing.JMenuItem JavaDoc.class) {
306                 continue;
307             }
308
309             EventListener[] oldL = new EventListener[0];
310             EventListener[] newL = new EventListener[0];
311             try {
312                 Method m = d.getGetListenerMethod();
313                 oldL = (EventListener[])MethodUtil.invoke(m, oldInstance, new Object JavaDoc[]{});
314                 newL = (EventListener[])MethodUtil.invoke(m, newInstance, new Object JavaDoc[]{});
315             }
316             catch (Throwable JavaDoc e2) {
317                 try {
318                     Method m = type.getMethod("getListeners", new Class JavaDoc[]{Class JavaDoc.class});
319                     oldL = (EventListener[])MethodUtil.invoke(m, oldInstance, new Object JavaDoc[]{listenerType});
320                     newL = (EventListener[])MethodUtil.invoke(m, newInstance, new Object JavaDoc[]{listenerType});
321                 }
322                 catch (Exception JavaDoc e3) {
323                     return;
324                 }
325             }
326
327             // Asssume the listeners are in the same order and that there are no gaps.
328
// Eventually, this may need to do true differencing.
329
String JavaDoc addListenerMethodName = d.getAddListenerMethod().getName();
330             for (int i = newL.length; i < oldL.length; i++) {
331                 // System.out.println("Adding listener: " + addListenerMethodName + oldL[i]);
332
invokeStatement(oldInstance, addListenerMethodName, new Object JavaDoc[]{oldL[i]}, out);
333             }
334
335             String JavaDoc removeListenerMethodName = d.getRemoveListenerMethod().getName();
336             for (int i = oldL.length; i < newL.length; i++) {
337                 invokeStatement(oldInstance, removeListenerMethodName, new Object JavaDoc[]{oldL[i]}, out);
338             }
339         }
340     }
341
342     /**
343      * This default implementation of the <code>initialize</code> method assumes
344      * all state held in objects of this type is exposed via the
345      * matching pairs of "setter" and "getter" methods in the order
346      * they are returned by the Introspector. If a property descriptor
347      * defines a "transient" attribute with a value equal to
348      * <code>Boolean.TRUE</code> the property is ignored by this
349      * default implementation. Note that this use of the word
350      * "transient" is quite independent of the field modifier
351      * that is used by the <code>ObjectOutputStream</code>.
352      * <p>
353      * For each non-transient property, an expression is created
354      * in which the nullary "getter" method is applied
355      * to the <code>oldInstance</code>. The value of this
356      * expression is the value of the property in the instance that is
357      * being serialized. If the value of this expression
358      * in the cloned environment <code>mutatesTo</code> the
359      * target value, the new value is initialized to make it
360      * equivalent to the old value. In this case, because
361      * the property value has not changed there is no need to
362      * call the corresponding "setter" method and no statement
363      * is emitted. If not however, the expression for this value
364      * is replaced with another expression (normally a constructor)
365      * and the corresponding "setter" method is called to install
366      * the new property value in the object. This scheme removes
367      * default information from the output produced by streams
368      * using this delegate.
369      * <p>
370      * In passing these statements to the output stream, where they
371      * will be executed, side effects are made to the <code>newInstance</code>.
372      * In most cases this allows the problem of properties
373      * whose values depend on each other to actually help the
374      * serialization process by making the number of statements
375      * that need to be written to the output smaller. In general,
376      * the problem of handling interdependent properties is reduced to
377      * that of finding an order for the properties in
378      * a class such that no property value depends on the value of
379      * a subsequent property.
380      *
381      * @param oldInstance The instance to be copied.
382      * @param newInstance The instance that is to be modified.
383      * @param out The stream to which any initialization statements should be written.
384      *
385      * @see java.beans.Introspector#getBeanInfo
386      * @see java.beans.PropertyDescriptor
387      */

388     protected void initialize(Class JavaDoc<?> type,
389                   Object JavaDoc oldInstance, Object JavaDoc newInstance,
390                   Encoder JavaDoc out)
391     {
392         // System.out.println("DefulatPD:initialize" + type);
393
super.initialize(type, oldInstance, newInstance, out);
394         if (oldInstance.getClass() == type) { // !type.isInterface()) {
395
initBean(type, oldInstance, newInstance, out);
396         }
397     }
398 }
399
Popular Tags