Index: src/main/java/java/beans/UtilCollectionPersistenceDelegate.java =================================================================== --- src/main/java/java/beans/UtilCollectionPersistenceDelegate.java (revision 0) +++ src/main/java/java/beans/UtilCollectionPersistenceDelegate.java (revision 0) @@ -0,0 +1,69 @@ +/* + * 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 java.beans; + +import java.util.Collection; +import java.util.Iterator; + +class UtilCollectionPersistenceDelegate extends + DefaultPersistenceDelegate { + @Override + protected void initialize(Class type, Object oldInstance, + Object newInstance, Encoder enc) { + + Collection oldList = (Collection) oldInstance, newList = (Collection)newInstance; + Iterator oldIterator = oldList.iterator(), newIterator = newList.iterator(); + for (; oldIterator.hasNext();) { + Expression getterExp = new Expression(oldIterator, "next", null); + try { + // Calculate the old value of the property + Object oldVal = getterExp.getValue(); + + Object newVal = null; + try { + newVal = new Expression(newIterator, "next", null).getValue(); + } catch (ArrayIndexOutOfBoundsException ex) { + // The newInstance has no elements, so current property + // value remains null + } + /* + * Make the target value and current property value equivalent + * in the new environment + */ + if (null == oldVal) { + if (null != newVal) { + // Set to null + Statement setterStm = new Statement(oldInstance, "add", + new Object[] { null }); + enc.writeStatement(setterStm); + } + } else { + PersistenceDelegate pd = enc + .getPersistenceDelegate(oldVal.getClass()); + if (!pd.mutatesTo(oldVal, newVal)) { + Statement setterStm = new Statement(oldInstance, "add", + new Object[] { oldVal }); + enc.writeStatement(setterStm); + } + } + } catch (Exception ex) { + enc.getExceptionListener().exceptionThrown(ex); + } + } + } +} Index: src/main/java/org/apache/harmony/beans/PrimitiveWrapperPersistenceDelegate.java =================================================================== --- src/main/java/org/apache/harmony/beans/PrimitiveWrapperPersistenceDelegate.java (revision 0) +++ src/main/java/org/apache/harmony/beans/PrimitiveWrapperPersistenceDelegate.java (revision 0) @@ -0,0 +1,69 @@ +/* + * 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 org.apache.harmony.beans; + +import java.beans.Encoder; +import java.beans.Expression; +import java.beans.PersistenceDelegate; + +/** + * A special internal PersistenceDelegate for wrapper classes of + * primitive types like int. + * + */ +public class PrimitiveWrapperPersistenceDelegate extends PersistenceDelegate { + + /* + * It's unnecessary to do anything for initialization, because two mutatable + * wrapper objects are actually equivalent already. + */ + @Override + protected void initialize(Class type, Object oldInstance, + Object newInstance, Encoder enc) { + // do nothing + } + + /* + * Instantiates a wrapper object using the constructor taking one String + * parameter except for Character. + */ + @Override + protected Expression instantiate(Object oldInstance, Encoder enc) { + if (oldInstance instanceof Character) { + return new Expression(oldInstance, oldInstance.toString(), + "charAt", new Object[] { new Integer(0) }); //$NON-NLS-1$ + } + return new Expression(oldInstance, oldInstance.getClass(), + "new", new Object[] { oldInstance //$NON-NLS-1$ + .toString() }); + } + + /* + * Two wrapper objects are regarded mutatable if they are equal. + */ + @Override + protected boolean mutatesTo(Object o1, Object o2) { + if (null == o2) { + return false; + } + return o1.equals(o2); + } + +} + Index: src/test/java/org/apache/harmony/beans/tests/java/beans/PersistenceDelegateTest.java =================================================================== --- src/test/java/org/apache/harmony/beans/tests/java/beans/PersistenceDelegateTest.java (revision 556788) +++ src/test/java/org/apache/harmony/beans/tests/java/beans/PersistenceDelegateTest.java (working copy) @@ -31,6 +31,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.EmptyStackException; +import java.util.LinkedList; import java.util.Stack; import junit.framework.TestCase; @@ -266,6 +267,27 @@ assertEquals(method, aMethod); assertEquals(method.getName(), aMethod.getName()); assertEquals("barTalk", aMethod.getName()); + } + + public void test_writeObject_java_util_Collection() { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + XMLEncoder encoder = new XMLEncoder(new BufferedOutputStream( + byteArrayOutputStream)); + LinkedList list = new LinkedList(); + list.add(10); + list.addFirst(2); + System.out.println(encoder.getPersistenceDelegate(LinkedList.class)); + + encoder.writeObject(list); + encoder.close(); + DataInputStream stream = new DataInputStream(new ByteArrayInputStream( + byteArrayOutputStream.toByteArray())); + XMLDecoder decoder = new XMLDecoder(stream); + LinkedList l = (LinkedList) decoder.readObject(); + assertEquals(list, l); + assertEquals(2, l.size()); + assertEquals(new Integer(10), l.get(1)); + } // <-- Index: src/main/java/java/beans/Encoder.java =================================================================== --- src/main/java/java/beans/Encoder.java (revision 556788) +++ src/main/java/java/beans/Encoder.java (working copy) @@ -17,287 +17,364 @@ package java.beans; -import java.util.HashMap; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Collection; +import java.util.Hashtable; import java.util.IdentityHashMap; -import java.util.Vector; -import org.apache.harmony.beans.DefaultPersistenceDelegatesFactory; -import org.apache.harmony.beans.NullPersistenceDelegate; -import org.apache.harmony.beans.ObjectNode; +import org.apache.harmony.beans.*; +/** + * The Encoder, together with PersistenceDelegate + * 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. + *

+ * The Encoder and PersistenceDelegate 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. + *

+ * + */ +@SuppressWarnings("unchecked") public class Encoder { - private ExceptionListener exceptionListener = defaultExListener; + private static final Hashtable delegates = new Hashtable(); - private static final ExceptionListener defaultExListener = new DefaultExceptionListener(); + private static final DefaultPersistenceDelegate defaultPD = new DefaultPersistenceDelegate(); - private static class DefaultExceptionListener implements ExceptionListener { + private static final ArrayPersistenceDelegate arrayPD = new ArrayPersistenceDelegate(); - public void exceptionThrown(Exception exception) { - System.err.println("Exception during encoding:" + exception); //$NON-NLS-1$ - System.err.println("Continue..."); - } - - } - - private static final HashMap, PersistenceDelegate> persistenceDelegates = new HashMap, PersistenceDelegate>(); - - Vector roots = new Vector(); - - IdentityHashMap nodes = new IdentityHashMap(); - - private IdentityHashMap oldNewMap = new IdentityHashMap(); - - public Encoder() { - super(); - } - - public Object get(Object oldInstance) { - if (oldInstance == null || oldInstance instanceof String - || oldInstance == String.class) { - return oldInstance; - } - - return oldNewMap.get(oldInstance); - } - - public Object remove(Object oldInstance) { - // TODO - notify references on node deletion - if (oldInstance == null) { - return null; - } - - getValue(nodes.remove(oldInstance)); - return oldNewMap.remove(oldInstance); - } - - public PersistenceDelegate getPersistenceDelegate(Class type) { - PersistenceDelegate result = persistenceDelegates.get(type); - - if (result == null) { - result = DefaultPersistenceDelegatesFactory - .getPersistenceDelegate(type); - } - - return result; - } - - public void setPersistenceDelegate(Class type, - PersistenceDelegate persistenceDelegate) { - if (type == null || persistenceDelegate == null) { - throw new NullPointerException(); - } - persistenceDelegates.put(type, persistenceDelegate); - } - - protected void writeObject(Object object) { - roots.add(object); - if (object == null) { - return; - } - doWriteObject(object); - } - - void doWriteObject(Object object) { - PersistenceDelegate pd = (object != null) ? getPersistenceDelegate(object - .getClass()) - : new NullPersistenceDelegate(); - - if (pd == null) { - pd = new DefaultPersistenceDelegate(); - } - - pd.writeObject(object, this); - if (isString(object.getClass())) { - nodes.put(object, new ObjectNode(pd.instantiate(object, this))); - } - } - - private Object forceNew(Object old) { - if (old == null) { - return null; - } - Object nu = get(old); - if (nu != null) { - return nu; - } - writeObject(old); - return get(old); - } - - private Object[] forceNewArray(Object oldArray[]) { - if (oldArray == null) { - return null; - } - Object newArray[] = new Object[oldArray.length]; - for (int i = 0; i < oldArray.length; i++) { - newArray[i] = forceNew(oldArray[i]); - } - return newArray; - } - - public void writeStatement(Statement oldStm) { - if (oldStm == null) { - throw new NullPointerException(); - } - try { - // FIXME add target processing here - Object newTarget = forceNew(oldStm.getTarget()); - Object newArgs[] = forceNewArray(oldStm.getArguments()); - Statement statement = new Statement(newTarget, oldStm - .getMethodName(), newArgs); - statement.execute(); - } catch (Exception e) { - getExceptionListener().exceptionThrown(e); - } - } + private static final java_lang_reflect_ProxyPersistenceDelegate proxyPD = new java_lang_reflect_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..."); + } + + } + + 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 java_lang_ClassPersistenceDelegate()); + delegates.put(Field.class, new java_lang_reflect_FieldPersistenceDelegate()); + delegates.put(Method.class, new java_lang_reflect_MethodPersistenceDelegate()); + delegates.put(String.class, new java_lang_StringPersistenceDelegate()); + delegates.put(Proxy.class, new java_lang_reflect_ProxyPersistenceDelegate()); + } + + private ExceptionListener listener = defaultExListener; + + private IdentityHashMap oldNewMap = new IdentityHashMap(); + + /** + * 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. + *

+ * Strings are special objects which have their new copy by default, so if + * the old object is a string, it is returned directly. + *

+ * + * @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 instanceof String) { + return old; + } + return oldNewMap.get(old); + } + + /** + * Returns the exception listener of this encoder. + *

+ * An encoder always have a non-null exception listener. A default exception + * listener is used when the encoder is created. + *

+ * + * @return the exception listener of this encoder + */ + public ExceptionListener getExceptionListener() { + return listener; + } + + /** + * Returns a PersistenceDelegate for the given class type. + *

+ * The PersistenceDelegate is determined as following: + *

    + *
  1. If a PersistenceDelegate has been registered by + * calling setPersistenceDelegate for the given type, it is + * returned.
  2. + *
  3. If the given type is an array class, a special + * PersistenceDelegate for array types is returned.
  4. + *
  5. If the given type is a proxy class, a special + * PersistenceDelegate for proxy classes is returned.
  6. + *
  7. Introspector is used to check the bean descriptor + * value "persistenceDelegate". If one is set, it is returned.
  8. + *
  9. If none of the above applies, the + * DefaultPersistenceDelegate is returned.
  10. + *
+ *

+ * + * @param type + * a class type + * @return a PersistenceDelegate 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 = (PersistenceDelegate) delegates + .get(type); + if (registeredPD != null) { + return registeredPD; + } + + if (Collection.class.isAssignableFrom(type)) { + return new UtilCollectionPersistenceDelegate(); + } + + if (type.isArray()) { + return arrayPD; + } + if (Proxy.isProxyClass(type)) { + return proxyPD; + } + + // check "persistenceDelegate" property + try { + BeanInfo binfo = Introspector.getBeanInfo(type); + if (binfo != null) { + PersistenceDelegate pd = (PersistenceDelegate) binfo + .getBeanDescriptor().getValue("persistenceDelegate"); //$NON-NLS-1$ + if (pd != null) { + return pd; + } + } + } catch (Exception e) { + // ignore + } + + // default persistence delegate + return defaultPD; + } + + private void put(Object old, Object nu) { + oldNewMap.put(old, nu); + } + + /** + * Remvoe 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) { + if (listener == null) { + listener = defaultExListener; + } + this.listener = listener; + } + + /** + * Register the PersistenceDelegate 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); + } + + private Object forceNew(Object old) { + if (old == null) { + return null; + } + Object nu = get(old); + if (nu != null) { + return nu; + } + writeObject(old); + return get(old); + } + + private Object[] forceNewArray(Object oldArray[]) { + if (oldArray == null) { + return null; + } + Object newArray[] = new Object[oldArray.length]; + for (int i = 0; i < oldArray.length; i++) { + newArray[i] = forceNew(oldArray[i]); + } + return newArray; + } + + /** + * Write an expression of old objects. + *

+ * The implementation first check the return value of the expression. If + * there exists a new version of the object, simply return. + *

+ *

+ * 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, + * writeObject() is called to create the new version. + *

+ *

+ * The new expression is then executed to obtained a new copy of the old + * return value. + *

+ *

+ * Call writeObject() 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. + *

+ * + * @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, noop + Object oldValue = oldExp.getValue(); + if (oldValue == null || get(oldValue) != null) { + return; + } + + // copy to newExp + Object newTarget = forceNew(oldExp.getTarget()); + Object newArgs[] = forceNewArray(oldExp.getArguments()); + Expression newExp = new Expression(newTarget, oldExp + .getMethodName(), newArgs); + + // execute newExp + Object newValue = null; + try { + newValue = newExp.getValue(); + } catch (IndexOutOfBoundsException ex) { + // Current Container does not have any component, newVal set + // to null + } + + // relate oldValue to newValue + put(oldValue, newValue); + + // 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. + *

+ * The implementation simply finds the PersistenceDelegate + * responsible for the object's class, and delegate the call to it. + *

+ * + * @param o + * the object to encode + */ + protected void writeObject(Object o) { + if (o == null) { + return; + } + Class type = o.getClass(); + getPersistenceDelegate(type).writeObject(o, this); + } + + /** + * Write a statement of old objects. + *

+ * 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, + * writeObject() is called to create one. + *

+ *

+ * The new statement is then executed to change the state of the new object. + *

+ * + * @param oldStat + * a statement of old objects + */ + public void writeStatement(Statement oldStat) { + if (oldStat == null) { + throw new NullPointerException(); + } + try { + // copy to newStat + Object newTarget = forceNew(oldStat.getTarget()); + Object newArgs[] = forceNewArray(oldStat.getArguments()); + Statement newStat = new Statement(newTarget, oldStat + .getMethodName(), newArgs); + + // execute newStat + newStat.execute(); + } catch (Exception e) { + listener.exceptionThrown(new Exception( + "failed to write statement: " + oldStat, e)); //$NON-NLS-1$ + } + } - private void put(Object old, Object nu) { - oldNewMap.put(old, nu); - } - - public void writeExpression(Expression oldExp) { - if (oldExp == null) { - throw new NullPointerException(); - } - try { - Object oldValue = oldExp.getValue(); - if (oldValue == null || get(oldValue) != null) { - return; - } - - // copy to newExp - Object newTarget = forceNew(oldExp.getTarget()); - Object newArgs[] = forceNewArray(oldExp.getArguments()); - Expression newExp = new Expression(newTarget, oldExp - .getMethodName(), newArgs); - - // execute newExp - Object newValue = null; - try { - newValue = newExp.getValue(); - } catch (IndexOutOfBoundsException ex) { - // Current Container does not have any component, newVal set - // to null - } - - // relate oldValue to newValue - put(oldValue, newValue); - - // force same state - writeObject(oldValue); - } catch (Exception e) { - // TODO - remove written args - getExceptionListener().exceptionThrown(e); - } - } - - public void setExceptionListener(ExceptionListener exceptionListener) { - if (exceptionListener == null) { - exceptionListener = defaultExListener; - } - this.exceptionListener = exceptionListener; - } - - public ExceptionListener getExceptionListener() { - return exceptionListener; - } - - private Object write(Object oldInstance) throws Exception { - if (oldInstance == null) { - return null; - } - - ObjectNode node = nodes.get(oldInstance); - - if (node == null) { - doWriteObject(oldInstance); - node = nodes.get(oldInstance); - } else { - node.addReference(); - } - - return node.getObjectValue(); - } - - Object[] write(Object[] oldInstances) throws Exception { - if (oldInstances != null) { - Object[] newInstances = new Object[oldInstances.length]; - - for (int i = 0; i < oldInstances.length; ++i) { - newInstances[i] = write(oldInstances[i]); - } - return newInstances; - } - return null; - } - - /* - * @param node node to return the value for @return tentative object value - * for given node - */ - private Object getValue(ObjectNode node) { - if (node != null) { - try { - Object result = node.getObjectValue(); - - return result; - } catch (Exception e) { - getExceptionListener().exceptionThrown(e); - } - } - return null; - } - - static boolean isNull(Class type) { - return type == null; - } - - static boolean isPrimitive(Class type) { - return type == Boolean.class || type == Byte.class - || type == Character.class || type == Double.class - || type == Float.class || type == Integer.class - || type == Long.class || type == Short.class; - } - - static boolean isString(Class type) { - return type == String.class; - - } - - static boolean isClass(Class type) { - return type == Class.class; - } - - static boolean isArray(Class type) { - return type.isArray(); - } - - static String getPrimitiveName(Class type) { - String result = null; - - if (type == Boolean.class) { - result = "boolean"; //$NON-NLS-1$ - } else if (type == Byte.class) { - result = "byte"; //$NON-NLS-1$ - } else if (type == Character.class) { - result = "char"; //$NON-NLS-1$ - } else if (type == Double.class) { - result = "double"; //$NON-NLS-1$ - } else if (type == Float.class) { - result = "float"; //$NON-NLS-1$ - } else if (type == Integer.class) { - result = "int"; //$NON-NLS-1$ - } else if (type == Long.class) { - result = "long"; //$NON-NLS-1$ - } else if (type == Short.class) { - result = "short"; //$NON-NLS-1$ - } else if (type == String.class) { - result = "string"; //$NON-NLS-1$ - } else if (type == Class.class) { - result = "class"; //$NON-NLS-1$ - } - - return result; - } } + Index: src/main/java/java/beans/XMLEncoder.java =================================================================== --- src/main/java/java/beans/XMLEncoder.java (revision 556788) +++ src/main/java/java/beans/XMLEncoder.java (working copy) @@ -15,603 +15,888 @@ * limitations under the License. */ + package java.beans; -import java.io.IOException; +import java.awt.SystemColor; +import java.awt.font.TextAttribute; import java.io.OutputStream; -import java.util.Enumeration; -import java.util.HashMap; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Array; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.IdentityHashMap; import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Vector; - -import org.apache.harmony.beans.ObjectNode; -import org.apache.harmony.beans.internal.nls.Messages; +import java.util.List; +/** + * XMLEncoder exnteds Encoder to write out the + * encoded statements and expressions in xml format. The xml can be read by + * XMLDecoder later to restore objects and their states. + *

+ * The API is similar to ObjectOutputStream. + *

+ * + */ public class XMLEncoder extends Encoder { - private OutputStream out; + /* + * Every object written by the encoder has a record. + */ + private static class Record { + boolean born = false; + + // The expression by which the object is created or obtained. + Expression exp = null; - private Object owner; + // Id of the object, if it is referenced more than once. + String id = null; - private final Vector printed = new Vector(); + // Count of the references of the object. + int refCount = 0; - public XMLEncoder(OutputStream out) { - this.out = out; - this.owner = null; - } - - @Override - public void writeObject(Object object) { - super.writeObject(object); - } - - public void setOwner(Object owner) { - this.owner = owner; - } - - public Object getOwner() { - return owner; - } - - @Override - public void writeExpression(Expression oldExp) { + // A list of statements that execute on the object. + ArrayList stats = new ArrayList(); + } - Object oldValue = null; - try { - oldValue = oldExp.getValue(); - } catch (Exception e) { - getExceptionListener() - .exceptionThrown( - new Exception("failed to execute expression: " - + oldExp, e)); + private static final int INDENT_UNIT = 1; + + private static final boolean isStaticConstantsSupported = true; + + // the main record of all root objects + private ArrayList flushPending = new ArrayList(); + + // the record of root objects with a void tag + private ArrayList flushPendingStat = new ArrayList(); + + // keep the pre-required objects for each root object + private ArrayList flushPrePending = new ArrayList(); + + private boolean hasXmlHeader = false; + + private int idSerialNo = 0; + + /* + * if any expression or statement references owner, it is set true in method + * recordStatement() or recordExpressions(), and, at the first time + * flushObject() meets an owner object, it calls the flushOwner() method and + * then set needOwner to false, so that all succeeding flushing of owner + * will call flushExpression() or flushStatement() normally, which will get + * a reference of the owner property. + */ + private boolean needOwner = false; + + private PrintWriter out; + + private Object owner = null; + + private IdentityHashMap records = new IdentityHashMap(); + + private boolean writingObject = false; + + /** + * Construct a XMLEncoder. + * + * @param out + * the output stream where xml is writtern to + */ + public XMLEncoder(OutputStream out) { + if (null != out) { + try { + this.out = new PrintWriter( + new OutputStreamWriter(out, "UTF-8"), true); + } catch (UnsupportedEncodingException e) { + // never occur + e.printStackTrace(); + } + } + } + + /** + * Call flush() first, then write out xml footer and close + * the underlying output stream. + */ + public void close() { + flush(); + out.println(""); + out.close(); + } + + private StringBuffer decapitalize(String s) { + StringBuffer buf = new StringBuffer(s); + buf.setCharAt(0, Character.toLowerCase(buf.charAt(0))); + return buf; + } + + /** + * Writes out all objects since last flush to the output stream. + *

+ * The implementation write the xml header first if it has not been + * writtern. Then all pending objects since last flush are writtern. + *

+ */ + public void flush() { + synchronized (this) { + // write xml header + if (!hasXmlHeader) { + out.println(""); + out.println(""); + hasXmlHeader = true; + } + + // preprocess pending objects + for (Iterator iter = flushPending.iterator(); iter.hasNext();) { + Object o = iter.next(); + Record rec = (Record) records.get(o); + if (rec != null) { + preprocess(o, rec); + } + } + + // flush pending objects + for (Iterator iter = flushPending.iterator(); iter.hasNext();) { + Object o = iter.next(); + flushObject(o, INDENT_UNIT); + // remove flushed obj + iter.remove(); + } + + // clear statement records + records.clear(); + flushPendingStat.clear(); + + // remove all old->new mappings + super.clear(); + } + } + + private void flushBasicObject(Object obj, int indent) { + if( obj instanceof Proxy) { return; } - - if (get(oldValue) != null) { + flushIndent(indent); + if (obj == null) { + out.println(""); + } else if (obj instanceof String) { + Record rec = (Record) records.get(obj); + if( null != rec) { + if (flushPendingStat.contains(obj)) { + flushExpression(obj, rec, indent - 3, true); + } else { + flushExpression(obj, rec, indent - 3, false); + } + return; + } + out.print(""); + flushString((String) obj); + out.println(""); + } else if (obj instanceof Class) { + out.print(""); + out.print(((Class) obj).getName()); + out.println(""); + } else if (obj instanceof Boolean) { + out.print(""); + out.print(obj); + out.println(""); + } else if (obj instanceof Byte) { + out.print(""); + out.print(obj); + out.println(""); + } else if (obj instanceof Character) { + out.print(""); + out.print(obj); + out.println(""); + } else if (obj instanceof Double) { + out.print(""); + out.print(obj); + out.println(""); + } else if (obj instanceof Float) { + out.print(""); + out.print(obj); + out.println(""); + } else if (obj instanceof Integer) { + out.print(""); + out.print(obj); + out.println(""); + } else if (obj instanceof Long) { + out.print(""); + out.print(obj); + out.println(""); + } else if (obj instanceof Short) { + out.print(""); + out.print(obj); + out.println(""); + } else { + getExceptionListener().exceptionThrown( + new Exception("Unknown basic object: " + obj)); + } + } + + private void flushExpression(Object obj, Record rec, int indent, + boolean asStatement) { + // not first time, use idref + if (rec.id != null) { + flushIndent(indent); + out.print(""); return; } - - Object oldTarget = oldExp.getTarget(); - ObjectNode valueNode = null; - Class valueType = null; + // generate id, if necessary + if (rec.refCount > 1) { + rec.id = nameForClass(obj.getClass()) + idSerialNo; + idSerialNo++; + } - // write target - if (!Statement.isPDConstructor(oldExp) - && !Statement.isStaticMethodCall(oldExp)) { - ObjectNode parent; + // flush + Statement stat = asStatement ? new Statement(rec.exp.getTarget(), + rec.exp.getMethodName(), rec.exp.getArguments()) : rec.exp; + flushStatement(stat, rec.id, rec.stats, indent); + } - // XXX investigate - // write(oldTarget); - parent = nodes.get(oldTarget); - if (parent != null) { - parent.addExpression(oldExp); - } + private void flushIndent(int indent) { + for (int i = 0; i < indent; i++) { + out.print(" "); } + } - // write value - valueType = oldValue.getClass(); - valueNode = nodes.get(oldValue); + private void flushObject(Object obj, int indent) { + Record rec = (Record) records.get(obj); + if (rec == null && !isBasicType(obj)) + return; - if (valueNode == null) { + if (obj == owner && this.needOwner) { + flushOwner(obj, rec, indent); + this.needOwner = false; + return; + } - if (isNull(valueType) || isPrimitive(valueType) - || isString(valueType)) { - valueNode = new ObjectNode(oldExp); + if (isBasicType(obj)) { + flushBasicObject(obj, indent); + } else { + if (flushPendingStat.contains(obj)) { + flushExpression(obj, rec, indent, true); } else { - try { - write((Object[])oldExp.getArguments()); - } catch (Exception e) { - getExceptionListener().exceptionThrown(e); - } - valueNode = new ObjectNode(oldExp, nodes); + flushExpression(obj, rec, indent, false); } - - nodes.put(oldValue, valueNode); - } else if (oldExp.getMethodName().equals("new")) { //$NON-NLS-1$ - valueNode.addReference(); - } else { - // XXX the information about referencedExpressions is not - // being used by anyone - // node.addReferencedExpression(oldExp); } - super.writeExpression(oldExp); } - @Override - public void writeStatement(Statement oldStm) { - try { - super.writeStatement(oldStm); - } catch (NullPointerException ignore) { - // ignore exception like RI does - ignore.printStackTrace(); - } - } + private void flushOwner(Object obj, Record rec, int indent) { + if (rec.refCount > 1) { + rec.id = nameForClass(obj.getClass()) + idSerialNo; + idSerialNo++; + } - public void flush() { - writeAll(); - } - - public void close() { - try { - flush(); - out.close(); - } catch (Exception e) { - getExceptionListener().exceptionThrown(e); - } - } + flushIndent(indent); + String tagName = "void"; + out.print("<"); + out.print(tagName); + + // id attribute + if (rec.id != null) { + out.print(" id=\""); + out.print(rec.id); + out.print("\""); + } - private void writeAll() { - Tag mainTag = new Tag("java"); //$NON-NLS-1$ - Enumeration e; + out.print(" property=\"owner\""); - printed.clear(); - NameMaker.clear(); + // open tag, end + if (rec.exp.getArguments().length == 0 && rec.stats.isEmpty()) { + out.println("/>"); + return; + } + out.println(">"); - mainTag.addAttr("version", System.getProperty("java.version")); //$NON-NLS-1$ //$NON-NLS-2$ - mainTag.addAttr("class", "java.beans.XMLDecoder"); //$NON-NLS-1$ //$NON-NLS-2$ + // arguments + for (int i = 0; i < rec.exp.getArguments().length; i++) { + flushObject(rec.exp.getArguments()[i], indent + INDENT_UNIT); + } - printBytes(0, ""); //$NON-NLS-1$ - printBytes(0, mainTag.toStringOnOpen()); + // sub statements + flushSubStatements(rec.stats, indent); - e = roots.elements(); - while (e.hasMoreElements()) { - Object object = e.nextElement(); + // close tag + flushIndent(indent); + out.print(""); + } - if (object != null) { - ObjectNode node = nodes.get(object); + private void flushStatArray(Statement stat, String id, List subStats, + int indent) { + // open tag, begin + flushIndent(indent); + out.print(""); + return; + } + out.println(">"); - printBytes(0, mainTag.toStringOnClose()); - } + // sub statements + flushSubStatements(subStats, indent); - // FIXME processing of default constructor: investigate - private void printObjectTag(int tabCount, Object object, ObjectNode node) { - Class nodeType = null; - - try { - nodeType = node.getObjectType(); - } catch (Exception e) { - Exception e2 = new Exception(Messages.getString( - "beans.3B", node.getInitializer())); //$NON-NLS-1$ - - e2.initCause(e); - getExceptionListener().exceptionThrown(e2); - return; - } + // close tag + flushIndent(indent); + out.println(""); + } - if (isPrimitive(nodeType) || isString(nodeType) || isClass(nodeType)) { - String tagName = getPrimitiveName(nodeType); - Object arg = node.getObjectArguments()[0]; - Tag tag = new Tag(tagName, arg.toString()); - - printBytes(tabCount, tag.toString()); - } else { // if array or complex object - Tag tag = null; - Object[] arguments = node.getObjectArguments(); - boolean objectPrinted = false; - boolean isReferenced = node.getReferencesNumber() > 0; - - if (isArray(nodeType)) { - tag = new Tag("array"); //$NON-NLS-1$ - } else { - tag = new Tag("object"); //$NON-NLS-1$ - } + private void flushStatCommon(Statement stat, String id, List subStats, + int indent) { + // open tag, begin + flushIndent(indent); + String tagName = stat instanceof Expression ? "object" : "void"; + out.print("<"); + out.print(tagName); + + // id attribute + if (id != null) { + out.print(" id=\""); + out.print(id); + out.print("\""); + } - // check if the object presents references - if (isReferenced) { - if (printed.contains(node)) { - String nodeId = node.getId(); - - if (nodeId != null) { - tag.addAttr("idref", node.getId()); //$NON-NLS-1$ - } - - objectPrinted = true; - } else { // if(printed.contains(node) == false - try { - Class type = node.getObjectType(); - - if (type != null) { - // check if it is necessary to assign - // and display *id* attribute to the object - String objectName = NameMaker.getInstanceName(type); - - node.setId(objectName); - tag.addAttr("id", objectName); //$NON-NLS-1$ - } - } catch (Exception e) { - getExceptionListener().exceptionThrown(e); - } - } - } + // special class attribute + if (stat.getTarget() instanceof Class) { + out.print(" class=\""); + out.print(((Class) stat.getTarget()).getName()); + out.print("\""); + } - if (!objectPrinted) { - try { - if (isArray(nodeType)) { - tag.addAttr("class", ((Class) arguments[0]).getName()); //$NON-NLS-1$ - tag.addAttr("length", ((Integer) arguments[1]) //$NON-NLS-1$ - .toString()); - } else { - tag.addAttr("class", ((Class)node.getInitializer().getTarget()).getName()); //$NON-NLS-1$ - tag.addAttr("method", node.getInitializer().getMethodName()); - } - } catch (Exception e) { - getExceptionListener().exceptionThrown(e); - } - } + // method attribute + if (!"new".equals(stat.getMethodName())) { + out.print(" method=\""); + out.print(stat.getMethodName()); + out.print("\""); + } - // preprocessing is done, print it! - if (objectPrinted) { - // if object has already been printed then only print the - // reference - printBytes(tabCount, tag.toStringShortForm()); - } else if (isArray(nodeType) && !node.statements().hasNext()) { - // if we have an empty array - printBytes(tabCount, tag.toStringShortForm()); - } else if (arguments.length == 0 && !node.statements().hasNext() - && !node.expressions().hasNext()) { - // if given tag has no children print the short form of the tag - printBytes(tabCount, tag.toStringShortForm()); - } else { - // the tag has not been printed and contains children, - // let's print them - - printBytes(tabCount, tag.toStringOnOpen()); - - printed.add(node); - - if (isArray(nodeType)) { // if array - Iterator it = node.statements(); - - while (it.hasNext()) { - Statement s = it.next(); - - printVoidTag(++tabCount, s); - --tabCount; - } - } else { // if object - Iterator i1; - Iterator i2; - - for (Object element : arguments) { - if (element != null) { - ObjectNode succNode = nodes.get(element); - - printObjectTag(++tabCount, element, succNode); - } else { - printNullTag(++tabCount); - } - - --tabCount; - } - - i1 = node.expressions(); - while (i1.hasNext()) { - Expression e = i1.next(); - - printVoidTag(++tabCount, e); - --tabCount; - } - - i2 = node.statements(); - while (i2.hasNext()) { - Statement s = i2.next(); - - printVoidTag(++tabCount, s); - --tabCount; - } - } // if object + // open tag, end + if (stat.getArguments().length == 0 && subStats.isEmpty()) { + out.println("/>"); + return; + } + out.println(">"); - printBytes(tabCount, tag.toStringOnClose()); - } - } // if node is of non-trivial type - } + // arguments + for (int i = 0; i < stat.getArguments().length; i++) { + flushObject(stat.getArguments()[i], indent + INDENT_UNIT); + } - private void printVoidTag(int tabCount, Expression expr) { - Object exprValue = null; + // sub statements + flushSubStatements(subStats, indent); - try { - Enumeration enumeration; - Tag tag; - String objectName; - String methodName; - ObjectNode node; - Object[] args; - - exprValue = expr.getValue(); - - // find out, if this object is already printed - enumeration = printed.elements(); - while (enumeration.hasMoreElements()) { - ObjectNode node2 = enumeration.nextElement(); - - if (node2.getObjectValue() == exprValue) { - return; - } - } + // close tag + flushIndent(indent); + out.print(""); + } - node = nodes.get(exprValue); + private void flushStatement(Statement stat, String id, List subStats, + int indent) { + Object target = stat.getTarget(); + String method = stat.getMethodName(); + Object args[] = stat.getArguments(); + + // special case for array + if (Array.class == target && "newInstance".equals(method)) { + flushStatArray(stat, id, subStats, indent); + return; + } + // special case for get(int) and set(int, Object) + if (isGetArrayStat(target, method, args) + || isSetArrayStat(target, method, args)) { + flushStatIndexed(stat, id, subStats, indent); + return; + } + // special case for getProperty() and setProperty(Object) + if (isGetPropertyStat(method, args) || isSetPropertyStat(method, args)) { + flushStatGetterSetter(stat, id, subStats, indent); + return; + } - // find out, if this object has no references to be printed - // System.out.println("---- node.getReferencesNumber() = " + - // node.getReferencesNumber()); - // System.out.println("---- node.getReferencedExpressionsNumber() = - // " + node.getReferencedExpressionsNumber()); + if (isStaticConstantsSupported + && "getField".equals(stat.getMethodName())) { + flushStatField(stat, id, subStats, indent); + return; + } - if (node.getReferencesNumber() == 0) { - return; - } + // common case + flushStatCommon(stat, id, subStats, indent); + } - tag = new Tag("void"); //$NON-NLS-1$ - objectName = NameMaker.getInstanceName(exprValue.getClass()); + private void flushStatField(Statement stat, String id, List subStats, + int indent) { + // open tag, begin + flushIndent(indent); + String tagName = "object"; + out.print("<"); + out.print(tagName); + + // id attribute + if (id != null) { + out.print(" id=\""); + out.print(id); + out.print("\""); + } - node.setId(objectName); - tag.addAttr("id", objectName); //$NON-NLS-1$ + // special class attribute + if (stat.getTarget() instanceof Class) { + out.print(" class=\""); + out.print(((Class) stat.getTarget()).getName()); + out.print("\""); + } - methodName = expr.getMethodName(); - args = expr.getArguments(); + Object target = stat.getTarget(); + if(target == SystemColor.class || target == TextAttribute.class) { + out.print(" field=\""); + out.print(stat.getArguments()[0]); + out.print("\""); + out.println("/>"); - if (methodName.startsWith("get") //$NON-NLS-1$ - && (args.length == 0 || args.length == 1 - && args[0].getClass() == Integer.class) - || methodName.startsWith("set") //$NON-NLS-1$ - && (args.length == 1 || args.length == 2 - && args[0].getClass() == Integer.class)) { - String propertyName = methodName.substring(3); - - if (propertyName.length() > 0) { - tag.addAttr("property", Introspector //$NON-NLS-1$ - .decapitalize(propertyName)); - } - - if (methodName.startsWith("get") && args.length == 1 //$NON-NLS-1$ - || methodName.startsWith("set") && args.length == 2) { //$NON-NLS-1$ - tag.addAttr("index", args[0].toString()); //$NON-NLS-1$ - } - } else { - tag.addAttr("method", expr.getMethodName()); //$NON-NLS-1$ - } + } + else { + out.print(" method=\""); + out.print(stat.getMethodName()); + out.print("\""); + out.println(">"); + Object fieldName = (String) stat.getArguments()[0]; + flushObject(fieldName, indent + INDENT_UNIT); + flushIndent(indent); + out.println(""); + } + } - printBytes(tabCount, tag.toStringOnOpen()); + private void flushStatGetterSetter(Statement stat, String id, + List subStats, int indent) { + // open tag, begin + flushIndent(indent); + String tagName = stat instanceof Expression ? "object" : "void"; + out.print("<"); + out.print(tagName); + + // id attribute + if (id != null) { + out.print(" id=\""); + out.print(id); + out.print("\""); + } - for (int i = tag.hasAttr("index") ? 1 : 0; i < args.length; ++i) { //$NON-NLS-1$ - if (args[i] != null) { - ObjectNode node2 = nodes.get(args[i]); - - printObjectTag(++tabCount, args[i], node2); - } else { - printNullTag(++tabCount); - } + // special class attribute + if (stat.getTarget() instanceof Class) { + out.print(" class=\""); + out.print(((Class) stat.getTarget()).getName()); + out.print("\""); + } - --tabCount; - } + // property attribute + out.print(" property=\""); + out.print(decapitalize(stat.getMethodName().substring(3))); + out.print("\""); + + // open tag, end + if (stat.getArguments().length == 0 && subStats.isEmpty()) { + out.println("/>"); + return; + } + out.println(">"); - printBytes(tabCount, tag.toStringOnClose()); + // arguments + for (int i = 0; i < stat.getArguments().length; i++) { + flushObject(stat.getArguments()[i], indent + INDENT_UNIT); + } - printed.add(node); - } catch (Exception e) { - // TODO - signal problem with expr.getValue() - } + // sub statements + flushSubStatements(subStats, indent); - } + // close tag + flushIndent(indent); + out.print(""); + } - private void printVoidTag(int tabCount, Statement stat) { - Tag tag = new Tag("void"); //$NON-NLS-1$ + private void flushStatIndexed(Statement stat, String id, List subStats, + int indent) { + // open tag, begin + flushIndent(indent); + String tagName = stat instanceof Expression ? "object" : "void"; + out.print("<"); + out.print(tagName); + + // id attribute + if (id != null) { + out.print(" id=\""); + out.print(id); + out.print("\""); + } - String methodName = stat.getMethodName(); - Object[] args = stat.getArguments(); + // special class attribute + if (stat.getTarget() instanceof Class) { + out.print(" class=\""); + out.print(((Class) stat.getTarget()).getName()); + out.print("\""); + } - if (methodName.startsWith("get") //$NON-NLS-1$ - && (args.length == 0 || args.length == 1 - && args[0].getClass() == Integer.class) - || methodName.startsWith("set") //$NON-NLS-1$ - && (args.length == 1 || args.length == 2 - && args[0].getClass() == Integer.class)) { - String propertyName = methodName.substring(3); - - if (propertyName.length() > 0) { - tag.addAttr("property", Introspector //$NON-NLS-1$ - .decapitalize(propertyName)); - } + // index attribute + out.print(" index=\""); + out.print(stat.getArguments()[0]); + out.print("\""); + + // open tag, end + if (stat.getArguments().length == 1 && subStats.isEmpty()) { + out.println("/>"); + return; + } + out.println(">"); - if (methodName.startsWith("get") && args.length == 1 //$NON-NLS-1$ - || methodName.startsWith("set") && args.length == 2) { //$NON-NLS-1$ - tag.addAttr("index", args[0].toString()); //$NON-NLS-1$ - } - } else { - tag.addAttr("method", stat.getMethodName()); //$NON-NLS-1$ - } + // arguments + for (int i = 1; i < stat.getArguments().length; i++) { + flushObject(stat.getArguments()[i], indent + INDENT_UNIT); + } - printBytes(tabCount, tag.toStringOnOpen()); + // sub statements + flushSubStatements(subStats, indent); - for (int i = tag.hasAttr("index") ? 1 : 0; i < args.length; ++i) { //$NON-NLS-1$ - if (args[i] != null) { - ObjectNode node = nodes.get(args[i]); - - printObjectTag(++tabCount, args[i], node); - } else { - printNullTag(++tabCount); - } + // close tag + flushIndent(indent); + out.print(""); + } - --tabCount; - } + private void flushString(String s) { + char c; + for (int i = 0; i < s.length(); i++) { + c = s.charAt(i); + if (c == '<') { + out.print("<"); + } else if (c == '>') { + out.print(">"); + } else if (c == '&') { + out.print("&"); + } else if (c == '\'') { + out.print("'"); + } else if (c == '"') { + out.print("""); + } else { + out.print(c); + } + } + } - printBytes(tabCount, tag.toStringOnClose()); - } + private void flushSubStatements(List subStats, int indent) { + for (int i = 0; i < subStats.size(); i++) { + Statement subStat = (Statement) subStats.get(i); + try { + if (subStat instanceof Expression) { + Expression subExp = (Expression) subStat; + Object obj = subExp.getValue(); + Record rec = (Record) records.get(obj); + flushExpression(obj, rec, indent + INDENT_UNIT, true); + } else { + flushStatement(subStat, null, Collections.EMPTY_LIST, + indent + INDENT_UNIT); + } + } catch (Exception e) { + // should not happen + getExceptionListener().exceptionThrown(e); + } + } + } - private void printNullTag(int tabCount) { - printBytes(tabCount, ""); //$NON-NLS-1$ - } - - private void printBytes(int tabCount, String s) { - try { - String result = ""; //$NON-NLS-1$ + /** + * Returns the owner of this encoder. + * + * @return the owner of this encoder + */ + public Object getOwner() { + return owner; + } - for (int i = 0; i < tabCount; ++i) { - result += ' '; - } - result = result + s + "\n"; //$NON-NLS-1$ - out.write(result.getBytes("UTF-8")); //$NON-NLS-1$ - } catch (IOException ioe) { - ExceptionListener listener = getExceptionListener(); + private boolean isBasicType(Object value) { + return value == null || value instanceof Boolean + || value instanceof Byte || value instanceof Character + || value instanceof Class || value instanceof Double + || value instanceof Float || value instanceof Integer + || value instanceof Long || value instanceof Short + || value instanceof String || value instanceof Proxy; + } - if (listener != null) { - listener.exceptionThrown(ioe); - } - } - } + private boolean isGetArrayStat(Object target, String method, Object[] args) { + return ("get".equals(method) && args.length == 1 + && args[0] instanceof Integer && target.getClass().isArray()); + } - /** - * Escapes '&', '<', '>', '\'', '"' chars. - * - * @param input - * input string to be escaped - * @return string with escaped characters - */ - static String escapeChars(String input) { - StringBuffer sb = new StringBuffer(); - - for (int i = 0; i < input.length(); i++) { - char c = input.charAt(i); - - switch (c) { - case '&': - sb.append("&"); //$NON-NLS-1$ - break; - case '<': - sb.append("<"); //$NON-NLS-1$ - break; - case '>': - sb.append(">"); //$NON-NLS-1$ - break; - case '\'': - sb.append("'"); //$NON-NLS-1$ - break; - case '"': - sb.append("""); //$NON-NLS-1$ - break; - default: - sb.append(c); - break; - } - } - return sb.toString(); - } + private boolean isGetPropertyStat(String method, Object[] args) { + return (method.startsWith("get") && method.length() > 3 && args.length == 0); + } - /** - * This class is used by XMLEncoder to store XML tag information. - */ - static class Tag { + private boolean isSetArrayStat(Object target, String method, Object[] args) { + return ("set".equals(method) && args.length == 2 + && args[0] instanceof Integer && target.getClass().isArray()); + } - String name; + private boolean isSetPropertyStat(String method, Object[] args) { + return (method.startsWith("set") && method.length() > 3 && args.length == 1); + } - LinkedHashMap attrs; + private String nameForClass(Class c) { + if (c.isArray()) { + return nameForClass(c.getComponentType()) + "Array"; + } else { + String name = c.getName(); + int i = name.lastIndexOf('.'); + if (-1 == i) { + return name; + } else { + return name.substring(i + 1); + } + } + } - String characters; + /* + * The preprocess removes unused statements and counts references of every + * object + */ + private void preprocess(Object obj, Record rec) { + if (isBasicType(obj) && writingObject) { + return; + } - public Tag(String name) { - this.name = name; - this.attrs = new LinkedHashMap(); - this.characters = null; - } + // count reference + rec.refCount++; - public Tag(String name, String characters) { - this.name = name; - this.attrs = new LinkedHashMap(); - this.characters = characters; - } + // do things only one time for each record + if (rec.refCount > 1) { + return; + } - public boolean hasAttr(String attrName) { - return attrs.get(attrName) != null; - } + // deal with 'field' property + try { + if (isStaticConstantsSupported + && "getField".equals(((Record) records.get(rec.exp + .getTarget())).exp.getMethodName())) { + records.remove(obj); + } + } catch (NullPointerException e) { + // do nothing, safely + } - public void addAttr(String attrName, String attrValue) { - attrs.put(attrName, attrValue); - } + // do it recursively + if (null != rec.exp) { + Object args[] = rec.exp.getArguments(); + for (int i = 0; i < args.length; i++) { + Record argRec = (Record) records.get(args[i]); + if (argRec != null) { + preprocess(args[i], argRec); + } + } + } - public String toStringOnOpenUnfinished() { - String result = "<" + name; //$NON-NLS-1$ - Iterator i = attrs.keySet().iterator(); - - while (i.hasNext()) { - String attrName = i.next(); - String attrValue = attrs.get(attrName); + for (Iterator iter = rec.stats.iterator(); iter.hasNext();) { + Statement subStat = (Statement) iter.next(); + if (subStat instanceof Expression) { + try { + Expression subExp = (Expression) subStat; + Record subRec = (Record) records.get(subExp.getValue()); + if (subRec == null || subRec.exp == null + || subRec.exp != subExp) { + iter.remove(); + continue; + } + preprocess(subExp.getValue(), subRec); + if (subRec.stats.isEmpty()) { + if (isGetArrayStat(subExp.getTarget(), subExp + .getMethodName(), subExp.getArguments()) + || isGetPropertyStat(subExp.getMethodName(), + subExp.getArguments())) { + iter.remove(); + continue; + } + } + } catch (Exception e) { + getExceptionListener().exceptionThrown(e); + iter.remove(); + } + continue; + } - result += " " + attrName + "=\"" + attrValue + "\""; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - } - return result; - } + Object subStatArgs[] = subStat.getArguments(); + for (int i = 0; i < subStatArgs.length; i++) { + Record argRec = (Record) records.get(subStatArgs[i]); + if (argRec != null) { + preprocess(subStatArgs[i], argRec); + } + } + } + } - public String toStringOnOpen() { - return toStringOnOpenUnfinished() + ">"; //$NON-NLS-1$ - } + private void recordExpression(Object value, Expression exp) { + // record how a new object is created or obtained + Record rec = (Record) records.get(value); + if (rec == null) { + rec = new Record(); + records.put(value, rec); + } - public String toStringShortForm() { - return toStringOnOpenUnfinished() + "/>"; //$NON-NLS-1$ - } + if (rec.exp == null) { + // it is generated by its sub stats + for (Iterator iter = rec.stats.iterator(); iter.hasNext();) { + Statement stat = (Statement) iter.next(); + try { + if (stat instanceof Expression) { + Expression expr = (Expression) stat; + Object subObj = expr.getValue(); + // if(expr.getTarget().getClass() == + // Class.class.getClass()) + flushPrePending.add(value); + } + } catch (Exception e) { + e.printStackTrace(); + } - public String toStringOnClose() { - return ""; //$NON-NLS-1$ //$NON-NLS-2$ - } + } + } - public String toStringOnCharacters() { - return XMLEncoder.escapeChars(characters); - } + rec.exp = exp; - @Override - public String toString() { - return toStringOnOpen() + toStringOnCharacters() - + toStringOnClose(); - } - } + // deal with 'owner' property + if (value == owner && owner != null) { + needOwner = true; + } - static class NameMaker { + // also record as a statement + recordStatement(exp); + } - private static final HashMap numOfExemplars = new HashMap(); + private void recordStatement(Statement stat) { + // deal with 'owner' property + if (stat.getTarget() == owner && owner != null) { + needOwner = true; + } - public static void clear() { - numOfExemplars.clear(); - } + // record how a statement affects the target object + Record rec = (Record) records.get(stat.getTarget()); + if (rec == null) { + rec = new Record(); + records.put(stat.getTarget(), rec); + } + rec.stats.add(stat); + } - private static String getCompName(Class clz) { - if (clz.isArray()) { - return getCompName(clz.getComponentType()) + "Array"; //$NON-NLS-1$ - } - return clz.getName().substring(clz.getName().lastIndexOf(".") + 1); //$NON-NLS-1$ - } + /** + * Sets the owner of this encoder. + * + * @param owner + * the owner to set + */ + public void setOwner(Object owner) { + this.owner = owner; + } + + /** + * Records the expression so that it can be writtern out later, then calls + * super implementation. + */ + public void writeExpression(Expression oldExp) { + boolean oldWritingObject = writingObject; + writingObject = true; + // get expression value + Object oldValue = null; + try { + oldValue = oldExp.getValue(); + } catch (Exception e) { + getExceptionListener() + .exceptionThrown( + new Exception("failed to execute expression: " + + oldExp, e)); + return; + } + + // check existence + if (get(oldValue) != null && (!(oldValue instanceof String) || oldWritingObject)) { + return; + } - public static String getInstanceName(Class type) { - String result = null; + // record how the object is obtained + if (!isBasicType(oldValue) || (oldValue instanceof String && !oldWritingObject)) { + recordExpression(oldValue, oldExp); + } - String fullName; - String shortName; - Integer iNum; - - if (type.isArray()) { - fullName = getCompName(type); - shortName = fullName; - } else { - fullName = type.getName(); - shortName = fullName.substring(fullName.lastIndexOf(".") + 1); //$NON-NLS-1$ - } - iNum = numOfExemplars.get(shortName); - if (iNum == null) { - numOfExemplars.put(shortName, new Integer(0)); - result = shortName + "0"; //$NON-NLS-1$ - } else { - int newValue = iNum.intValue() + 1; + super.writeExpression(oldExp); + writingObject = oldWritingObject; + } + + /** + * Records the object so that it can be writtern out later, then calls super + * implementation. + */ + public void writeObject(Object o) { + synchronized (this) { + boolean oldWritingObject = writingObject; + writingObject = true; + try { + super.writeObject(o); + } finally { + writingObject = oldWritingObject; + } + + // root object? + if (!writingObject) { + // add to pending + flushPending.addAll(flushPrePending); + flushPendingStat.addAll(flushPrePending); + flushPrePending.clear(); + if (flushPending.contains(o)) { + flushPrePending.remove(o); + flushPendingStat.remove(o); + } else { + flushPending.add(o); + } + if (needOwner) { + this.flushPending.remove(owner); + this.flushPending.add(0, owner); + } + } + } + } + + /** + * Records the statement so that it can be writtern out later, then calls + * super implementation. + */ + public void writeStatement(Statement oldStat) { + // record how the object is changed + recordStatement(oldStat); + + super.writeStatement(oldStat); + } - result = shortName + Integer.toString(newValue); - numOfExemplars.put(shortName, new Integer(newValue)); - } - return result; - } - } } + +