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