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