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

Save This Page
Home » openjdk-7 » java » beans » [javadoc | source]