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