/* | 
|
 * Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved. | 
|
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | 
|
 * | 
|
 * This code is free software; you can redistribute it and/or modify it | 
|
 * under the terms of the GNU General Public License version 2 only, as | 
|
 * published by the Free Software Foundation.  Oracle designates this | 
|
 * particular file as subject to the "Classpath" exception as provided | 
|
 * by Oracle in the LICENSE file that accompanied this code. | 
|
 * | 
|
 * This code is distributed in the hope that it will be useful, but WITHOUT | 
|
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | 
|
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License | 
|
 * version 2 for more details (a copy is included in the LICENSE file that | 
|
 * accompanied this code). | 
|
 * | 
|
 * You should have received a copy of the GNU General Public License version | 
|
 * 2 along with this work; if not, write to the Free Software Foundation, | 
|
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | 
|
 * | 
|
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | 
|
 * or visit www.oracle.com if you need additional information or have any | 
|
 * questions. | 
|
*/  | 
|
package java.beans;  | 
|
import com.sun.beans.finder.PersistenceDelegateFinder;  | 
|
import java.util.HashMap;  | 
|
import java.util.IdentityHashMap;  | 
|
import java.util.Map;  | 
|
/**  | 
|
* An <code>Encoder</code> is a class which can be used to create  | 
|
* files or streams that encode the state of a collection of  | 
|
* JavaBeans in terms of their public APIs. The <code>Encoder</code>,  | 
|
* in conjunction with its persistence delegates, is responsible for  | 
|
* breaking the object graph down into a series of <code>Statements</code>s  | 
|
* and <code>Expression</code>s which can be used to create it.  | 
|
* A subclass typically provides a syntax for these expressions  | 
|
* using some human readable form - like Java source code or XML.  | 
|
*  | 
|
* @since 1.4  | 
|
*  | 
|
* @author Philip Milne  | 
|
*/  | 
|
public class Encoder { | 
|
private final PersistenceDelegateFinder finder = new PersistenceDelegateFinder();  | 
|
private Map<Object, Expression> bindings = new IdentityHashMap<>();  | 
|
private ExceptionListener exceptionListener;  | 
|
boolean executeStatements = true;  | 
|
private Map<Object, Object> attributes;  | 
|
    /** | 
|
     * Write the specified object to the output stream. | 
|
     * The serialized form will denote a series of | 
|
     * expressions, the combined effect of which will create | 
|
     * an equivalent object when the input stream is read. | 
|
     * By default, the object is assumed to be a <em>JavaBean</em> | 
|
     * with a nullary constructor, whose state is defined by | 
|
     * the matching pairs of "setter" and "getter" methods | 
|
     * returned by the Introspector. | 
|
     * | 
|
     * @param o The object to be written to the stream. | 
|
     * | 
|
     * @see XMLDecoder#readObject | 
|
*/  | 
|
protected void writeObject(Object o) {  | 
|
if (o == this) {  | 
|
return;  | 
|
}  | 
|
PersistenceDelegate info = getPersistenceDelegate(o == null ? null : o.getClass());  | 
|
info.writeObject(o, this);  | 
|
}  | 
|
    /** | 
|
     * Sets the exception handler for this stream to <code>exceptionListener</code>. | 
|
     * The exception handler is notified when this stream catches recoverable | 
|
     * exceptions. | 
|
     * | 
|
     * @param exceptionListener The exception handler for this stream; | 
|
     *       if <code>null</code> the default exception listener will be used. | 
|
     * | 
|
     * @see #getExceptionListener | 
|
*/  | 
|
public void setExceptionListener(ExceptionListener exceptionListener) {  | 
|
this.exceptionListener = exceptionListener;  | 
|
}  | 
|
    /** | 
|
     * Gets the exception handler for this stream. | 
|
     * | 
|
     * @return The exception handler for this stream; | 
|
     *    Will return the default exception listener if this has not explicitly been set. | 
|
     * | 
|
     * @see #setExceptionListener | 
|
*/  | 
|
public ExceptionListener getExceptionListener() {  | 
|
return (exceptionListener != null) ? exceptionListener : Statement.defaultExceptionListener;  | 
|
}  | 
|
Object getValue(Expression exp) {  | 
|
        try { | 
|
return (exp == null) ? null : exp.getValue();  | 
|
}  | 
|
catch (Exception e) {  | 
|
getExceptionListener().exceptionThrown(e);  | 
|
throw new RuntimeException("failed to evaluate: " + exp.toString());  | 
|
}  | 
|
}  | 
|
    /** | 
|
     * Returns the persistence delegate for the given type. | 
|
     * The persistence delegate is calculated by applying | 
|
     * the following rules in order: | 
|
     * <ol> | 
|
     * <li> | 
|
     * If a persistence delegate is associated with the given type | 
|
     * by using the {@link #setPersistenceDelegate} method | 
|
     * it is returned. | 
|
     * <li> | 
|
     * A persistence delegate is then looked up by the name | 
|
     * composed of the the fully qualified name of the given type | 
|
     * and the "PersistenceDelegate" postfix. | 
|
     * For example, a persistence delegate for the {@code Bean} class | 
|
     * should be named {@code BeanPersistenceDelegate} | 
|
     * and located in the same package. | 
|
     * <pre> | 
|
     * public class Bean { ... } | 
|
     * public class BeanPersistenceDelegate { ... }</pre> | 
|
     * The instance of the {@code BeanPersistenceDelegate} class | 
|
     * is returned for the {@code Bean} class. | 
|
     * <li> | 
|
     * If the type is {@code null}, | 
|
     * a shared internal persistence delegate is returned | 
|
     * that encodes {@code null} value. | 
|
     * <li> | 
|
     * If the type is a {@code enum} declaration, | 
|
     * a shared internal persistence delegate is returned | 
|
     * that encodes constants of this enumeration | 
|
     * by their names. | 
|
     * <li> | 
|
     * If the type is a primitive type or the corresponding wrapper, | 
|
     * a shared internal persistence delegate is returned | 
|
     * that encodes values of the given type. | 
|
     * <li> | 
|
     * If the type is an array, | 
|
     * a shared internal persistence delegate is returned | 
|
     * that encodes an array of the appropriate type and length, | 
|
     * and each of its elements as if they are properties. | 
|
     * <li> | 
|
     * If the type is a proxy, | 
|
     * a shared internal persistence delegate is returned | 
|
     * that encodes a proxy instance by using | 
|
     * the {@link java.lang.reflect.Proxy#newProxyInstance} method. | 
|
     * <li> | 
|
     * If the {@link BeanInfo} for this type has a {@link BeanDescriptor} | 
|
     * which defined a "persistenceDelegate" attribute, | 
|
     * the value of this named attribute is returned. | 
|
     * <li> | 
|
     * In all other cases the default persistence delegate is returned. | 
|
     * The default persistence delegate assumes the type is a <em>JavaBean</em>, | 
|
     * implying that it has a default constructor and that its state | 
|
     * may be characterized by the matching pairs of "setter" and "getter" | 
|
     * methods returned by the {@link Introspector} class. | 
|
     * The default constructor is the constructor with the greatest number | 
|
     * of parameters that has the {@link ConstructorProperties} annotation. | 
|
     * If none of the constructors has the {@code ConstructorProperties} annotation, | 
|
     * then the nullary constructor (constructor with no parameters) will be used. | 
|
     * For example, in the following code fragment, the nullary constructor | 
|
     * for the {@code Foo} class will be used, | 
|
     * while the two-parameter constructor | 
|
     * for the {@code Bar} class will be used. | 
|
     * <pre> | 
|
     * public class Foo { | 
|
     *     public Foo() { ... } | 
|
     *     public Foo(int x) { ... } | 
|
     * } | 
|
     * public class Bar { | 
|
     *     public Bar() { ... } | 
|
     *     @ConstructorProperties({"x"}) | 
|
     *     public Bar(int x) { ... } | 
|
     *     @ConstructorProperties({"x", "y"}) | 
|
     *     public Bar(int x, int y) { ... } | 
|
     * }</pre> | 
|
     * </ol> | 
|
     * | 
|
     * @param type  the class of the objects | 
|
     * @return the persistence delegate for the given type | 
|
     * | 
|
     * @see #setPersistenceDelegate | 
|
     * @see java.beans.Introspector#getBeanInfo | 
|
     * @see java.beans.BeanInfo#getBeanDescriptor | 
|
*/  | 
|
public PersistenceDelegate getPersistenceDelegate(Class<?> type) {  | 
|
PersistenceDelegate pd = this.finder.find(type);  | 
|
if (pd == null) {  | 
|
pd = MetaData.getPersistenceDelegate(type);  | 
|
if (pd != null) {  | 
|
this.finder.register(type, pd);  | 
|
}  | 
|
}  | 
|
return pd;  | 
|
}  | 
|
    /** | 
|
     * Associates the specified persistence delegate with the given type. | 
|
     * | 
|
     * @param type  the class of objects that the specified persistence delegate applies to | 
|
     * @param delegate  the persistence delegate for instances of the given type | 
|
     * | 
|
     * @see #getPersistenceDelegate | 
|
     * @see java.beans.Introspector#getBeanInfo | 
|
     * @see java.beans.BeanInfo#getBeanDescriptor | 
|
*/  | 
|
public void setPersistenceDelegate(Class<?> type, PersistenceDelegate delegate) {  | 
|
this.finder.register(type, delegate);  | 
|
}  | 
|
    /** | 
|
     * Removes the entry for this instance, returning the old entry. | 
|
     * | 
|
     * @param oldInstance The entry that should be removed. | 
|
     * @return The entry that was removed. | 
|
     * | 
|
     * @see #get | 
|
*/  | 
|
public Object remove(Object oldInstance) {  | 
|
Expression exp = bindings.remove(oldInstance);  | 
|
return getValue(exp);  | 
|
}  | 
|
    /** | 
|
     * Returns a tentative value for <code>oldInstance</code> in | 
|
     * the environment created by this stream. A persistence | 
|
     * delegate can use its <code>mutatesTo</code> method to | 
|
     * determine whether this value may be initialized to | 
|
     * form the equivalent object at the output or whether | 
|
     * a new object must be instantiated afresh. If the | 
|
     * stream has not yet seen this value, null is returned. | 
|
     * | 
|
     * @param  oldInstance The instance to be looked up. | 
|
     * @return The object, null if the object has not been seen before. | 
|
*/  | 
|
public Object get(Object oldInstance) {  | 
|
if (oldInstance == null || oldInstance == this ||  | 
|
oldInstance.getClass() == String.class) {  | 
|
return oldInstance;  | 
|
}  | 
|
Expression exp = bindings.get(oldInstance);  | 
|
return getValue(exp);  | 
|
}  | 
|
private Object writeObject1(Object oldInstance) {  | 
|
Object o = get(oldInstance);  | 
|
if (o == null) {  | 
|
writeObject(oldInstance);  | 
|
o = get(oldInstance);  | 
|
}  | 
|
return o;  | 
|
}  | 
|
private Statement cloneStatement(Statement oldExp) {  | 
|
Object oldTarget = oldExp.getTarget();  | 
|
Object newTarget = writeObject1(oldTarget);  | 
|
Object[] oldArgs = oldExp.getArguments();  | 
|
Object[] newArgs = new Object[oldArgs.length];  | 
|
for (int i = 0; i < oldArgs.length; i++) {  | 
|
newArgs[i] = writeObject1(oldArgs[i]);  | 
|
}  | 
|
Statement newExp = Statement.class.equals(oldExp.getClass())  | 
|
? new Statement(newTarget, oldExp.getMethodName(), newArgs)  | 
|
: new Expression(newTarget, oldExp.getMethodName(), newArgs);  | 
|
newExp.loader = oldExp.loader;  | 
|
return newExp;  | 
|
}  | 
|
    /** | 
|
     * Writes statement <code>oldStm</code> to the stream. | 
|
     * The <code>oldStm</code> should be written entirely | 
|
     * in terms of the callers environment, i.e. the | 
|
     * target and all arguments should be part of the | 
|
     * object graph being written. These expressions | 
|
     * represent a series of "what happened" expressions | 
|
     * which tell the output stream how to produce an | 
|
     * object graph like the original. | 
|
     * <p> | 
|
     * The implementation of this method will produce | 
|
     * a second expression to represent the same expression in | 
|
     * an environment that will exist when the stream is read. | 
|
     * This is achieved simply by calling <code>writeObject</code> | 
|
     * on the target and all the arguments and building a new | 
|
     * expression with the results. | 
|
     * | 
|
     * @param oldStm The expression to be written to the stream. | 
|
*/  | 
|
public void writeStatement(Statement oldStm) {  | 
|
        // System.out.println("writeStatement: " + oldExp); | 
|
Statement newStm = cloneStatement(oldStm);  | 
|
if (oldStm.getTarget() != this && executeStatements) {  | 
|
            try { | 
|
newStm.execute();  | 
|
} catch (Exception e) {  | 
|
getExceptionListener().exceptionThrown(new Exception("Encoder: discarding statement "  | 
|
+ newStm, e));  | 
|
}  | 
|
}  | 
|
}  | 
|
    /** | 
|
     * The implementation first checks to see if an | 
|
     * expression with this value has already been written. | 
|
     * If not, the expression is cloned, using | 
|
     * the same procedure as <code>writeStatement</code>, | 
|
     * and the value of this expression is reconciled | 
|
     * with the value of the cloned expression | 
|
     * by calling <code>writeObject</code>. | 
|
     * | 
|
     * @param oldExp The expression to be written to the stream. | 
|
*/  | 
|
public void writeExpression(Expression oldExp) {  | 
|
        // System.out.println("Encoder::writeExpression: " + oldExp); | 
|
Object oldValue = getValue(oldExp);  | 
|
if (get(oldValue) != null) {  | 
|
return;  | 
|
}  | 
|
bindings.put(oldValue, (Expression)cloneStatement(oldExp));  | 
|
writeObject(oldValue);  | 
|
}  | 
|
    void clear() { | 
|
bindings.clear();  | 
|
}  | 
|
    // Package private method for setting an attributes table for the encoder | 
|
void setAttribute(Object key, Object value) {  | 
|
if (attributes == null) {  | 
|
attributes = new HashMap<>();  | 
|
}  | 
|
attributes.put(key, value);  | 
|
}  | 
|
Object getAttribute(Object key) {  | 
|
if (attributes == null) {  | 
|
return null;  | 
|
}  | 
|
return attributes.get(key);  | 
|
}  | 
|
}  |