/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.googlecode.openbeans; import java.awt.Choice; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.Insets; import java.awt.List; import java.awt.Menu; import java.awt.MenuBar; import java.awt.MenuShortcut; import java.awt.Point; import java.awt.Rectangle; import java.awt.ScrollPane; import java.awt.SystemColor; import java.awt.font.TextAttribute; import com.googlecode.openbeans.ArrayPersistenceDelegate; import com.googlecode.openbeans.BeanInfo; import com.googlecode.openbeans.DefaultPersistenceDelegate; import com.googlecode.openbeans.ExceptionListener; import com.googlecode.openbeans.Expression; import com.googlecode.openbeans.Introspector; import com.googlecode.openbeans.NullPersistenceDelegate; import com.googlecode.openbeans.PersistenceDelegate; import com.googlecode.openbeans.ProxyPersistenceDelegate; import com.googlecode.openbeans.Statement; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Collection; import java.util.Date; import java.util.Hashtable; import java.util.IdentityHashMap; import java.util.Map; import javax.swing.Box; import javax.swing.DefaultComboBoxModel; import javax.swing.JFrame; import javax.swing.JTabbedPane; import javax.swing.ToolTipManager; /** * The <code>Encoder</code>, together with <code>PersistenceDelegate</code> s, * can encode an object into a series of java statements. By executing these * statements, a new object can be created and it will has the same state as the * original object which has been passed to the encoder. Here "has the same * state" means the two objects are indistinguishable from their public API. * <p> * The <code>Encoder</code> and <code>PersistenceDelegate</code> s do this by * creating copies of the input object and all objects it references. The copy * process continues recursively util every object in the object graph has its * new copy and the new version has the same state as the old version. All * statements used to create those new objects and executed on them during the * process form the result of encoding. * </p> * */ public class Encoder { private static final Hashtable<Class<?>, PersistenceDelegate> delegates = new Hashtable<Class<?>, PersistenceDelegate>(); private static final DefaultPersistenceDelegate defaultPD = new DefaultPersistenceDelegate(); private static final UtilCollectionsPersistenceDelegate utilCollectionsPD = new UtilCollectionsPersistenceDelegate(); private static final ArrayPersistenceDelegate arrayPD = new ArrayPersistenceDelegate(); private static final ProxyPersistenceDelegate proxyPD = new ProxyPersistenceDelegate(); private static final NullPersistenceDelegate nullPD = new NullPersistenceDelegate(); private static final ExceptionListener defaultExListener = new DefaultExceptionListener(); private static class DefaultExceptionListener implements ExceptionListener { public void exceptionThrown(Exception exception) { System.err.println("Exception during encoding:" + exception); //$NON-NLS-1$ System.err.println("Continue..."); //$NON-NLS-1$ } } static { PersistenceDelegate ppd = new PrimitiveWrapperPersistenceDelegate(); delegates.put(Boolean.class, ppd); delegates.put(Byte.class, ppd); delegates.put(Character.class, ppd); delegates.put(Double.class, ppd); delegates.put(Float.class, ppd); delegates.put(Integer.class, ppd); delegates.put(Long.class, ppd); delegates.put(Short.class, ppd); delegates.put(Class.class, new ClassPersistenceDelegate()); delegates.put(Field.class, new FieldPersistenceDelegate()); delegates.put(Method.class, new MethodPersistenceDelegate()); delegates.put(String.class, new StringPersistenceDelegate()); delegates.put(Proxy.class, new ProxyPersistenceDelegate()); delegates.put(Choice.class, new AwtChoicePersistenceDelegate()); delegates.put(Color.class, new AwtColorPersistenceDelegate()); delegates.put(Container.class, new AwtContainerPersistenceDelegate()); delegates.put(Component.class, new AwtComponentPersistenceDelegate()); delegates.put(Cursor.class, new AwtCursorPersistenceDelegate()); delegates.put(Dimension.class, new AwtDimensionPersistenceDelegate()); delegates.put(Font.class, new AwtFontPersistenceDelegate()); delegates.put(Insets.class, new AwtInsetsPersistenceDelegate()); delegates.put(List.class, new AwtListPersistenceDelegate()); delegates.put(Menu.class, new AwtMenuPersistenceDelegate()); delegates.put(MenuBar.class, new AwtMenuBarPersistenceDelegate()); delegates.put(MenuShortcut.class, new AwtMenuShortcutPersistenceDelegate()); delegates.put(Point.class, new AwtPointPersistenceDelegate()); delegates.put(Rectangle.class, new AwtRectanglePersistenceDelegate()); delegates.put(SystemColor.class, new AwtSystemColorPersistenceDelegate()); delegates.put(TextAttribute.class, new AwtFontTextAttributePersistenceDelegate()); delegates.put(Box.class, new SwingBoxPersistenceDelegate()); delegates.put(JFrame.class, new SwingJFramePersistenceDelegate()); delegates.put(JTabbedPane.class, new SwingJTabbedPanePersistenceDelegate()); delegates.put(DefaultComboBoxModel.class, new SwingDefaultComboBoxModelPersistenceDelegate()); delegates.put(ToolTipManager.class, new SwingToolTipManagerPersistenceDelegate()); delegates.put(ScrollPane.class, new AwtScrollPanePersistenceDelegate()); delegates.put(Date.class, new UtilDatePersistenceDelegate()); PersistenceDelegate pd = new UtilListPersistenceDelegate(); delegates.put(java.util.List.class, pd); delegates.put(java.util.AbstractList.class, pd); pd = new UtilCollectionPersistenceDelegate(); delegates.put(java.util.Collection.class, pd); delegates.put(java.util.AbstractCollection.class, pd); pd = new UtilMapPersistenceDelegate(); delegates.put(java.util.Map.class, pd); delegates.put(java.util.AbstractMap.class, pd); delegates.put(java.util.Hashtable.class, pd); } private ExceptionListener listener = defaultExListener; private IdentityHashMap<Object, Object> oldNewMap = new IdentityHashMap<Object, Object>(); /** * Construct a new encoder. */ public Encoder() { super(); } /** * Clear all the new objects have been created. */ void clear() { oldNewMap.clear(); } /** * Gets the new copy of the given old object. * <p> * Strings are special objects which have their new copy by default, so if * the old object is a string, it is returned directly. * </p> * * @param old * an old object * @return the new copy of the given old object, or null if there is not * one. */ public Object get(Object old) { if (old == null || old.getClass() == String.class) { return old; } return oldNewMap.get(old); } /** * Returns the exception listener of this encoder. * <p> * An encoder always have a non-null exception listener. A default exception * listener is used when the encoder is created. * </p> * * @return the exception listener of this encoder */ public ExceptionListener getExceptionListener() { return listener; } /** * Returns a <code>PersistenceDelegate</code> for the given class type. * <p> * The <code>PersistenceDelegate</code> is determined as following: * <ol> * <li>If a <code>PersistenceDelegate</code> has been registered by calling * <code>setPersistenceDelegate</code> for the given type, it is returned.</li> * <li>If the given type is an array class, a special * <code>PersistenceDelegate</code> for array types is returned.</li> * <li>If the given type is a proxy class, a special * <code>PersistenceDelegate</code> for proxy classes is returned.</li> * <li><code>Introspector</code> is used to check the bean descriptor value * "persistenceDelegate". If one is set, it is returned.</li> * <li>If none of the above applies, the * <code>DefaultPersistenceDelegate</code> is returned.</li> * </ol> * </p> * * @param type * a class type * @return a <code>PersistenceDelegate</code> for the given class type */ public PersistenceDelegate getPersistenceDelegate(Class<?> type) { if (type == null) { return nullPD; // may be return a special PD? } // registered delegate PersistenceDelegate registeredPD = delegates.get(type); if (registeredPD != null) { return registeredPD; } if (type.getName().startsWith( UtilCollectionsPersistenceDelegate.CLASS_PREFIX)) { return utilCollectionsPD; } if (type.isArray()) { return arrayPD; } if (Proxy.isProxyClass(type)) { return proxyPD; } // check "persistenceDelegate" property try { BeanInfo beanInfo = Introspector.getBeanInfo(type); if (beanInfo != null) { PersistenceDelegate pd = (PersistenceDelegate) beanInfo .getBeanDescriptor().getValue("persistenceDelegate"); //$NON-NLS-1$ if (pd != null) { return pd; } } } catch (Exception e) { // Ignored } // default persistence delegate return defaultPD; } /** * Remove the existing new copy of the given old object. * * @param old * an old object * @return the removed new version of the old object, or null if there is * not one */ public Object remove(Object old) { return oldNewMap.remove(old); } /** * Sets the exception listener of this encoder. * * @param listener * the exception listener to set */ public void setExceptionListener(ExceptionListener listener) { this.listener = listener == null ? defaultExListener : listener; } /** * Register the <code>PersistenceDelegate</code> of the specified type. * * @param type * @param delegate */ public void setPersistenceDelegate(Class<?> type, PersistenceDelegate delegate) { if (type == null || delegate == null) { throw new NullPointerException(); } delegates.put(type, delegate); } Object expressionValue(Expression exp) { try { return exp == null ? null : exp.getValue(); } catch (IndexOutOfBoundsException e) { return null; } catch (Exception e) { listener.exceptionThrown(new Exception( "failed to excute expression: " + exp, e)); //$NON-NLS-1$ } return null; } private Statement createNewStatement(Statement oldStat) { Object newTarget = createNewObject(oldStat.getTarget()); Object[] oldArgs = oldStat.getArguments(); Object[] newArgs = new Object[oldArgs.length]; for (int index = 0; index < oldArgs.length; index++) { newArgs[index] = createNewObject(oldArgs[index]); } if (oldStat.getClass() == Expression.class) { return new Expression(newTarget, oldStat.getMethodName(), newArgs); } else { return new Statement(newTarget, oldStat.getMethodName(), newArgs); } } private Object createNewObject(Object old) { Object nu = get(old); if (nu == null) { writeObject(old); nu = get(old); } return nu; } /** * Write an expression of old objects. * <p> * The implementation first check the return value of the expression. If * there exists a new version of the object, simply return. * </p> * <p> * A new expression is created using the new versions of the target and the * arguments. If any of the old objects do not have its new version yet, * <code>writeObject()</code> is called to create the new version. * </p> * <p> * The new expression is then executed to obtained a new copy of the old * return value. * </p> * <p> * Call <code>writeObject()</code> with the old return value, so that more * statements will be executed on its new version to change it into the same * state as the old value. * </p> * * @param oldExp * the expression to write. The target, arguments, and return * value of the expression are all old objects. */ public void writeExpression(Expression oldExp) { if (oldExp == null) { throw new NullPointerException(); } try { // if oldValue exists, no operation Object oldValue = expressionValue(oldExp); if (oldValue == null || get(oldValue) != null) { return; } // copy to newExp Expression newExp = (Expression) createNewStatement(oldExp); // relate oldValue to newValue try { oldNewMap.put(oldValue, newExp.getValue()); } catch (IndexOutOfBoundsException e) { // container does not have any component, set newVal null } // force same state writeObject(oldValue); } catch (Exception e) { listener.exceptionThrown(new Exception( "failed to write expression: " + oldExp, e)); //$NON-NLS-1$ } } /** * Encode the given object into a series of statements and expressions. * <p> * The implementation simply finds the <code>PersistenceDelegate</code> * responsible for the object's class, and delegate the call to it. * </p> * * @param o * the object to encode */ protected void writeObject(Object o) { if (o == null) { return; } getPersistenceDelegate(o.getClass()).writeObject(o, this); } /** * Write a statement of old objects. * <p> * A new statement is created by using the new versions of the target and * arguments. If any of the objects do not have its new copy yet, * <code>writeObject()</code> is called to create one. * </p> * <p> * The new statement is then executed to change the state of the new object. * </p> * * @param oldStat * a statement of old objects */ public void writeStatement(Statement oldStat) { if (oldStat == null) { throw new NullPointerException(); } Statement newStat = createNewStatement(oldStat); try { // execute newStat newStat.execute(); } catch (Exception e) { listener.exceptionThrown(new Exception( "failed to write statement: " + oldStat, e)); //$NON-NLS-1$ } } }