jdk/src/share/classes/java/beans/Encoder.java
changeset 2 90ce3da70b43
child 3239 f675984c2349
equal deleted inserted replaced
0:fd16c54261b3 2:90ce3da70b43
       
     1 /*
       
     2  * Copyright 2000-2007 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.Collections;
       
    28 import java.util.HashMap;
       
    29 import java.util.IdentityHashMap;
       
    30 import java.util.Map;
       
    31 
       
    32 /**
       
    33  * An <code>Encoder</code> is a class which can be used to create
       
    34  * files or streams that encode the state of a collection of
       
    35  * JavaBeans in terms of their public APIs. The <code>Encoder</code>,
       
    36  * in conjunction with its persistence delegates, is responsible for
       
    37  * breaking the object graph down into a series of <code>Statements</code>s
       
    38  * and <code>Expression</code>s which can be used to create it.
       
    39  * A subclass typically provides a syntax for these expressions
       
    40  * using some human readable form - like Java source code or XML.
       
    41  *
       
    42  * @since 1.4
       
    43  *
       
    44  * @author Philip Milne
       
    45  */
       
    46 
       
    47 public class Encoder {
       
    48     private final Map<Class<?>, PersistenceDelegate> delegates
       
    49             = Collections.synchronizedMap(new HashMap<Class<?>, PersistenceDelegate>());
       
    50     private Map bindings = new IdentityHashMap();
       
    51     private ExceptionListener exceptionListener;
       
    52     boolean executeStatements = true;
       
    53     private Map attributes;
       
    54 
       
    55     /**
       
    56      * Write the specified object to the output stream.
       
    57      * The serialized form will denote a series of
       
    58      * expressions, the combined effect of which will create
       
    59      * an equivalent object when the input stream is read.
       
    60      * By default, the object is assumed to be a <em>JavaBean</em>
       
    61      * with a nullary constructor, whose state is defined by
       
    62      * the matching pairs of "setter" and "getter" methods
       
    63      * returned by the Introspector.
       
    64      *
       
    65      * @param o The object to be written to the stream.
       
    66      *
       
    67      * @see XMLDecoder#readObject
       
    68      */
       
    69     protected void writeObject(Object o) {
       
    70         if (o == this) {
       
    71             return;
       
    72         }
       
    73         PersistenceDelegate info = getPersistenceDelegate(o == null ? null : o.getClass());
       
    74         info.writeObject(o, this);
       
    75     }
       
    76 
       
    77     /**
       
    78      * Sets the exception handler for this stream to <code>exceptionListener</code>.
       
    79      * The exception handler is notified when this stream catches recoverable
       
    80      * exceptions.
       
    81      *
       
    82      * @param exceptionListener The exception handler for this stream;
       
    83      *       if <code>null</code> the default exception listener will be used.
       
    84      *
       
    85      * @see #getExceptionListener
       
    86      */
       
    87     public void setExceptionListener(ExceptionListener exceptionListener) {
       
    88         this.exceptionListener = exceptionListener;
       
    89     }
       
    90 
       
    91     /**
       
    92      * Gets the exception handler for this stream.
       
    93      *
       
    94      * @return The exception handler for this stream;
       
    95      *    Will return the default exception listener if this has not explicitly been set.
       
    96      *
       
    97      * @see #setExceptionListener
       
    98      */
       
    99     public ExceptionListener getExceptionListener() {
       
   100         return (exceptionListener != null) ? exceptionListener : Statement.defaultExceptionListener;
       
   101     }
       
   102 
       
   103     Object getValue(Expression exp) {
       
   104         try {
       
   105             return (exp == null) ? null : exp.getValue();
       
   106         }
       
   107         catch (Exception e) {
       
   108             getExceptionListener().exceptionThrown(e);
       
   109             throw new RuntimeException("failed to evaluate: " + exp.toString());
       
   110         }
       
   111     }
       
   112 
       
   113     /**
       
   114      * Returns the persistence delegate for the given type.
       
   115      * The persistence delegate is calculated
       
   116      * by applying the following of rules in order:
       
   117      * <ul>
       
   118      * <li>
       
   119      * If the type is an array, an internal persistence
       
   120      * delegate is returned which will instantiate an
       
   121      * array of the appropriate type and length, initializing
       
   122      * each of its elements as if they are properties.
       
   123      * <li>
       
   124      * If the type is a proxy, an internal persistence
       
   125      * delegate is returned which will instantiate a
       
   126      * new proxy instance using the static
       
   127      * "newProxyInstance" method defined in the
       
   128      * Proxy class.
       
   129      * <li>
       
   130      * If the BeanInfo for this type has a <code>BeanDescriptor</code>
       
   131      * which defined a "persistenceDelegate" property, this
       
   132      * value is returned.
       
   133      * <li>
       
   134      * In all other cases the default persistence delegate
       
   135      * is returned. The default persistence delegate assumes
       
   136      * the type is a <em>JavaBean</em>, implying that it has a default constructor
       
   137      * and that its state may be characterized by the matching pairs
       
   138      * of "setter" and "getter" methods returned by the Introspector.
       
   139      * The default constructor is the constructor with the greatest number
       
   140      * of parameters that has the {@link ConstructorProperties} annotation.
       
   141      * If none of the constructors have the {@code ConstructorProperties} annotation,
       
   142      * then the nullary constructor (constructor with no parameters) will be used.
       
   143      * For example, in the following the nullary constructor
       
   144      * for {@code Foo} will be used, while the two parameter constructor
       
   145      * for {@code Bar} will be used.
       
   146      * <code>
       
   147      *   public class Foo {
       
   148      *     public Foo() { ... }
       
   149      *     public Foo(int x) { ... }
       
   150      *   }
       
   151      *   public class Bar {
       
   152      *     public Bar() { ... }
       
   153      *     &#64;ConstructorProperties({"x"})
       
   154      *     public Bar(int x) { ... }
       
   155      *     &#64;ConstructorProperties({"x", "y"})
       
   156      *     public Bar(int x, int y) { ... }
       
   157      *   }
       
   158      * </code>
       
   159      * </ul>
       
   160      *
       
   161      * @param  type The type of the object.
       
   162      * @return The persistence delegate for this type of object.
       
   163      *
       
   164      * @see #setPersistenceDelegate
       
   165      * @see java.beans.Introspector#getBeanInfo
       
   166      * @see java.beans.BeanInfo#getBeanDescriptor
       
   167      */
       
   168     public PersistenceDelegate getPersistenceDelegate(Class<?> type) {
       
   169         PersistenceDelegate pd = this.delegates.get(type);
       
   170         return (pd != null) ? pd : MetaData.getPersistenceDelegate(type);
       
   171     }
       
   172 
       
   173     /**
       
   174      * Sets the persistence delegate associated with this <code>type</code> to
       
   175      * <code>persistenceDelegate</code>.
       
   176      *
       
   177      * @param  type The class of objects that <code>persistenceDelegate</code> applies to.
       
   178      * @param  persistenceDelegate The persistence delegate for instances of <code>type</code>.
       
   179      *
       
   180      * @see #getPersistenceDelegate
       
   181      * @see java.beans.Introspector#getBeanInfo
       
   182      * @see java.beans.BeanInfo#getBeanDescriptor
       
   183      */
       
   184     public void setPersistenceDelegate(Class<?> type,
       
   185                                        PersistenceDelegate persistenceDelegate)
       
   186     {
       
   187         if (persistenceDelegate != null) {
       
   188             this.delegates.put(type, persistenceDelegate);
       
   189         } else {
       
   190             this.delegates.remove(type);
       
   191         }
       
   192     }
       
   193 
       
   194     /**
       
   195      * Removes the entry for this instance, returning the old entry.
       
   196      *
       
   197      * @param oldInstance The entry that should be removed.
       
   198      * @return The entry that was removed.
       
   199      *
       
   200      * @see #get
       
   201      */
       
   202     public Object remove(Object oldInstance) {
       
   203         Expression exp = (Expression)bindings.remove(oldInstance);
       
   204         return getValue(exp);
       
   205     }
       
   206 
       
   207     /**
       
   208      * Returns a tentative value for <code>oldInstance</code> in
       
   209      * the environment created by this stream. A persistence
       
   210      * delegate can use its <code>mutatesTo</code> method to
       
   211      * determine whether this value may be initialized to
       
   212      * form the equivalent object at the output or whether
       
   213      * a new object must be instantiated afresh. If the
       
   214      * stream has not yet seen this value, null is returned.
       
   215      *
       
   216      * @param  oldInstance The instance to be looked up.
       
   217      * @return The object, null if the object has not been seen before.
       
   218      */
       
   219     public Object get(Object oldInstance) {
       
   220         if (oldInstance == null || oldInstance == this ||
       
   221             oldInstance.getClass() == String.class) {
       
   222             return oldInstance;
       
   223         }
       
   224         Expression exp = (Expression)bindings.get(oldInstance);
       
   225         return getValue(exp);
       
   226     }
       
   227 
       
   228     private Object writeObject1(Object oldInstance) {
       
   229         Object o = get(oldInstance);
       
   230         if (o == null) {
       
   231             writeObject(oldInstance);
       
   232             o = get(oldInstance);
       
   233         }
       
   234         return o;
       
   235     }
       
   236 
       
   237     private Statement cloneStatement(Statement oldExp) {
       
   238         Object oldTarget = oldExp.getTarget();
       
   239         Object newTarget = writeObject1(oldTarget);
       
   240 
       
   241         Object[] oldArgs = oldExp.getArguments();
       
   242         Object[] newArgs = new Object[oldArgs.length];
       
   243         for (int i = 0; i < oldArgs.length; i++) {
       
   244             newArgs[i] = writeObject1(oldArgs[i]);
       
   245         }
       
   246         if (oldExp.getClass() == Statement.class) {
       
   247             return new Statement(newTarget, oldExp.getMethodName(), newArgs);
       
   248         }
       
   249         else {
       
   250             return new Expression(newTarget, oldExp.getMethodName(), newArgs);
       
   251         }
       
   252     }
       
   253 
       
   254     /**
       
   255      * Writes statement <code>oldStm</code> to the stream.
       
   256      * The <code>oldStm</code> should be written entirely
       
   257      * in terms of the callers environment, i.e. the
       
   258      * target and all arguments should be part of the
       
   259      * object graph being written. These expressions
       
   260      * represent a series of "what happened" expressions
       
   261      * which tell the output stream how to produce an
       
   262      * object graph like the original.
       
   263      * <p>
       
   264      * The implementation of this method will produce
       
   265      * a second expression to represent the same expression in
       
   266      * an environment that will exist when the stream is read.
       
   267      * This is achieved simply by calling <code>writeObject</code>
       
   268      * on the target and all the arguments and building a new
       
   269      * expression with the results.
       
   270      *
       
   271      * @param oldStm The expression to be written to the stream.
       
   272      */
       
   273     public void writeStatement(Statement oldStm) {
       
   274         // System.out.println("writeStatement: " + oldExp);
       
   275         Statement newStm = cloneStatement(oldStm);
       
   276         if (oldStm.getTarget() != this && executeStatements) {
       
   277             try {
       
   278                 newStm.execute();
       
   279             } catch (Exception e) {
       
   280                 getExceptionListener().exceptionThrown(new Exception("Encoder: discarding statement "
       
   281                                                                      + newStm, e));
       
   282             }
       
   283         }
       
   284     }
       
   285 
       
   286     /**
       
   287      * The implementation first checks to see if an
       
   288      * expression with this value has already been written.
       
   289      * If not, the expression is cloned, using
       
   290      * the same procedure as <code>writeStatement</code>,
       
   291      * and the value of this expression is reconciled
       
   292      * with the value of the cloned expression
       
   293      * by calling <code>writeObject</code>.
       
   294      *
       
   295      * @param oldExp The expression to be written to the stream.
       
   296      */
       
   297     public void writeExpression(Expression oldExp) {
       
   298         // System.out.println("Encoder::writeExpression: " + oldExp);
       
   299         Object oldValue = getValue(oldExp);
       
   300         if (get(oldValue) != null) {
       
   301             return;
       
   302         }
       
   303         bindings.put(oldValue, (Expression)cloneStatement(oldExp));
       
   304         writeObject(oldValue);
       
   305     }
       
   306 
       
   307     void clear() {
       
   308         bindings.clear();
       
   309     }
       
   310 
       
   311     // Package private method for setting an attributes table for the encoder
       
   312     void setAttribute(Object key, Object value) {
       
   313         if (attributes == null) {
       
   314             attributes = new HashMap();
       
   315         }
       
   316         attributes.put(key, value);
       
   317     }
       
   318 
       
   319     Object getAttribute(Object key) {
       
   320         if (attributes == null) {
       
   321             return null;
       
   322         }
       
   323         return attributes.get(key);
       
   324     }
       
   325 }