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