1/*
2 * Copyright (c) 2000, 2011, Oracle and/or its affiliates. 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.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package java.beans;
26
27import com.sun.beans.finder.PersistenceDelegateFinder;
28
29import java.util.HashMap;
30import java.util.IdentityHashMap;
31import java.util.Map;
32
33/**
34 * An {@code Encoder} is a class which can be used to create
35 * files or streams that encode the state of a collection of
36 * JavaBeans in terms of their public APIs. The {@code Encoder},
37 * in conjunction with its persistence delegates, is responsible for
38 * breaking the object graph down into a series of {@code Statement}s
39 * and {@code Expression}s which can be used to create it.
40 * A subclass typically provides a syntax for these expressions
41 * using some human readable form - like Java source code or XML.
42 *
43 * @since 1.4
44 *
45 * @author Philip Milne
46 */
47
48public class Encoder {
49    private final PersistenceDelegateFinder finder = new PersistenceDelegateFinder();
50    private Map<Object, Expression> bindings = new IdentityHashMap<>();
51    private ExceptionListener exceptionListener;
52    boolean executeStatements = true;
53    private Map<Object, Object> 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}.
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} 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 by applying
116     * the following rules in order:
117     * <ol>
118     * <li>
119     * If a persistence delegate is associated with the given type
120     * by using the {@link #setPersistenceDelegate} method
121     * it is returned.
122     * <li>
123     * A persistence delegate is then looked up by the name
124     * composed of the fully qualified name of the given type
125     * and the "PersistenceDelegate" postfix.
126     * For example, a persistence delegate for the {@code Bean} class
127     * should be named {@code BeanPersistenceDelegate}
128     * and located in the same package.
129     * <pre>
130     * public class Bean { ... }
131     * public class BeanPersistenceDelegate { ... }</pre>
132     * The instance of the {@code BeanPersistenceDelegate} class
133     * is returned for the {@code Bean} class.
134     * <li>
135     * If the type is {@code null},
136     * a shared internal persistence delegate is returned
137     * that encodes {@code null} value.
138     * <li>
139     * If the type is an {@code enum} declaration,
140     * a shared internal persistence delegate is returned
141     * that encodes constants of this enumeration
142     * by their names.
143     * <li>
144     * If the type is a primitive type or the corresponding wrapper,
145     * a shared internal persistence delegate is returned
146     * that encodes values of the given type.
147     * <li>
148     * If the type is an array,
149     * a shared internal persistence delegate is returned
150     * that encodes an array of the appropriate type and length,
151     * and each of its elements as if they are properties.
152     * <li>
153     * If the type is a proxy,
154     * a shared internal persistence delegate is returned
155     * that encodes a proxy instance by using
156     * the {@link java.lang.reflect.Proxy#newProxyInstance} method.
157     * <li>
158     * If the {@link BeanInfo} for this type has a {@link BeanDescriptor}
159     * which defined a "persistenceDelegate" attribute,
160     * the value of this named attribute is returned.
161     * <li>
162     * In all other cases the default persistence delegate is returned.
163     * The default persistence delegate assumes the type is a <em>JavaBean</em>,
164     * implying that it has a default constructor and that its state
165     * may be characterized by the matching pairs of "setter" and "getter"
166     * methods returned by the {@link Introspector} class.
167     * The default constructor is the constructor with the greatest number
168     * of parameters that has the {@link ConstructorProperties} annotation.
169     * If none of the constructors has the {@code ConstructorProperties} annotation,
170     * then the nullary constructor (constructor with no parameters) will be used.
171     * For example, in the following code fragment, the nullary constructor
172     * for the {@code Foo} class will be used,
173     * while the two-parameter constructor
174     * for the {@code Bar} class will be used.
175     * <pre>
176     * public class Foo {
177     *     public Foo() { ... }
178     *     public Foo(int x) { ... }
179     * }
180     * public class Bar {
181     *     public Bar() { ... }
182     *     &#64;ConstructorProperties({"x"})
183     *     public Bar(int x) { ... }
184     *     &#64;ConstructorProperties({"x", "y"})
185     *     public Bar(int x, int y) { ... }
186     * }</pre>
187     * </ol>
188     *
189     * @param type  the class of the objects
190     * @return the persistence delegate for the given type
191     *
192     * @see #setPersistenceDelegate
193     * @see java.beans.Introspector#getBeanInfo
194     * @see java.beans.BeanInfo#getBeanDescriptor
195     */
196    public PersistenceDelegate getPersistenceDelegate(Class<?> type) {
197        PersistenceDelegate pd = this.finder.find(type);
198        if (pd == null) {
199            pd = MetaData.getPersistenceDelegate(type);
200            if (pd != null) {
201                this.finder.register(type, pd);
202            }
203        }
204        return pd;
205    }
206
207    /**
208     * Associates the specified persistence delegate with the given type.
209     *
210     * @param type  the class of objects that the specified persistence delegate applies to
211     * @param delegate  the persistence delegate for instances of the given type
212     *
213     * @see #getPersistenceDelegate
214     * @see java.beans.Introspector#getBeanInfo
215     * @see java.beans.BeanInfo#getBeanDescriptor
216     */
217    public void setPersistenceDelegate(Class<?> type, PersistenceDelegate delegate) {
218        this.finder.register(type, delegate);
219    }
220
221    /**
222     * Removes the entry for this instance, returning the old entry.
223     *
224     * @param oldInstance The entry that should be removed.
225     * @return The entry that was removed.
226     *
227     * @see #get
228     */
229    public Object remove(Object oldInstance) {
230        Expression exp = bindings.remove(oldInstance);
231        return getValue(exp);
232    }
233
234    /**
235     * Returns a tentative value for {@code oldInstance} in
236     * the environment created by this stream. A persistence
237     * delegate can use its {@code mutatesTo} method to
238     * determine whether this value may be initialized to
239     * form the equivalent object at the output or whether
240     * a new object must be instantiated afresh. If the
241     * stream has not yet seen this value, null is returned.
242     *
243     * @param  oldInstance The instance to be looked up.
244     * @return The object, null if the object has not been seen before.
245     */
246    public Object get(Object oldInstance) {
247        if (oldInstance == null || oldInstance == this ||
248            oldInstance.getClass() == String.class) {
249            return oldInstance;
250        }
251        Expression exp = bindings.get(oldInstance);
252        return getValue(exp);
253    }
254
255    private Object writeObject1(Object oldInstance) {
256        Object o = get(oldInstance);
257        if (o == null) {
258            writeObject(oldInstance);
259            o = get(oldInstance);
260        }
261        return o;
262    }
263
264    private Statement cloneStatement(Statement oldExp) {
265        Object oldTarget = oldExp.getTarget();
266        Object newTarget = writeObject1(oldTarget);
267
268        Object[] oldArgs = oldExp.getArguments();
269        Object[] newArgs = new Object[oldArgs.length];
270        for (int i = 0; i < oldArgs.length; i++) {
271            newArgs[i] = writeObject1(oldArgs[i]);
272        }
273        Statement newExp = Statement.class.equals(oldExp.getClass())
274                ? new Statement(newTarget, oldExp.getMethodName(), newArgs)
275                : new Expression(newTarget, oldExp.getMethodName(), newArgs);
276        newExp.loader = oldExp.loader;
277        return newExp;
278    }
279
280    /**
281     * Writes statement {@code oldStm} to the stream.
282     * The {@code oldStm} should be written entirely
283     * in terms of the callers environment, i.e. the
284     * target and all arguments should be part of the
285     * object graph being written. These expressions
286     * represent a series of "what happened" expressions
287     * which tell the output stream how to produce an
288     * object graph like the original.
289     * <p>
290     * The implementation of this method will produce
291     * a second expression to represent the same expression in
292     * an environment that will exist when the stream is read.
293     * This is achieved simply by calling {@code writeObject}
294     * on the target and all the arguments and building a new
295     * expression with the results.
296     *
297     * @param oldStm The expression to be written to the stream.
298     */
299    public void writeStatement(Statement oldStm) {
300        // System.out.println("writeStatement: " + oldExp);
301        Statement newStm = cloneStatement(oldStm);
302        if (oldStm.getTarget() != this && executeStatements) {
303            try {
304                newStm.execute();
305            } catch (Exception e) {
306                getExceptionListener().exceptionThrown(new Exception("Encoder: discarding statement "
307                                                                     + newStm, e));
308            }
309        }
310    }
311
312    /**
313     * The implementation first checks to see if an
314     * expression with this value has already been written.
315     * If not, the expression is cloned, using
316     * the same procedure as {@code writeStatement},
317     * and the value of this expression is reconciled
318     * with the value of the cloned expression
319     * by calling {@code writeObject}.
320     *
321     * @param oldExp The expression to be written to the stream.
322     */
323    public void writeExpression(Expression oldExp) {
324        // System.out.println("Encoder::writeExpression: " + oldExp);
325        Object oldValue = getValue(oldExp);
326        if (get(oldValue) != null) {
327            return;
328        }
329        bindings.put(oldValue, (Expression)cloneStatement(oldExp));
330        writeObject(oldValue);
331    }
332
333    void clear() {
334        bindings.clear();
335    }
336
337    // Package private method for setting an attributes table for the encoder
338    void setAttribute(Object key, Object value) {
339        if (attributes == null) {
340            attributes = new HashMap<>();
341        }
342        attributes.put(key, value);
343    }
344
345    Object getAttribute(Object key) {
346        if (attributes == null) {
347            return null;
348        }
349        return attributes.get(key);
350    }
351}
352