001 /*
002 * Copyright 2000-2007 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025 package java.beans;
026
027 import java.util.Collections;
028 import java.util.HashMap;
029 import java.util.IdentityHashMap;
030 import java.util.Map;
031
032 /**
033 * An <code>Encoder</code> is a class which can be used to create
034 * files or streams that encode the state of a collection of
035 * JavaBeans in terms of their public APIs. The <code>Encoder</code>,
036 * in conjunction with its persistence delegates, is responsible for
037 * breaking the object graph down into a series of <code>Statements</code>s
038 * and <code>Expression</code>s which can be used to create it.
039 * A subclass typically provides a syntax for these expressions
040 * using some human readable form - like Java source code or XML.
041 *
042 * @since 1.4
043 *
044 * @version 1.3 11/15/00
045 * @author Philip Milne
046 */
047
048 public class Encoder {
049 private final Map<Class<?>, PersistenceDelegate> delegates = Collections
050 .synchronizedMap(new HashMap<Class<?>, PersistenceDelegate>());
051 private Map bindings = new IdentityHashMap();
052 private ExceptionListener exceptionListener;
053 boolean executeStatements = true;
054 private Map attributes;
055
056 /**
057 * Write the specified object to the output stream.
058 * The serialized form will denote a series of
059 * expressions, the combined effect of which will create
060 * an equivalent object when the input stream is read.
061 * By default, the object is assumed to be a <em>JavaBean</em>
062 * with a nullary constructor, whose state is defined by
063 * the matching pairs of "setter" and "getter" methods
064 * returned by the Introspector.
065 *
066 * @param o The object to be written to the stream.
067 *
068 * @see XMLDecoder#readObject
069 */
070 protected void writeObject(Object o) {
071 if (o == this ) {
072 return;
073 }
074 PersistenceDelegate info = getPersistenceDelegate(o == null ? null
075 : o.getClass());
076 info.writeObject(o, this );
077 }
078
079 /**
080 * Sets the exception handler for this stream to <code>exceptionListener</code>.
081 * The exception handler is notified when this stream catches recoverable
082 * exceptions.
083 *
084 * @param exceptionListener The exception handler for this stream;
085 * if <code>null</code> the default exception listener will be used.
086 *
087 * @see #getExceptionListener
088 */
089 public void setExceptionListener(ExceptionListener exceptionListener) {
090 this .exceptionListener = exceptionListener;
091 }
092
093 /**
094 * Gets the exception handler for this stream.
095 *
096 * @return The exception handler for this stream;
097 * Will return the default exception listener if this has not explicitly been set.
098 *
099 * @see #setExceptionListener
100 */
101 public ExceptionListener getExceptionListener() {
102 return (exceptionListener != null) ? exceptionListener
103 : Statement.defaultExceptionListener;
104 }
105
106 Object getValue(Expression exp) {
107 try {
108 return (exp == null) ? null : exp.getValue();
109 } catch (Exception e) {
110 getExceptionListener().exceptionThrown(e);
111 throw new RuntimeException("failed to evaluate: "
112 + exp.toString());
113 }
114 }
115
116 /**
117 * Returns the persistence delegate for the given type.
118 * The persistence delegate is calculated
119 * by applying the following of rules in order:
120 * <ul>
121 * <li>
122 * If the type is an array, an internal persistence
123 * delegate is returned which will instantiate an
124 * array of the appropriate type and length, initializing
125 * each of its elements as if they are properties.
126 * <li>
127 * If the type is a proxy, an internal persistence
128 * delegate is returned which will instantiate a
129 * new proxy instance using the static
130 * "newProxyInstance" method defined in the
131 * Proxy class.
132 * <li>
133 * If the BeanInfo for this type has a <code>BeanDescriptor</code>
134 * which defined a "persistenceDelegate" property, this
135 * value is returned.
136 * <li>
137 * In all other cases the default persistence delegate
138 * is returned. The default persistence delegate assumes
139 * the type is a <em>JavaBean</em>, implying that it has a default constructor
140 * and that its state may be characterized by the matching pairs
141 * of "setter" and "getter" methods returned by the Introspector.
142 * The default constructor is the constructor with the greatest number
143 * of parameters that has the {@link ConstructorProperties} annotation.
144 * If none of the constructors have the {@code ConstructorProperties} annotation,
145 * then the nullary constructor (constructor with no parameters) will be used.
146 * For example, in the following the nullary constructor
147 * for {@code Foo} will be used, while the two parameter constructor
148 * for {@code Bar} will be used.
149 * <code>
150 * public class Foo {
151 * public Foo() { ... }
152 * public Foo(int x) { ... }
153 * }
154 * public class Bar {
155 * public Bar() { ... }
156 * @ConstructorProperties({"x"})
157 * public Bar(int x) { ... }
158 * @ConstructorProperties({"x", "y"})
159 * public Bar(int x, int y) { ... }
160 * }
161 * </code>
162 * </ul>
163 *
164 * @param type The type of the object.
165 * @return The persistence delegate for this type of object.
166 *
167 * @see #setPersistenceDelegate
168 * @see java.beans.Introspector#getBeanInfo
169 * @see java.beans.BeanInfo#getBeanDescriptor
170 */
171 public PersistenceDelegate getPersistenceDelegate(Class<?> type) {
172 PersistenceDelegate pd = this .delegates.get(type);
173 return (pd != null) ? pd : MetaData
174 .getPersistenceDelegate(type);
175 }
176
177 /**
178 * Sets the persistence delegate associated with this <code>type</code> to
179 * <code>persistenceDelegate</code>.
180 *
181 * @param type The class of objects that <code>persistenceDelegate</code> applies to.
182 * @param persistenceDelegate The persistence delegate for instances of <code>type</code>.
183 *
184 * @see #getPersistenceDelegate
185 * @see java.beans.Introspector#getBeanInfo
186 * @see java.beans.BeanInfo#getBeanDescriptor
187 */
188 public void setPersistenceDelegate(Class<?> type,
189 PersistenceDelegate persistenceDelegate) {
190 if (persistenceDelegate != null) {
191 this .delegates.put(type, persistenceDelegate);
192 } else {
193 this .delegates.remove(type);
194 }
195 }
196
197 /**
198 * Removes the entry for this instance, returning the old entry.
199 *
200 * @param oldInstance The entry that should be removed.
201 * @return The entry that was removed.
202 *
203 * @see #get
204 */
205 public Object remove(Object oldInstance) {
206 Expression exp = (Expression) bindings.remove(oldInstance);
207 return getValue(exp);
208 }
209
210 /**
211 * Returns a tentative value for <code>oldInstance</code> in
212 * the environment created by this stream. A persistence
213 * delegate can use its <code>mutatesTo</code> method to
214 * determine whether this value may be initialized to
215 * form the equivalent object at the output or whether
216 * a new object must be instantiated afresh. If the
217 * stream has not yet seen this value, null is returned.
218 *
219 * @param oldInstance The instance to be looked up.
220 * @return The object, null if the object has not been seen before.
221 */
222 public Object get(Object oldInstance) {
223 if (oldInstance == null || oldInstance == this
224 || oldInstance.getClass() == String.class) {
225 return oldInstance;
226 }
227 Expression exp = (Expression) bindings.get(oldInstance);
228 return getValue(exp);
229 }
230
231 private Object writeObject1(Object oldInstance) {
232 Object o = get(oldInstance);
233 if (o == null) {
234 writeObject(oldInstance);
235 o = get(oldInstance);
236 }
237 return o;
238 }
239
240 private Statement cloneStatement(Statement oldExp) {
241 Object oldTarget = oldExp.getTarget();
242 Object newTarget = writeObject1(oldTarget);
243
244 Object[] oldArgs = oldExp.getArguments();
245 Object[] newArgs = new Object[oldArgs.length];
246 for (int i = 0; i < oldArgs.length; i++) {
247 newArgs[i] = writeObject1(oldArgs[i]);
248 }
249 if (oldExp.getClass() == Statement.class) {
250 return new Statement(newTarget, oldExp.getMethodName(),
251 newArgs);
252 } else {
253 return new Expression(newTarget, oldExp.getMethodName(),
254 newArgs);
255 }
256 }
257
258 /**
259 * Writes statement <code>oldStm</code> to the stream.
260 * The <code>oldStm</code> should be written entirely
261 * in terms of the callers environment, i.e. the
262 * target and all arguments should be part of the
263 * object graph being written. These expressions
264 * represent a series of "what happened" expressions
265 * which tell the output stream how to produce an
266 * object graph like the original.
267 * <p>
268 * The implementation of this method will produce
269 * a second expression to represent the same expression in
270 * an environment that will exist when the stream is read.
271 * This is achieved simply by calling <code>writeObject</code>
272 * on the target and all the arguments and building a new
273 * expression with the results.
274 *
275 * @param oldStm The expression to be written to the stream.
276 */
277 public void writeStatement(Statement oldStm) {
278 // System.out.println("writeStatement: " + oldExp);
279 Statement newStm = cloneStatement(oldStm);
280 if (oldStm.getTarget() != this && executeStatements) {
281 try {
282 newStm.execute();
283 } catch (Exception e) {
284 getExceptionListener().exceptionThrown(
285 new Exception("Encoder: discarding statement "
286 + newStm, e));
287 }
288 }
289 }
290
291 /**
292 * The implementation first checks to see if an
293 * expression with this value has already been written.
294 * If not, the expression is cloned, using
295 * the same procedure as <code>writeStatement</code>,
296 * and the value of this expression is reconciled
297 * with the value of the cloned expression
298 * by calling <code>writeObject</code>.
299 *
300 * @param oldExp The expression to be written to the stream.
301 */
302 public void writeExpression(Expression oldExp) {
303 // System.out.println("Encoder::writeExpression: " + oldExp);
304 Object oldValue = getValue(oldExp);
305 if (get(oldValue) != null) {
306 return;
307 }
308 bindings.put(oldValue, (Expression) cloneStatement(oldExp));
309 writeObject(oldValue);
310 }
311
312 void clear() {
313 bindings.clear();
314 }
315
316 // Package private method for setting an attributes table for the encoder
317 void setAttribute(Object key, Object value) {
318 if (attributes == null) {
319 attributes = new HashMap();
320 }
321 attributes.put(key, value);
322 }
323
324 Object getAttribute(Object key) {
325 if (attributes == null) {
326 return null;
327 }
328 return attributes.get(key);
329 }
330 }
|