| /* |
| * 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} 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}, |
| * in conjunction with its persistence delegates, is responsible for |
| * breaking the object graph down into a series of {@code Statement}s |
| * and {@code Expression}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}. |
| * The exception handler is notified when this stream catches recoverable |
| * exceptions. |
| * |
| * @param exceptionListener The exception handler for this stream; |
| * if {@code null} 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 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 an {@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} in |
| * the environment created by this stream. A persistence |
| * delegate can use its {@code mutatesTo} 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} to the stream. |
| * The {@code oldStm} 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} |
| * 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}, |
| * and the value of this expression is reconciled |
| * with the value of the cloned expression |
| * by calling {@code writeObject}. |
| * |
| * @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); |
| } |
| } |