blob: 9970d53dbba2bf37fa3b8993fd1aa4e0b6a7ff4b [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2000-2006 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 */
25package java.beans;
26
27import java.util.*;
28import java.lang.reflect.*;
29import 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
59public 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) throws IntrospectionException {
168 if (property == null) {
169 throw new IllegalArgumentException("Property name is null");
170 }
171 BeanInfo info = Introspector.getBeanInfo(type);
172 for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
173 if (property.equals(pd.getName())) {
174 Method method = pd.getReadMethod();
175 if (method != null) {
176 return method;
177 }
178 throw new IllegalStateException("Could not find getter for the property " + property);
179 }
180 }
181 throw new IllegalStateException("Could not find property by the name " + property);
182 }
183
184 // This is a workaround for a bug in the introspector.
185 // PropertyDescriptors are not shared amongst subclasses.
186 private boolean isTransient(Class type, PropertyDescriptor pd) {
187 if (type == null) {
188 return false;
189 }
190 // This code was mistakenly deleted - it may be fine and
191 // is more efficient than the code below. This should
192 // all disappear anyway when property descriptors are shared
193 // by the introspector.
194 /*
195 Method getter = pd.getReadMethod();
196 Class declaringClass = getter.getDeclaringClass();
197 if (declaringClass == type) {
198 return Boolean.TRUE.equals(pd.getValue("transient"));
199 }
200 */
201 String pName = pd.getName();
202 BeanInfo info = MetaData.getBeanInfo(type);
203 PropertyDescriptor[] propertyDescriptors = info.getPropertyDescriptors();
204 for (int i = 0; i < propertyDescriptors.length; ++i ) {
205 PropertyDescriptor pd2 = propertyDescriptors[i];
206 if (pName.equals(pd2.getName())) {
207 Object value = pd2.getValue("transient");
208 if (value != null) {
209 return Boolean.TRUE.equals(value);
210 }
211 }
212 }
213 return isTransient(type.getSuperclass(), pd);
214 }
215
216 private static boolean equals(Object o1, Object o2) {
217 return (o1 == null) ? (o2 == null) : o1.equals(o2);
218 }
219
220 private void doProperty(Class type, PropertyDescriptor pd, Object oldInstance, Object newInstance, Encoder out) throws Exception {
221 Method getter = pd.getReadMethod();
222 Method setter = pd.getWriteMethod();
223
224 if (getter != null && setter != null && !isTransient(type, pd)) {
225 Expression oldGetExp = new Expression(oldInstance, getter.getName(), new Object[]{});
226 Expression newGetExp = new Expression(newInstance, getter.getName(), new Object[]{});
227 Object oldValue = oldGetExp.getValue();
228 Object newValue = newGetExp.getValue();
229 out.writeExpression(oldGetExp);
230 if (!equals(newValue, out.get(oldValue))) {
231 // Search for a static constant with this value;
232 Object e = (Object[])pd.getValue("enumerationValues");
233 if (e instanceof Object[] && Array.getLength(e) % 3 == 0) {
234 Object[] a = (Object[])e;
235 for(int i = 0; i < a.length; i = i + 3) {
236 try {
237 Field f = type.getField((String)a[i]);
238 if (f.get(null).equals(oldValue)) {
239 out.remove(oldValue);
240 out.writeExpression(new Expression(oldValue, f, "get", new Object[]{null}));
241 }
242 }
243 catch (Exception ex) {}
244 }
245 }
246 invokeStatement(oldInstance, setter.getName(), new Object[]{oldValue}, out);
247 }
248 }
249 }
250
251 static void invokeStatement(Object instance, String methodName, Object[] args, Encoder out) {
252 out.writeStatement(new Statement(instance, methodName, args));
253 }
254
255 // Write out the properties of this instance.
256 private void initBean(Class type, Object oldInstance, Object newInstance, Encoder out) {
257 // System.out.println("initBean: " + oldInstance);
258 BeanInfo info = MetaData.getBeanInfo(type);
259
260 // Properties
261 PropertyDescriptor[] propertyDescriptors = info.getPropertyDescriptors();
262 for (int i = 0; i < propertyDescriptors.length; ++i ) {
263 try {
264 doProperty(type, propertyDescriptors[i], oldInstance, newInstance, out);
265 }
266 catch (Exception e) {
267 out.getExceptionListener().exceptionThrown(e);
268 }
269 }
270
271 // Listeners
272 /*
273 Pending(milne). There is a general problem with the archival of
274 listeners which is unresolved as of 1.4. Many of the methods
275 which install one object inside another (typically "add" methods
276 or setters) automatically install a listener on the "child" object
277 so that its "parent" may respond to changes that are made to it.
278 For example the JTable:setModel() method automatically adds a
279 TableModelListener (the JTable itself in this case) to the supplied
280 table model.
281
282 We do not need to explictly add these listeners to the model in an
283 archive as they will be added automatically by, in the above case,
284 the JTable's "setModel" method. In some cases, we must specifically
285 avoid trying to do this since the listener may be an inner class
286 that cannot be instantiated using public API.
287
288 No general mechanism currently
289 exists for differentiating between these kind of listeners and
290 those which were added explicitly by the user. A mechanism must
291 be created to provide a general means to differentiate these
292 special cases so as to provide reliable persistence of listeners
293 for the general case.
294 */
295 if (!java.awt.Component.class.isAssignableFrom(type)) {
296 return; // Just handle the listeners of Components for now.
297 }
298 EventSetDescriptor[] eventSetDescriptors = info.getEventSetDescriptors();
299 for (int e = 0; e < eventSetDescriptors.length; e++) {
300 EventSetDescriptor d = eventSetDescriptors[e];
301 Class listenerType = d.getListenerType();
302
303
304 // The ComponentListener is added automatically, when
305 // Contatiner:add is called on the parent.
306 if (listenerType == java.awt.event.ComponentListener.class) {
307 continue;
308 }
309
310 // JMenuItems have a change listener added to them in
311 // their "add" methods to enable accessibility support -
312 // see the add method in JMenuItem for details. We cannot
313 // instantiate this instance as it is a private inner class
314 // and do not need to do this anyway since it will be created
315 // and installed by the "add" method. Special case this for now,
316 // ignoring all change listeners on JMenuItems.
317 if (listenerType == javax.swing.event.ChangeListener.class &&
318 type == javax.swing.JMenuItem.class) {
319 continue;
320 }
321
322 EventListener[] oldL = new EventListener[0];
323 EventListener[] newL = new EventListener[0];
324 try {
325 Method m = d.getGetListenerMethod();
326 oldL = (EventListener[])MethodUtil.invoke(m, oldInstance, new Object[]{});
327 newL = (EventListener[])MethodUtil.invoke(m, newInstance, new Object[]{});
328 }
329 catch (Throwable e2) {
330 try {
331 Method m = type.getMethod("getListeners", new Class[]{Class.class});
332 oldL = (EventListener[])MethodUtil.invoke(m, oldInstance, new Object[]{listenerType});
333 newL = (EventListener[])MethodUtil.invoke(m, newInstance, new Object[]{listenerType});
334 }
335 catch (Exception e3) {
336 return;
337 }
338 }
339
340 // Asssume the listeners are in the same order and that there are no gaps.
341 // Eventually, this may need to do true differencing.
342 String addListenerMethodName = d.getAddListenerMethod().getName();
343 for (int i = newL.length; i < oldL.length; i++) {
344 // System.out.println("Adding listener: " + addListenerMethodName + oldL[i]);
345 invokeStatement(oldInstance, addListenerMethodName, new Object[]{oldL[i]}, out);
346 }
347
348 String removeListenerMethodName = d.getRemoveListenerMethod().getName();
349 for (int i = oldL.length; i < newL.length; i++) {
350 invokeStatement(oldInstance, removeListenerMethodName, new Object[]{newL[i]}, out);
351 }
352 }
353 }
354
355 /**
356 * This default implementation of the <code>initialize</code> method assumes
357 * all state held in objects of this type is exposed via the
358 * matching pairs of "setter" and "getter" methods in the order
359 * they are returned by the Introspector. If a property descriptor
360 * defines a "transient" attribute with a value equal to
361 * <code>Boolean.TRUE</code> the property is ignored by this
362 * default implementation. Note that this use of the word
363 * "transient" is quite independent of the field modifier
364 * that is used by the <code>ObjectOutputStream</code>.
365 * <p>
366 * For each non-transient property, an expression is created
367 * in which the nullary "getter" method is applied
368 * to the <code>oldInstance</code>. The value of this
369 * expression is the value of the property in the instance that is
370 * being serialized. If the value of this expression
371 * in the cloned environment <code>mutatesTo</code> the
372 * target value, the new value is initialized to make it
373 * equivalent to the old value. In this case, because
374 * the property value has not changed there is no need to
375 * call the corresponding "setter" method and no statement
376 * is emitted. If not however, the expression for this value
377 * is replaced with another expression (normally a constructor)
378 * and the corresponding "setter" method is called to install
379 * the new property value in the object. This scheme removes
380 * default information from the output produced by streams
381 * using this delegate.
382 * <p>
383 * In passing these statements to the output stream, where they
384 * will be executed, side effects are made to the <code>newInstance</code>.
385 * In most cases this allows the problem of properties
386 * whose values depend on each other to actually help the
387 * serialization process by making the number of statements
388 * that need to be written to the output smaller. In general,
389 * the problem of handling interdependent properties is reduced to
390 * that of finding an order for the properties in
391 * a class such that no property value depends on the value of
392 * a subsequent property.
393 *
394 * @param oldInstance The instance to be copied.
395 * @param newInstance The instance that is to be modified.
396 * @param out The stream to which any initialization statements should be written.
397 *
398 * @see java.beans.Introspector#getBeanInfo
399 * @see java.beans.PropertyDescriptor
400 */
401 protected void initialize(Class<?> type,
402 Object oldInstance, Object newInstance,
403 Encoder out)
404 {
405 // System.out.println("DefulatPD:initialize" + type);
406 super.initialize(type, oldInstance, newInstance, out);
407 if (oldInstance.getClass() == type) { // !type.isInterface()) {
408 initBean(type, oldInstance, newInstance, out);
409 }
410 }
411}