Path: blob/master/src/java.desktop/share/classes/java/beans/DefaultPersistenceDelegate.java
41152 views
/*1* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/24package java.beans;2526import java.util.*;27import java.lang.reflect.*;28import java.util.Objects;29import sun.reflect.misc.*;303132/**33* The {@code DefaultPersistenceDelegate} is a concrete implementation of34* the abstract {@code PersistenceDelegate} class and35* is the delegate used by default for classes about36* which no information is available. The {@code DefaultPersistenceDelegate}37* provides, version resilient, public API-based persistence for38* classes that follow the JavaBeans conventions without any class specific39* configuration.40* <p>41* The key assumptions are that the class has a nullary constructor42* and that its state is accurately represented by matching pairs43* of "setter" and "getter" methods in the order they are returned44* by the Introspector.45* In addition to providing code-free persistence for JavaBeans,46* the {@code DefaultPersistenceDelegate} provides a convenient means47* to effect persistent storage for classes that have a constructor48* that, while not nullary, simply requires some property values49* as arguments.50*51* @see #DefaultPersistenceDelegate(String[])52* @see java.beans.Introspector53*54* @since 1.455*56* @author Philip Milne57*/5859public class DefaultPersistenceDelegate extends PersistenceDelegate {60private static final String[] EMPTY = {};61private final String[] constructor;62private Boolean definesEquals;6364/**65* Creates a persistence delegate for a class with a nullary constructor.66*67* @see #DefaultPersistenceDelegate(java.lang.String[])68*/69public DefaultPersistenceDelegate() {70this.constructor = EMPTY;71}7273/**74* Creates a default persistence delegate for a class with a75* constructor whose arguments are the values of the property76* names as specified by {@code constructorPropertyNames}.77* The constructor arguments are created by78* evaluating the property names in the order they are supplied.79* To use this class to specify a single preferred constructor for use80* in the serialization of a particular type, we state the81* names of the properties that make up the constructor's82* arguments. For example, the {@code Font} class which83* does not define a nullary constructor can be handled84* 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 #instantiate93*/94public DefaultPersistenceDelegate(String[] constructorPropertyNames) {95this.constructor = (constructorPropertyNames == null) ? EMPTY : constructorPropertyNames.clone();96}9798private static boolean definesEquals(Class<?> type) {99try {100return type == type.getMethod("equals", Object.class).getDeclaringClass();101}102catch(NoSuchMethodException e) {103return false;104}105}106107private boolean definesEquals(Object instance) {108if (definesEquals != null) {109return (definesEquals == Boolean.TRUE);110}111else {112boolean result = definesEquals(instance.getClass());113definesEquals = result ? Boolean.TRUE : Boolean.FALSE;114return result;115}116}117118/**119* If the number of arguments in the specified constructor is non-zero and120* the class of {@code oldInstance} explicitly declares an "equals" method121* this method returns the value of {@code oldInstance.equals(newInstance)}.122* Otherwise, this method uses the superclass's definition which returns true if the123* 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} may be128* created by applying a series of mutations to {@code oldInstance}.129*130* @see #DefaultPersistenceDelegate(String[])131*/132protected boolean mutatesTo(Object oldInstance, Object newInstance) {133// Assume the instance is either mutable or a singleton134// if it has a nullary constructor.135return (constructor.length == 0) || !definesEquals(oldInstance) ?136super.mutatesTo(oldInstance, newInstance) :137oldInstance.equals(newInstance);138}139140/**141* This default implementation of the {@code instantiate} method returns142* an expression containing the predefined method name "new" which denotes a143* call to a constructor with the arguments as specified in144* the {@code DefaultPersistenceDelegate}'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}.149*150* @throws NullPointerException if {@code out} is {@code null}151* and this value is used in the method152*153* @see #DefaultPersistenceDelegate(String[])154*/155protected Expression instantiate(Object oldInstance, Encoder out) {156int nArgs = constructor.length;157Class<?> type = oldInstance.getClass();158Object[] constructorArgs = new Object[nArgs];159for(int i = 0; i < nArgs; i++) {160try {161Method method = findMethod(type, this.constructor[i]);162constructorArgs[i] = MethodUtil.invoke(method, oldInstance, new Object[0]);163}164catch (Exception e) {165out.getExceptionListener().exceptionThrown(e);166}167}168return new Expression(oldInstance, oldInstance.getClass(), "new", constructorArgs);169}170171private Method findMethod(Class<?> type, String property) {172if (property == null) {173throw new IllegalArgumentException("Property name is null");174}175PropertyDescriptor pd = getPropertyDescriptor(type, property);176if (pd == null) {177throw new IllegalStateException("Could not find property by the name " + property);178}179Method method = pd.getReadMethod();180if (method == null) {181throw new IllegalStateException("Could not find getter for the property " + property);182}183return method;184}185186private void doProperty(Class<?> type, PropertyDescriptor pd, Object oldInstance, Object newInstance, Encoder out) throws Exception {187Method getter = pd.getReadMethod();188Method setter = pd.getWriteMethod();189190if (getter != null && setter != null) {191Expression oldGetExp = new Expression(oldInstance, getter.getName(), new Object[]{});192Expression newGetExp = new Expression(newInstance, getter.getName(), new Object[]{});193Object oldValue = oldGetExp.getValue();194Object newValue = newGetExp.getValue();195out.writeExpression(oldGetExp);196if (!Objects.equals(newValue, out.get(oldValue))) {197// Search for a static constant with this value;198Object e = (Object[])pd.getValue("enumerationValues");199if (e instanceof Object[] && Array.getLength(e) % 3 == 0) {200Object[] a = (Object[])e;201for(int i = 0; i < a.length; i = i + 3) {202try {203Field f = type.getField((String)a[i]);204if (f.get(null).equals(oldValue)) {205out.remove(oldValue);206out.writeExpression(new Expression(oldValue, f, "get", new Object[]{null}));207}208}209catch (Exception ex) {}210}211}212invokeStatement(oldInstance, setter.getName(), new Object[]{oldValue}, out);213}214}215}216217static void invokeStatement(Object instance, String methodName, Object[] args, Encoder out) {218out.writeStatement(new Statement(instance, methodName, args));219}220221// Write out the properties of this instance.222private void initBean(Class<?> type, Object oldInstance, Object newInstance, Encoder out) {223for (Field field : type.getFields()) {224if (!ReflectUtil.isPackageAccessible(field.getDeclaringClass())) {225continue;226}227int mod = field.getModifiers();228if (Modifier.isFinal(mod) || Modifier.isStatic(mod) || Modifier.isTransient(mod)) {229continue;230}231try {232Expression oldGetExp = new Expression(field, "get", new Object[] { oldInstance });233Expression newGetExp = new Expression(field, "get", new Object[] { newInstance });234Object oldValue = oldGetExp.getValue();235Object newValue = newGetExp.getValue();236out.writeExpression(oldGetExp);237if (!Objects.equals(newValue, out.get(oldValue))) {238out.writeStatement(new Statement(field, "set", new Object[] { oldInstance, oldValue }));239}240}241catch (Exception exception) {242out.getExceptionListener().exceptionThrown(exception);243}244}245BeanInfo info;246try {247info = Introspector.getBeanInfo(type);248} catch (IntrospectionException exception) {249return;250}251// Properties252for (PropertyDescriptor d : info.getPropertyDescriptors()) {253if (d.isTransient()) {254continue;255}256try {257doProperty(type, d, oldInstance, newInstance, out);258}259catch (Exception e) {260out.getExceptionListener().exceptionThrown(e);261}262}263264// Listeners265/*266Pending(milne). There is a general problem with the archival of267listeners which is unresolved as of 1.4. Many of the methods268which install one object inside another (typically "add" methods269or setters) automatically install a listener on the "child" object270so that its "parent" may respond to changes that are made to it.271For example the JTable:setModel() method automatically adds a272TableModelListener (the JTable itself in this case) to the supplied273table model.274275We do not need to explicitly add these listeners to the model in an276archive as they will be added automatically by, in the above case,277the JTable's "setModel" method. In some cases, we must specifically278avoid trying to do this since the listener may be an inner class279that cannot be instantiated using public API.280281No general mechanism currently282exists for differentiating between these kind of listeners and283those which were added explicitly by the user. A mechanism must284be created to provide a general means to differentiate these285special cases so as to provide reliable persistence of listeners286for the general case.287*/288if (!java.awt.Component.class.isAssignableFrom(type)) {289return; // Just handle the listeners of Components for now.290}291for (EventSetDescriptor d : info.getEventSetDescriptors()) {292if (d.isTransient()) {293continue;294}295Class<?> listenerType = d.getListenerType();296297298// The ComponentListener is added automatically, when299// Contatiner:add is called on the parent.300if (listenerType == java.awt.event.ComponentListener.class) {301continue;302}303304// JMenuItems have a change listener added to them in305// their "add" methods to enable accessibility support -306// see the add method in JMenuItem for details. We cannot307// instantiate this instance as it is a private inner class308// and do not need to do this anyway since it will be created309// and installed by the "add" method. Special case this for now,310// ignoring all change listeners on JMenuItems.311if (listenerType == javax.swing.event.ChangeListener.class &&312type == javax.swing.JMenuItem.class) {313continue;314}315316EventListener[] oldL = new EventListener[0];317EventListener[] newL = new EventListener[0];318try {319Method m = d.getGetListenerMethod();320oldL = (EventListener[])MethodUtil.invoke(m, oldInstance, new Object[]{});321newL = (EventListener[])MethodUtil.invoke(m, newInstance, new Object[]{});322}323catch (Exception e2) {324try {325Method m = type.getMethod("getListeners", new Class<?>[]{Class.class});326oldL = (EventListener[])MethodUtil.invoke(m, oldInstance, new Object[]{listenerType});327newL = (EventListener[])MethodUtil.invoke(m, newInstance, new Object[]{listenerType});328}329catch (Exception e3) {330return;331}332}333334// Asssume the listeners are in the same order and that there are no gaps.335// Eventually, this may need to do true differencing.336String addListenerMethodName = d.getAddListenerMethod().getName();337for (int i = newL.length; i < oldL.length; i++) {338// System.out.println("Adding listener: " + addListenerMethodName + oldL[i]);339invokeStatement(oldInstance, addListenerMethodName, new Object[]{oldL[i]}, out);340}341342String removeListenerMethodName = d.getRemoveListenerMethod().getName();343for (int i = oldL.length; i < newL.length; i++) {344invokeStatement(oldInstance, removeListenerMethodName, new Object[]{newL[i]}, out);345}346}347}348349/**350* This default implementation of the {@code initialize} method assumes351* all state held in objects of this type is exposed via the352* matching pairs of "setter" and "getter" methods in the order353* they are returned by the Introspector. If a property descriptor354* defines a "transient" attribute with a value equal to355* {@code Boolean.TRUE} the property is ignored by this356* default implementation. Note that this use of the word357* "transient" is quite independent of the field modifier358* that is used by the {@code ObjectOutputStream}.359* <p>360* For each non-transient property, an expression is created361* in which the nullary "getter" method is applied362* to the {@code oldInstance}. The value of this363* expression is the value of the property in the instance that is364* being serialized. If the value of this expression365* in the cloned environment {@code mutatesTo} the366* target value, the new value is initialized to make it367* equivalent to the old value. In this case, because368* the property value has not changed there is no need to369* call the corresponding "setter" method and no statement370* is emitted. If not however, the expression for this value371* is replaced with another expression (normally a constructor)372* and the corresponding "setter" method is called to install373* the new property value in the object. This scheme removes374* default information from the output produced by streams375* using this delegate.376* <p>377* In passing these statements to the output stream, where they378* will be executed, side effects are made to the {@code newInstance}.379* In most cases this allows the problem of properties380* whose values depend on each other to actually help the381* serialization process by making the number of statements382* that need to be written to the output smaller. In general,383* the problem of handling interdependent properties is reduced to384* that of finding an order for the properties in385* a class such that no property value depends on the value of386* a subsequent property.387*388* @param type the type of the instances389* @param oldInstance The instance to be copied.390* @param newInstance The instance that is to be modified.391* @param out The stream to which any initialization statements should be written.392*393* @throws NullPointerException if {@code out} is {@code null}394*395* @see java.beans.Introspector#getBeanInfo396* @see java.beans.PropertyDescriptor397*/398protected void initialize(Class<?> type,399Object oldInstance, Object newInstance,400Encoder out)401{402// System.out.println("DefulatPD:initialize" + type);403super.initialize(type, oldInstance, newInstance, out);404if (oldInstance.getClass() == type) { // !type.isInterface()) {405initBean(type, oldInstance, newInstance, out);406}407}408409private static PropertyDescriptor getPropertyDescriptor(Class<?> type, String property) {410try {411for (PropertyDescriptor pd : Introspector.getBeanInfo(type).getPropertyDescriptors()) {412if (property.equals(pd.getName()))413return pd;414}415} catch (IntrospectionException exception) {416}417return null;418}419}420421422