001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package java.beans;
019:
020: import java.awt.Choice;
021: import java.awt.Color;
022: import java.awt.Component;
023: import java.awt.Container;
024: import java.awt.Cursor;
025: import java.awt.Dimension;
026: import java.awt.Font;
027: import java.awt.Insets;
028: import java.awt.List;
029: import java.awt.Menu;
030: import java.awt.MenuBar;
031: import java.awt.MenuShortcut;
032: import java.awt.Point;
033: import java.awt.Rectangle;
034: import java.awt.ScrollPane;
035: import java.awt.SystemColor;
036: import java.awt.font.TextAttribute;
037: import java.lang.reflect.Field;
038: import java.lang.reflect.Method;
039: import java.lang.reflect.Proxy;
040: import java.util.Collection;
041: import java.util.Date;
042: import java.util.Hashtable;
043: import java.util.Map;
044:
045: import javax.swing.Box;
046: import javax.swing.DefaultComboBoxModel;
047: import javax.swing.JFrame;
048: import javax.swing.JTabbedPane;
049: import javax.swing.ToolTipManager;
050:
051: /**
052: * The <code>Encoder</code>, together with <code>PersistenceDelegate</code>
053: * s, can encode an object into a series of java statements. By executing these
054: * statements, a new object can be created and it will has the same state as the
055: * original object which has been passed to the encoder. Here "has the same
056: * state" means the two objects are indistinguishable from their public API.
057: * <p>
058: * The <code>Encoder</code> and <code>PersistenceDelegate</code> s do this
059: * by creating copies of the input object and all objects it references. The
060: * copy process continues recursively util every object in the object graph has
061: * its new copy and the new version has the same state as the old version. All
062: * statements used to create those new objects and executed on them during the
063: * process form the result of encoding.
064: * </p>
065: *
066: */
067: @SuppressWarnings("unchecked")
068: public class Encoder {
069:
070: private static final Hashtable delegates = new Hashtable();
071:
072: private static final DefaultPersistenceDelegate defaultPD = new DefaultPersistenceDelegate();
073:
074: private static final ArrayPersistenceDelegate arrayPD = new ArrayPersistenceDelegate();
075:
076: private static final ProxyPersistenceDelegate proxyPD = new ProxyPersistenceDelegate();
077:
078: private static final NullPersistenceDelegate nullPD = new NullPersistenceDelegate();
079:
080: private static final ExceptionListener defaultExListener = new DefaultExceptionListener();
081:
082: private static class DefaultExceptionListener implements
083: ExceptionListener {
084:
085: public void exceptionThrown(Exception exception) {
086: System.err
087: .println("Exception during encoding:" + exception); //$NON-NLS-1$
088: System.err.println("Continue..."); //$NON-NLS-1$
089: }
090:
091: }
092:
093: static {
094: PersistenceDelegate ppd = new PrimitiveWrapperPersistenceDelegate();
095: delegates.put(Boolean.class, ppd);
096: delegates.put(Byte.class, ppd);
097: delegates.put(Character.class, ppd);
098: delegates.put(Double.class, ppd);
099: delegates.put(Float.class, ppd);
100: delegates.put(Integer.class, ppd);
101: delegates.put(Long.class, ppd);
102: delegates.put(Short.class, ppd);
103:
104: delegates.put(Class.class, new ClassPersistenceDelegate());
105: delegates.put(Field.class, new FieldPersistenceDelegate());
106: delegates.put(Method.class, new MethodPersistenceDelegate());
107: delegates.put(String.class, new StringPersistenceDelegate());
108: delegates.put(Proxy.class, new ProxyPersistenceDelegate());
109:
110: delegates.put(Choice.class, new AwtChoicePersistenceDelegate());
111: delegates.put(Color.class, new AwtColorPersistenceDelegate());
112: delegates.put(Container.class,
113: new AwtContainerPersistenceDelegate());
114: delegates.put(Component.class,
115: new AwtComponentPersistenceDelegate());
116: delegates.put(Cursor.class, new AwtCursorPersistenceDelegate());
117: delegates.put(Dimension.class,
118: new AwtDimensionPersistenceDelegate());
119: delegates.put(Font.class, new AwtFontPersistenceDelegate());
120: delegates.put(Insets.class, new AwtInsetsPersistenceDelegate());
121: delegates.put(List.class, new AwtListPersistenceDelegate());
122: delegates.put(Menu.class, new AwtMenuPersistenceDelegate());
123: delegates.put(MenuBar.class,
124: new AwtMenuBarPersistenceDelegate());
125: delegates.put(MenuShortcut.class,
126: new AwtMenuShortcutPersistenceDelegate());
127: delegates.put(Point.class, new AwtPointPersistenceDelegate());
128: delegates.put(Rectangle.class,
129: new AwtRectanglePersistenceDelegate());
130: delegates.put(SystemColor.class,
131: new AwtSystemColorPersistenceDelegate());
132: delegates.put(TextAttribute.class,
133: new AwtFontTextAttributePersistenceDelegate());
134:
135: delegates.put(Box.class, new SwingBoxPersistenceDelegate());
136: delegates.put(JFrame.class,
137: new SwingJFramePersistenceDelegate());
138: delegates.put(JTabbedPane.class,
139: new SwingJTabbedPanePersistenceDelegate());
140: delegates.put(DefaultComboBoxModel.class,
141: new SwingDefaultComboBoxModelPersistenceDelegate());
142: delegates.put(ToolTipManager.class,
143: new SwingToolTipManagerPersistenceDelegate());
144: delegates.put(ScrollPane.class,
145: new AwtScrollPanePersistenceDelegate());
146:
147: delegates.put(Date.class, new UtilDatePersistenceDelegate());
148: }
149:
150: private ExceptionListener listener = defaultExListener;
151:
152: private ReferenceMap oldNewMap = new ReferenceMap();
153:
154: /**
155: * Construct a new encoder.
156: */
157: public Encoder() {
158: super ();
159: }
160:
161: /**
162: * Clear all the new objects have been created.
163: */
164: void clear() {
165: oldNewMap.clear();
166: }
167:
168: /**
169: * Gets the new copy of the given old object.
170: * <p>
171: * Strings are special objects which have their new copy by default, so if
172: * the old object is a string, it is returned directly.
173: * </p>
174: *
175: * @param old
176: * an old object
177: * @return the new copy of the given old object, or null if there is not
178: * one.
179: */
180: public Object get(Object old) {
181: if (old == null || old instanceof String) {
182: return old;
183: }
184: return oldNewMap.get(old);
185: }
186:
187: /**
188: * Returns the exception listener of this encoder.
189: * <p>
190: * An encoder always have a non-null exception listener. A default exception
191: * listener is used when the encoder is created.
192: * </p>
193: *
194: * @return the exception listener of this encoder
195: */
196: public ExceptionListener getExceptionListener() {
197: return listener;
198: }
199:
200: /**
201: * Returns a <code>PersistenceDelegate</code> for the given class type.
202: * <p>
203: * The <code>PersistenceDelegate</code> is determined as following:
204: * <ol>
205: * <li>If a <code>PersistenceDelegate</code> has been registered by
206: * calling <code>setPersistenceDelegate</code> for the given type, it is
207: * returned.</li>
208: * <li>If the given type is an array class, a special
209: * <code>PersistenceDelegate</code> for array types is returned.</li>
210: * <li>If the given type is a proxy class, a special
211: * <code>PersistenceDelegate</code> for proxy classes is returned.</li>
212: * <li><code>Introspector</code> is used to check the bean descriptor
213: * value "persistenceDelegate". If one is set, it is returned.</li>
214: * <li>If none of the above applies, the
215: * <code>DefaultPersistenceDelegate</code> is returned.</li>
216: * </ol>
217: * </p>
218: *
219: * @param type
220: * a class type
221: * @return a <code>PersistenceDelegate</code> for the given class type
222: */
223: public PersistenceDelegate getPersistenceDelegate(Class<?> type) {
224: if (type == null) {
225: return nullPD; // may be return a special PD?
226: }
227:
228: // registered delegate
229: PersistenceDelegate registeredPD = (PersistenceDelegate) delegates
230: .get(type);
231: if (registeredPD != null) {
232: return registeredPD;
233: }
234:
235: if (java.util.List.class.isAssignableFrom(type)) {
236: return new UtilListPersistenceDelegate();
237: }
238:
239: if (Collection.class.isAssignableFrom(type)) {
240: return new UtilCollectionPersistenceDelegate();
241: }
242:
243: if (Map.class.isAssignableFrom(type)) {
244: return new UtilMapPersistenceDelegate();
245: }
246:
247: if (type.isArray()) {
248: return arrayPD;
249: }
250: if (Proxy.isProxyClass(type)) {
251: return proxyPD;
252: }
253:
254: // check "persistenceDelegate" property
255: try {
256: BeanInfo binfo = Introspector.getBeanInfo(type);
257: if (binfo != null) {
258: PersistenceDelegate pd = (PersistenceDelegate) binfo
259: .getBeanDescriptor().getValue(
260: "persistenceDelegate"); //$NON-NLS-1$
261: if (pd != null) {
262: return pd;
263: }
264: }
265: } catch (Exception e) {
266: // ignore
267: }
268:
269: // default persistence delegate
270: return defaultPD;
271: }
272:
273: private void put(Object old, Object nu) {
274: oldNewMap.put(old, nu);
275: }
276:
277: /**
278: * Remvoe the existing new copy of the given old object.
279: *
280: * @param old
281: * an old object
282: * @return the removed new version of the old object, or null if there is
283: * not one
284: */
285: public Object remove(Object old) {
286: return oldNewMap.remove(old);
287: }
288:
289: /**
290: * Sets the exception listener of this encoder.
291: *
292: * @param listener
293: * the exception listener to set
294: */
295: public void setExceptionListener(ExceptionListener listener) {
296: if (listener == null) {
297: this .listener = defaultExListener;
298: return;
299: }
300: this .listener = listener;
301: }
302:
303: /**
304: * Register the <code>PersistenceDelegate</code> of the specified type.
305: *
306: * @param type
307: * @param delegate
308: */
309: public void setPersistenceDelegate(Class<?> type,
310: PersistenceDelegate delegate) {
311: if (type == null || delegate == null) {
312: throw new NullPointerException();
313: }
314: delegates.put(type, delegate);
315: }
316:
317: private Object forceNew(Object old) {
318: if (old == null) {
319: return null;
320: }
321: Object nu = get(old);
322: if (nu != null) {
323: return nu;
324: }
325: writeObject(old);
326: return get(old);
327: }
328:
329: private Object[] forceNewArray(Object oldArray[]) {
330: if (oldArray == null) {
331: return null;
332: }
333: Object newArray[] = new Object[oldArray.length];
334: for (int i = 0; i < oldArray.length; i++) {
335: newArray[i] = forceNew(oldArray[i]);
336: }
337: return newArray;
338: }
339:
340: /**
341: * Write an expression of old objects.
342: * <p>
343: * The implementation first check the return value of the expression. If
344: * there exists a new version of the object, simply return.
345: * </p>
346: * <p>
347: * A new expression is created using the new versions of the target and the
348: * arguments. If any of the old objects do not have its new version yet,
349: * <code>writeObject()</code> is called to create the new version.
350: * </p>
351: * <p>
352: * The new expression is then executed to obtained a new copy of the old
353: * return value.
354: * </p>
355: * <p>
356: * Call <code>writeObject()</code> with the old return value, so that more
357: * statements will be executed on its new version to change it into the same
358: * state as the old value.
359: * </p>
360: *
361: * @param oldExp
362: * the expression to write. The target, arguments, and return
363: * value of the expression are all old objects.
364: */
365: public void writeExpression(Expression oldExp) {
366: if (oldExp == null) {
367: throw new NullPointerException();
368: }
369: try {
370: // if oldValue exists, noop
371: Object oldValue = oldExp.getValue();
372: if (oldValue == null || get(oldValue) != null) {
373: return;
374: }
375:
376: // copy to newExp
377: Object newTarget = forceNew(oldExp.getTarget());
378: Object newArgs[] = forceNewArray(oldExp.getArguments());
379: Expression newExp = new Expression(newTarget, oldExp
380: .getMethodName(), newArgs);
381:
382: // execute newExp
383: Object newValue = null;
384: try {
385: newValue = newExp.getValue();
386: } catch (IndexOutOfBoundsException ex) {
387: // Current Container does not have any component, newVal set
388: // to null
389: }
390:
391: // relate oldValue to newValue
392: put(oldValue, newValue);
393:
394: // force same state
395: writeObject(oldValue);
396: } catch (Exception e) {
397: listener.exceptionThrown(new Exception(
398: "failed to write expression: " + oldExp, e)); //$NON-NLS-1$
399: }
400: }
401:
402: /**
403: * Encode the given object into a series of statements and expressions.
404: * <p>
405: * The implementation simply finds the <code>PersistenceDelegate</code>
406: * responsible for the object's class, and delegate the call to it.
407: * </p>
408: *
409: * @param o
410: * the object to encode
411: */
412: protected void writeObject(Object o) {
413: if (o == null) {
414: return;
415: }
416: Class type = o.getClass();
417: getPersistenceDelegate(type).writeObject(o, this );
418: }
419:
420: /**
421: * Write a statement of old objects.
422: * <p>
423: * A new statement is created by using the new versions of the target and
424: * arguments. If any of the objects do not have its new copy yet,
425: * <code>writeObject()</code> is called to create one.
426: * </p>
427: * <p>
428: * The new statement is then executed to change the state of the new object.
429: * </p>
430: *
431: * @param oldStat
432: * a statement of old objects
433: */
434: public void writeStatement(Statement oldStat) {
435: if (oldStat == null) {
436: throw new NullPointerException();
437: }
438: try {
439: // copy to newStat
440: Object newTarget = forceNew(oldStat.getTarget());
441: Object newArgs[] = forceNewArray(oldStat.getArguments());
442: Statement newStat = new Statement(newTarget, oldStat
443: .getMethodName(), newArgs);
444:
445: // execute newStat
446: newStat.execute();
447: } catch (Exception e) {
448: listener.exceptionThrown(new Exception(
449: "failed to write statement: " + oldStat, e)); //$NON-NLS-1$
450: }
451: }
452:
453: }
|