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 555924) +++ src/test/java/org/apache/harmony/beans/tests/java/beans/PersistenceDelegateTest.java (working copy) @@ -21,23 +21,22 @@ import java.beans.Expression; import java.beans.PersistenceDelegate; import java.beans.Statement; - -import java.util.Stack; +import java.beans.XMLDecoder; +import java.beans.XMLEncoder; +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.EmptyStackException; +import java.util.Stack; import junit.framework.TestCase; import org.apache.harmony.beans.tests.support.mock.MockFoo; import org.apache.harmony.beans.tests.support.mock.MockFooStop; - -import java.beans.XMLEncoder; -import java.beans.XMLDecoder; -import java.io.ByteArrayOutputStream; -import java.io.ByteArrayInputStream; -import java.io.DataInputStream; -import java.io.BufferedOutputStream; -import java.io.IOException; -import java.lang.reflect.Field; /** * Test java.beans.PersistenceDelegate */ @@ -251,6 +250,23 @@ assertEquals(value, field); assertEquals(value.getName(), field.getName()); } + + public void test_writeObject_java_lang_reflect_Method() throws SecurityException, NoSuchMethodException{ + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + XMLEncoder encoder = new XMLEncoder(new BufferedOutputStream( + byteArrayOutputStream)); + Method method = Bar.class.getMethod("barTalk", (Class[])null); + + encoder.writeObject(method); + encoder.close(); + DataInputStream stream = new DataInputStream(new ByteArrayInputStream( + byteArrayOutputStream.toByteArray())); + XMLDecoder decoder = new XMLDecoder(stream); + Method aMethod = (Method) decoder.readObject(); + assertEquals(method, aMethod); + assertEquals(method.getName(), aMethod.getName()); + assertEquals("barTalk", aMethod.getName()); + } // <-- Index: src/main/java/java/beans/PersistenceDelegate.java =================================================================== --- src/main/java/java/beans/PersistenceDelegate.java (revision 555924) +++ src/main/java/java/beans/PersistenceDelegate.java (working copy) @@ -1,76 +1,123 @@ -/* - * 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 - * +/* + * 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. + * + * 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 org.apache.harmony.beans.internal.nls.Messages; +package java.beans; +/** + * PersistenceDelegate instances write received bean objects to + * encoders in the form of expressions and statements, which can be evaluated or + * executed to reconstruct the recorded bean objects in a new environment during + * decoding. Expressions are usually used to instantiate bean objects in the new + * environment, and statements are used to initialize their properties if + * necessary. As a result, the reconstructed bean objects become equivalent to + * the original recorded ones in terms of their public states. + * + */ public abstract class PersistenceDelegate { - protected void initialize(Class type, Object oldInstance, - Object newInstance, Encoder out) { - - if (type == null) { - throw new NullPointerException(Messages.getString("beans.4B")); //$NON-NLS-1$ - } + /** + * Default constructor. + */ + public PersistenceDelegate() { + // empty + } - if (out != null) { - PersistenceDelegate pd = out.getPersistenceDelegate(type - .getSuperclass()); - - if (pd != null) { - try { - pd.initialize(type, oldInstance, newInstance, out); - } catch (StackOverflowError err) { - // circular redundancy - // we should catch in order to be compatible with RI - } - } - } else { - throw new NullPointerException( - Messages.getString("beans.4C")); //$NON-NLS-1$ + /** + * Produces a series of expressions and statements for the initialization of + * a bean object's properties. The default implementation simply invokes the + * initialization provided by the super class's + * PersisteneceDelegate instance. + * + * @param type + * the type of the bean + * @param oldInstance + * the original bean object to be recorded + * @param newInstance + * the simmulating new bean object to be initialized + * @param enc + * the encoder to write the outputs to + */ + protected void initialize(Class type, Object oldInstance, + Object newInstance, Encoder enc) { + Class c = type.getSuperclass(); + if (null != c) { + PersistenceDelegate pd = enc.getPersistenceDelegate(c); + pd.initialize(c, oldInstance, newInstance, enc); } } - protected abstract Expression instantiate(Object oldInstance, Encoder out); - - protected boolean mutatesTo(Object oldInstance, Object newInstance) { - boolean bothInstancesAreNull = (oldInstance == null) - && (newInstance == null); - - if (bothInstancesAreNull) { + /** + * Constructs an expression for instantiating an object of the same type as + * the old instance. Any exceptions occured during this process could be + * reported to the exception listener registered in the given encoder. + * + * @param oldInstance + * the old instance + * @param enc + * the encoder that wants to record the old instance + * @return an expression for instantiating an object of the same type as the + * old instance + */ + protected abstract Expression instantiate(Object oldInstance, Encoder enc); + + /** + * Determines whether one object mutates to the other object. One object is + * considered able to mutate to another object if they are indistinguishable + * in terms of behaviors of all public APIs. The default implementation here + * is to return true only if the two objects are instances of the same + * class. + * + * @param o1 + * one object + * @param o2 + * the other object + * @return true if second object mutates to the first object, otherwise + * false + */ + protected boolean mutatesTo(Object o1, Object o2) { + if (null == o1 || null == o2 ) { return false; } - return (oldInstance != null) && (newInstance != null) ? oldInstance - .getClass() == newInstance.getClass() : false; + return o1.getClass() == o2.getClass(); } + /** + * Writes a bean object to the given encoder. First it is checked whether + * the simulating new object can be mutated to the old instance. If yes, it + * is initialized to produce a series of expressions and statements that can + * be used to restore the old instance. Otherwise, remove the new object in + * the simulating new environment and writes an expression that can + * instantiate a new instance of the same type as the old one to the given + * encoder. + * + * @param oldInstance + * the old instance to be written + * @param out + * the encoder that the old instance will be written to + */ public void writeObject(Object oldInstance, Encoder out) { - // nulls are covered by NullPersistenceDelegate - assert oldInstance != null; - Object newInstance = out.get(oldInstance); - if (mutatesTo(oldInstance, newInstance)) { initialize(oldInstance.getClass(), oldInstance, newInstance, out); } else { - out.remove(oldInstance); - - out.writeExpression(instantiate(oldInstance, out)); + out.remove(oldInstance); + Expression exp = instantiate(oldInstance, out); + out.writeExpression(exp); newInstance = out.get(oldInstance); if (newInstance != null) { @@ -79,4 +126,6 @@ } } } + } + Index: src/main/java/java/beans/XMLDecoder.java =================================================================== --- src/main/java/java/beans/XMLDecoder.java (revision 555924) +++ src/main/java/java/beans/XMLDecoder.java (working copy) @@ -1,142 +1,541 @@ -/* - * 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 - * +/* + * 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. + * + * 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.io.IOException; import java.io.InputStream; -import java.util.Iterator; -import java.util.NoSuchElementException; -import java.util.Vector; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Stack; + +import javax.xml.parsers.SAXParserFactory; -import org.apache.harmony.beans.Handler; -import org.xml.sax.InputSource; +import org.xml.sax.Attributes; import org.xml.sax.SAXException; -import org.xml.sax.XMLReader; -import org.xml.sax.helpers.XMLReaderFactory; +import org.xml.sax.SAXParseException; +import org.xml.sax.helpers.DefaultHandler; +/** + * XMLDecoder reads objects from xml created by + * XMLEncoder. + *

The API is similar to ObjectInputStream.

+ * + */ public class XMLDecoder { - private InputStream is = null; + private ClassLoader defaultClassLoader = null; + + private static class DefaultExceptionListener implements ExceptionListener { + + public void exceptionThrown(Exception e) { + e.printStackTrace(); + System.err.println("Continue..."); + } + } - private Object owner = null; + private class SAXHandler extends DefaultHandler { - private ExceptionListener exceptionListener = null; + boolean inJavaElem = false; - private final Vector objects = new Vector(); + HashMap idObjMap = new HashMap(); - private Iterator iterator = null; - - private ClassLoader classLoader = null; + public void characters(char[] ch, int start, int length) + throws SAXException { + if (!inJavaElem) { + return; + } + if (readObjs.size() > 0) { + Elem elem = (Elem) readObjs.peek(); + if (elem.isBasicType) { + String str = new String(ch, start, length); + elem.methodName = elem.methodName == null ? str + : elem.methodName + str; + } + } + } - public XMLDecoder(InputStream is, Object owner, - ExceptionListener exceptionListener, ClassLoader cl) { - this.is = is; - this.owner = owner; - this.exceptionListener = exceptionListener; - this.classLoader = cl; + public void startElement(String uri, String localName, String qName, + Attributes attributes) throws SAXException { + if (!inJavaElem) { + if ("java".equals(qName)) { + inJavaElem = true; + } else { + listener.exceptionThrown(new Exception( + "unknown root element: " + qName)); + } + return; + } + + if ("object".equals(qName)) { + startObjectElem(attributes); + } else if ("array".equals(qName)) { + startArrayElem(attributes); + } else if ("void".equals(qName)) { + startVoidElem(attributes); + } else if ("boolean".equals(qName) || "byte".equals(qName) + || "char".equals(qName) || "class".equals(qName) + || "double".equals(qName) || "float".equals(qName) + || "int".equals(qName) || "long".equals(qName) + || "short".equals(qName) || "string".equals(qName) + || "null".equals(qName)) { + startBasicElem(qName, attributes); + } + } + + private void startObjectElem(Attributes attributes) throws SAXException { + Elem elem = new Elem(); + elem.isExpression = true; + elem.id = attributes.getValue("id"); + elem.idref = attributes.getValue("idref"); + if (elem.idref == null) { + obtainTarget(elem, attributes); + obtainMethod(elem, attributes); + } + + readObjs.push(elem); + } + + private void obtainTarget(Elem elem, Attributes attributes) { + String className = attributes.getValue("class"); + if (className != null) { + try { + elem.target = classForName(className); + } catch (ClassNotFoundException e) { + listener.exceptionThrown(e); + } + } else { + Elem parent = latestUnclosedElem(); + if(parent == null) { + elem.target = owner; + return; + } + elem.target = execute(parent); + } + } + + private void obtainMethod(Elem elem, Attributes attributes) { + elem.methodName = attributes.getValue("method"); + if (elem.methodName != null) { + return; + } + + elem.methodName = attributes.getValue("property"); + if (elem.methodName != null) { + elem.fromProperty = true; + return; + } + + elem.methodName = attributes.getValue("index"); + if (elem.methodName != null) { + elem.fromIndex = true; + return; + } + + elem.methodName = attributes.getValue("field"); + if (elem.methodName != null) { + elem.fromField = true; + return; + } + + elem.methodName = attributes.getValue("owner"); + if (elem.methodName != null) { + elem.fromOwner = true; + return; + } + + elem.methodName = "new"; // default method name + } + + private Class classForName(String className) + throws ClassNotFoundException { + if ("boolean".equals(className)) { + return Boolean.TYPE; + } else if ("byte".equals(className)) { + return Byte.TYPE; + } else if ("char".equals(className)) { + return Character.TYPE; + } else if ("double".equals(className)) { + return Double.TYPE; + } else if ("float".equals(className)) { + return Float.TYPE; + } else if ("int".equals(className)) { + return Integer.TYPE; + } else if ("long".equals(className)) { + return Long.TYPE; + } else if ("short".equals(className)) { + return Short.TYPE; + } else { + return Class.forName(className, true, + defaultClassLoader == null ? Thread.currentThread() + .getContextClassLoader() : defaultClassLoader); + } + } + + private void startArrayElem(Attributes attributes) { + Elem elem = new Elem(); + elem.isExpression = true; + elem.id = attributes.getValue("id"); + try { + // find componet class + Class compClass = classForName(attributes.getValue("class")); + // find length + int length = Integer.parseInt(attributes.getValue("length")); + // execute, new array instance + elem.result = Array.newInstance(compClass, length); + elem.isExecuted = true; + } catch (Exception e) { + listener.exceptionThrown(e); + } + readObjs.push(elem); + } + + private void startVoidElem(Attributes attributes) { + Elem elem = new Elem(); + elem.id = attributes.getValue("id"); + obtainTarget(elem, attributes); + obtainMethod(elem, attributes); + readObjs.push(elem); + } + + private void startBasicElem(String tagName, Attributes attributes) { + Elem elem = new Elem(); + elem.isBasicType = true; + elem.isExpression = true; + elem.id = attributes.getValue("id"); + elem.idref = attributes.getValue("idref"); + elem.target = tagName; + readObjs.push(elem); + } + + public void endElement(String uri, String localName, String qName) + throws SAXException { + if (!inJavaElem) { + return; + } + if ("java".equals(qName)) { + inJavaElem = false; + return; + } + // find the elem to close + Elem toClose = latestUnclosedElem(); + // make sure it is executed + execute(toClose); + // set to closed + toClose.isClosed = true; + // pop it and its children + while (readObjs.pop() != toClose) { + // + } + // push back expression + if (toClose.isExpression) { + readObjs.push(toClose); + } + } + + private Elem latestUnclosedElem() { + for (int i = readObjs.size() - 1; i >= 0; i--) { + Elem elem = (Elem) readObjs.get(i); + if (!elem.isClosed) { + return elem; + } + } + return null; + } + + private Object execute(Elem elem) { + if (elem.isExecuted) { + return elem.result; + } + + // execute to obtain result + try { + if (elem.idref != null) { + elem.result = idObjMap.get(elem.idref); + } else if (elem.isBasicType) { + elem.result = executeBasic(elem); + } else { + elem.result = executeCommon(elem); + } + } catch (Exception e) { + listener.exceptionThrown(e); + } + + // track id + if (elem.id != null) { + idObjMap.put(elem.id, elem.result); + } + + elem.isExecuted = true; + return elem.result; + } + + private Object executeCommon(Elem elem) throws Exception { + // pop args + ArrayList args = new ArrayList(5); + while (readObjs.peek() != elem) { + Elem argElem = (Elem) readObjs.pop(); + args.add(0, argElem.result); + } + // decide method name + String method = elem.methodName; + if (elem.fromProperty) { + method = (args.size() == 0 ? "get" : "set") + + capitalize(method); + } + if (elem.fromIndex) { + Integer index = Integer.valueOf(method); + args.add(0, index); + method = args.size() == 1 ? "get" : "set"; + } + if(elem.fromField) { + Field f = ((Class)elem.target).getField(method); + return (new Expression(f, "get", new Object[] {null})).getValue(); + } + if(elem.fromOwner) { + return owner; + } + + if(elem.target == owner) { + if("getOwner".equals(method)) { + return owner; + } else { + Class[] c = new Class[args.size()]; + for(int i = 0; i < args.size(); i++) { + c[i] = args.get(i).getClass(); + } + Method m = owner.getClass().getMethod(method, c); + return m.invoke(owner, args.toArray()); + } + } + + // execute + Expression exp = new Expression(elem.target, method, args.toArray()); + return exp.getValue(); + } + + private String capitalize(String str) { + StringBuffer buf = new StringBuffer(str); + buf.setCharAt(0, Character.toUpperCase(buf.charAt(0))); + return buf.toString(); + } + + private Object executeBasic(Elem elem) throws Exception { + String tag = (String) elem.target; + String value = elem.methodName; + + if ("null".equals(tag)) { + return null; + } else if ("string".equals(tag)) { + return value; + } else if ("class".equals(tag)) { + return classForName(value); + } else if ("boolean".equals(tag)) { + return Boolean.valueOf(value); + } else if ("byte".equals(tag)) { + return Byte.valueOf(value); + } else if ("char".equals(tag)) { + return new Character(value.charAt(0)); + } else if ("double".equals(tag)) { + return Double.valueOf(value); + } else if ("float".equals(tag)) { + return Float.valueOf(value); + } else if ("int".equals(tag)) { + return Integer.valueOf(value); + } else if ("long".equals(tag)) { + return Long.valueOf(value); + } else if ("short".equals(tag)) { + return Short.valueOf(value); + } else { + throw new Exception("Unknown tag of basic type: " + tag); + } + } + + public void error(SAXParseException e) throws SAXException { + listener.exceptionThrown(e); + } + + public void fatalError(SAXParseException e) throws SAXException { + listener.exceptionThrown(e); + } + + public void warning(SAXParseException e) throws SAXException { + listener.exceptionThrown(e); + } } - public XMLDecoder(InputStream is, Object owner, - ExceptionListener exceptionListener) { - this.is = is; - this.owner = owner; - this.exceptionListener = exceptionListener; + private static class Elem { + String id; + + String idref; + + boolean isExecuted; + + boolean isExpression; + + boolean isBasicType; + + boolean isClosed; + + Object target; + + String methodName; + + boolean fromProperty; + + boolean fromIndex; + + boolean fromField; + + boolean fromOwner; + + Object result; } - public XMLDecoder(InputStream is, Object owner) { - this.is = is; - this.owner = owner; + private InputStream inputStream; + + private ExceptionListener listener; + + private Object owner; + + private Stack readObjs = new Stack(); + + private int readObjIndex = 0; + + /** + * Create a decoder to read from specified input stream. + * + * @param inputStream an input stream of xml + */ + public XMLDecoder(InputStream inputStream) { + this(inputStream, null, null); } - public XMLDecoder(InputStream is) { - this.is = is; + /** + * Create a decoder to read from specified input stream. + * + * @param inputStream an input stream of xml + * @param owner the owner of this decoder + */ + public XMLDecoder(InputStream inputStream, Object owner) { + this(inputStream, owner, null); } - public void setOwner(Object owner) { + /** + * Create a decoder to read from specified input stream. + * + * @param inputStream an input stream of xml + * @param owner the owner of this decoder + * @param listener listen to the exceptions thrown by the decoder + */ + public XMLDecoder(InputStream inputStream, Object owner, + ExceptionListener listener) { + if (inputStream == null) { + throw new IllegalArgumentException("Input stream cannot be null"); + } + if (listener == null) { + listener = new DefaultExceptionListener(); + } + this.inputStream = inputStream; this.owner = owner; - } + this.listener = listener; - public Object readObject() { try { - if (iterator == null) { - initialize(); - } - return iterator.next(); - } catch (NoSuchElementException nsee) { - throw new ArrayIndexOutOfBoundsException(); + SAXParserFactory.newInstance().newSAXParser().parse(inputStream, + new SAXHandler()); + } catch (Exception e) { + listener.exceptionThrown(e); } } - - public Object getOwner() { - return owner; + + public XMLDecoder(InputStream inputStream, Object owner, + ExceptionListener listener, ClassLoader cl) { + this(inputStream, owner, listener); + defaultClassLoader = cl; } - public void setExceptionListener(ExceptionListener exceptionListener) { - this.exceptionListener = exceptionListener; + /** + * Close the input stream of xml data. + */ + public void close() { + try { + inputStream.close(); + } catch (Exception e) { + listener.exceptionThrown(e); + } } + /** + * Returns the exception listener. + * @return the exception listener + */ public ExceptionListener getExceptionListener() { - return exceptionListener; + return listener; } - public void close() { - try { - is.close(); - } catch (IOException ioe) { - handleException(ioe); - } + /** + * Returns the owner of this decoder. + * @return the owner of this decoder + */ + public Object getOwner() { + return owner; } - private void handleException(Exception e) { - if (exceptionListener != null) { - exceptionListener.exceptionThrown(e); + /** + * Reads the next object. + * + * @return the next object + * @exception ArrayIndexOutOfBoundsException if no more objects to read + */ + public Object readObject() { + if (readObjIndex >= readObjs.size()) { + throw new ArrayIndexOutOfBoundsException("no more objects to read"); + } + Elem elem = (Elem) readObjs.get(readObjIndex); + if (!elem.isClosed) { + // bad element, error occured while parsing + throw new ArrayIndexOutOfBoundsException("no more objects to read"); } + readObjIndex++; + return elem.result; } - private void initialize() { - ClassLoader oldCL = null; - - try { - String saxParserClassName = System - .getProperty("org.xml.sax.driver"); //$NON-NLS-1$ - - if (saxParserClassName == null) { - saxParserClassName = "org.apache.xerces.parsers.SAXParser"; //$NON-NLS-1$ - } - XMLReader xmlReader = XMLReaderFactory - .createXMLReader(saxParserClassName); - xmlReader.setContentHandler(new Handler(this, objects)); - if (classLoader != null) { - oldCL = Thread.currentThread().getContextClassLoader(); - Thread.currentThread().setContextClassLoader(classLoader); - } - xmlReader.parse(new InputSource(is)); - if (classLoader != null) { - Thread.currentThread().setContextClassLoader(oldCL); - } - } catch (SAXException saxe) { - saxe.printStackTrace(); - handleException(saxe); - } catch (IOException ioe) { - ioe.printStackTrace(); - handleException(ioe); - } finally { - iterator = objects.iterator(); + /** + * Sets the exception listener. + * + * @param listener an exception listener + */ + public void setExceptionListener(ExceptionListener listener) { + if (listener != null) { + this.listener = listener; } } + + /** + * Sets the owner of this decoder. + * + * @param owner the owner of this decoder + */ + public void setOwner(Object owner) { + this.owner = owner; + } } + + Index: src/main/java/org/apache/harmony/beans/java_lang_reflect_MethodPersistenceDelegate.java =================================================================== --- src/main/java/org/apache/harmony/beans/java_lang_reflect_MethodPersistenceDelegate.java (revision 555924) +++ src/main/java/org/apache/harmony/beans/java_lang_reflect_MethodPersistenceDelegate.java (working copy) @@ -32,7 +32,7 @@ assert oldInstance instanceof Method : oldInstance; Method oldMethod = (Method) oldInstance; Class declClass = oldMethod.getDeclaringClass(); - return new Expression(oldMethod, declClass, "getDeclaredMethod", //$NON-NLS-1$ + return new Expression(oldMethod, declClass, "getMethod", //$NON-NLS-1$ new Object[] { oldMethod.getName(), oldMethod.getParameterTypes() }); }