From: <sim...@us...> - 2009-03-01 21:29:26
|
Revision: 890 http://zkforge.svn.sourceforge.net/zkforge/?rev=890&view=rev Author: simon_massey Date: 2009-03-01 21:29:10 +0000 (Sun, 01 Mar 2009) Log Message: ----------- Trying to write to a map. Modified Paths: -------------- trunk/zktradespace/.classpath trunk/zktradespace/pom.xml trunk/zktradespace/src/main/java/org/zkoss/nirvana/ConsumeEventWrapper.java trunk/zktradespace/src/main/java/org/zkoss/nirvana/NirvanaChannelSubscription.java trunk/zktradespace/src/main/java/org/zkoss/nirvana/stockinfo/StockRowRenderer.java trunk/zktradespace/src/main/webapp/WEB-INF/web.xml Added Paths: ----------- trunk/zktradespace/src/main/java/org/zkoss/dynclass/ trunk/zktradespace/src/main/java/org/zkoss/dynclass/BeanCreator.java trunk/zktradespace/src/main/java/org/zkoss/dynclass/ClassCreator.java trunk/zktradespace/src/main/java/org/zkoss/dynclass/ClassDefinition.java trunk/zktradespace/src/main/java/org/zkoss/dynclass/ConstantPool.java trunk/zktradespace/src/main/java/org/zkoss/dynclass/DynBeanInfoBase.java trunk/zktradespace/src/main/java/org/zkoss/dynclass/DynClassLoader.java trunk/zktradespace/src/main/java/org/zkoss/dynclass/Handler.java trunk/zktradespace/src/main/java/org/zkoss/dynclass/MethodDefinition.java trunk/zktradespace/src/main/java/org/zkoss/dynclass/MethodDescription.java trunk/zktradespace/src/main/java/org/zkoss/dynclass/PropertyMethodNameTx.java trunk/zktradespace/src/main/java/org/zkoss/dynclass/RTCompiler.java trunk/zktradespace/src/main/java/org/zkoss/dynclass/Translator.java trunk/zktradespace/src/main/java/org/zkoss/nirvana/ChannelInWrapper.java trunk/zktradespace/src/main/java/org/zkoss/nirvana/ChannelOutWrapper.java trunk/zktradespace/src/main/java/org/zkoss/nirvana/NirvanaChannelAdaptor.java trunk/zktradespace/src/main/java/org/zkoss/nirvana/NirvanaChannelWriter.java trunk/zktradespace/src/main/java/org/zkoss/nirvana/NotImplementedMap.java trunk/zktradespace/src/main/webapp/chat.zul trunk/zktradespace/src/test/ trunk/zktradespace/src/test/java/ trunk/zktradespace/src/test/java/org/ trunk/zktradespace/src/test/java/org/zkoss/ trunk/zktradespace/src/test/java/org/zkoss/dynclass/ trunk/zktradespace/src/test/java/org/zkoss/dynclass/TestDynamicBeans.java trunk/zktradespace/src/test/java/org/zkoss/nirvana/ Removed Paths: ------------- trunk/zktradespace/src/main/java/org/zkoss/nirvana/ChannelWrapper.java Modified: trunk/zktradespace/.classpath =================================================================== --- trunk/zktradespace/.classpath 2009-03-01 13:23:10 UTC (rev 889) +++ trunk/zktradespace/.classpath 2009-03-01 21:29:10 UTC (rev 890) @@ -2,6 +2,7 @@ <classpath> <classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/> <classpathentry kind="src" path="src/main/java"/> + <classpathentry kind="src" path="src/test/java"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> <classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/> <classpathentry kind="output" path="target/classes"/> Modified: trunk/zktradespace/pom.xml =================================================================== --- trunk/zktradespace/pom.xml 2009-03-01 13:23:10 UTC (rev 889) +++ trunk/zktradespace/pom.xml 2009-03-01 21:29:10 UTC (rev 890) @@ -17,6 +17,7 @@ <commons-io.version>1.4</commons-io.version> <log4j.version>1.2.15</log4j.version> <servlet-api.version>2.3</servlet-api.version> + <cglib.version>2.2</cglib.version> </properties> <dependencies> <!-- tradespace app --> @@ -67,7 +68,7 @@ <version>${xerces.version}</version> <scope>compile</scope> </dependency> - + <!-- ZK --> <dependency> <groupId>org.zkoss.zk</groupId> Added: trunk/zktradespace/src/main/java/org/zkoss/dynclass/BeanCreator.java =================================================================== --- trunk/zktradespace/src/main/java/org/zkoss/dynclass/BeanCreator.java (rev 0) +++ trunk/zktradespace/src/main/java/org/zkoss/dynclass/BeanCreator.java 2009-03-01 21:29:10 UTC (rev 890) @@ -0,0 +1,165 @@ +package org.zkoss.dynclass; + +import java.util.Map; +import java.util.HashMap; +import java.util.Set; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Arrays; +import java.util.List; +import java.util.ArrayList; +import java.util.Collections; + +import java.lang.reflect.Method; +import java.lang.reflect.InvocationHandler; + +/** + * <p>BeanCreator provides methods for representing a Map as a JavaBean. + * The <code>createBeanFromMap()</code> method performs the entire conversion + * in a single step. This is the most convenient way to perform conversion; + * however it is not the fastest, and may constribute to memory retention + * (i.e. leaks) in some cases - see below.</p> + * + * <p>There is also a two-step Map-to-JavaBean process that is + * more efficient and eliminates memory concerns, which can be used when + * the set of properties on the generated bean is known in advance. First, + * create the JavaBeans Class with <code>createClassForProperties()</code>. + * Cache this class for the life of the application (e.g. in a static + * variable). Then, when a JavaBean with this property set is needed, call + * <code>createBean()</code> with a Map reflecting the property values.</p> + * + * <p>Example: + * <blockquote><code> + * // Class variable:<br> + * private static final Class userBeanClass = BeanCreator.createClassForProperties(new String[] { "userName", "email" });<br> + * // ...<br> + * Map map = ...; // create map with values for "userName" and "email"<br> + * Object bean = BeanCreator.createBean(userBeanClass, map); + * </code></blockquote> + * </p> + * <p><b>Memory issue:</b> BeanCreator will not retain references to + * Classes it generates, since this would prevent garbage collection of + * the Class instances. However, many third-party JavaBeans libraries + * keep static caches of Classes they have analyzed, and do not provide for + * releasing cached resources. This will not be noticable in short-lived + * applications, but long-running servers could be affected. If you are + * concerned about this, use <code>createClassFromPropertySet()</code> to + * generate a fixed set of Classes, and instantiate them with + * <code>createBean()</code>.</p> + * + * <p><b>Property names:</b> Any String can be used as a property name, + * even if it is not a valid Java identifier. If BeanCreator encounters a + * property name that does not follow the usual JavaBeans casing conventions + * (or contains non-identifier characters), BeanCreator will generate a + * BeanInfo class for the bean class. This is transparent to clients and + * bean inspection tools.</p> + * + */ +public class BeanCreator { + + private BeanCreator() { + } + + /** + * <p>Generate a object with get and set methods for every key in the map + * that can be translated into a JavaBeans property name. See class + * documentation for possible memory issues. + */ + public static Object createBeanFromMap(Map map) throws Exception { + + return createBean(createClassForProperties(map.keySet()), map); + } + + /** + * Create a JavaBeans class from the given property list. + */ + public static Class createClassForProperties(String[] props) { + + HashSet hs = new HashSet(); + hs.addAll(Arrays.asList(props)); + return createClassForProperties(hs); + } + + /** + * Create a JavaBeans class from the given property set. Instances of + * the class can be instantiated by <code>createBean</code>. + */ + public static Class createClassForProperties(Set keySet) { + + List methods = new ArrayList(keySet.size() * 2); + Iterator iter = keySet.iterator(); + boolean needsBeanInfo = false; + + while (iter.hasNext()) { + Object key = iter.next(); + if (!(key instanceof String)) { + continue; + } + String name = (String) key; + String suffix = PropertyMethodNameTx.encodeProperty(name); + + boolean isStandardProperty = suffix.length() == name.length(); + + String getterPrefix = (isStandardProperty? "get" : "g$t"); + methods.add(new MethodDefinition(getterPrefix + suffix, + Object.class, + null)); + String setterPrefix = (isStandardProperty? "set" : "s$t"); + methods.add(new MethodDefinition(setterPrefix + suffix, + Void.TYPE, + new Class[] { Object.class })); + needsBeanInfo |= !isStandardProperty; + } + + MethodDefinition[] ml = (MethodDefinition[]) + methods.toArray(new MethodDefinition[methods.size()]); + ClassDefinition classDef = new ClassDefinition(Object.class, null, ml); + + Class cls = ClassCreator.defineClass(classDef); + if (needsBeanInfo) { + + ClassDefinition infoClass = new ClassDefinition(DynBeanInfoBase.class); + DynClassLoader cl = (DynClassLoader) cls.getClassLoader(); + cl.defineClass(infoClass, cls.getName() + "BeanInfo"); + } + return cls; + } + + /** + * Create a JavaBean with the given class. Property values are + * provided by propertyMap. + * @param beanClass a JavaBeans class generated by <code>BeanCreator.createClassForProperties()</code> + */ + public static Object createBean(Class beanClass, Map propertyMap) throws Exception { + + return ClassCreator.newInstance(beanClass, + new MapInvocationHandler(propertyMap)); + } + + private static class MapInvocationHandler implements InvocationHandler { + + private Map map; + + MapInvocationHandler(Map map) { + + this.map = map; + } + + public Object invoke(Object p, Method m, Object[] args) { + + String name = m.getName(); + String propName = PropertyMethodNameTx.decodeProperty(name, 3); + + if (name.startsWith("get") || name.startsWith("g$t")) { + return map.get(propName); + } + else if (name.startsWith("set") || name.startsWith("s$t")) { + map.put(propName, args[0]); + return null; + } + else { + throw new IllegalArgumentException("Can't implement: " + name); + } + } + } +} Added: trunk/zktradespace/src/main/java/org/zkoss/dynclass/ClassCreator.java =================================================================== --- trunk/zktradespace/src/main/java/org/zkoss/dynclass/ClassCreator.java (rev 0) +++ trunk/zktradespace/src/main/java/org/zkoss/dynclass/ClassCreator.java 2009-03-01 21:29:10 UTC (rev 890) @@ -0,0 +1,63 @@ +package org.zkoss.dynclass; + +import java.io.ByteArrayOutputStream; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.InvocationHandler; + +import java.util.Map; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Collections; + +/** + * ClassCreator turns a ClassDefinition into a real-live Class. + * It also has utility methods for constructing instances of + * classes it creates. + */ +public class ClassCreator { + + private static DynClassLoader classLoader = new DynClassLoader(); + + /** + * Create a new instance of the given class, which must have been + * created by ClassCreator. Method calls will be dispatched to + * the given InvocationHandler. + */ + public static Object newInstance(Class cls, InvocationHandler impl) throws Exception { + + if (cls == null) { + throw new IllegalArgumentException("Class is null"); + } + if (impl == null) { + throw new IllegalArgumentException("Handler is null"); + } + ClassLoader cl = cls.getClassLoader(); + if (!(cl instanceof DynClassLoader)) { + throw new IllegalArgumentException("Not a DynClass: " + cls.getName()); + } + + return ((DynClassLoader)cl).instantiate(cls, impl); + } + + /* + public static Object newInstance(Class cls, + Object[] args, + InvocationHandler impl) throws Exception { + } + */ + + /** + * Create a Class from the given description. Recent definitions are + * cached, so if an equivalent ClassDefinition was provided recently + * then there is a good chance the same class will be returned. + */ + public static Class defineClass(ClassDefinition def) { + + if (classLoader.getMethodCount() > 300) { + // put current loader out to pasture + classLoader = new DynClassLoader(); + } + return classLoader.defineClass(def); + } +} Added: trunk/zktradespace/src/main/java/org/zkoss/dynclass/ClassDefinition.java =================================================================== --- trunk/zktradespace/src/main/java/org/zkoss/dynclass/ClassDefinition.java (rev 0) +++ trunk/zktradespace/src/main/java/org/zkoss/dynclass/ClassDefinition.java 2009-03-01 21:29:10 UTC (rev 890) @@ -0,0 +1,140 @@ +package org.zkoss.dynclass; + +import java.util.Iterator; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.ArrayList; +import java.util.Collections; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +/** + * This class provides sufficient information for generating a java + * Class. + */ +public class ClassDefinition { + + private Class superclass; + private List interfaces; + + private List methodList; + + private boolean noargCtor = false; + + /** + * Create a ClassDefinition with the given + * interfaces. Implementations of + * interface methods and abstract methods will be created implicitly. + * @param interfaces the interfaces to implement in the generated + * class. null and 0-length arrays are equivalent. + */ + public ClassDefinition(Class[] interfaces) { + + this(Object.class, interfaces, null); + } + + /** + * Create a ClassDefinition with the given superclass and + * interfaces. Implementations of + * interface methods and abstract methods will be created implicitly. + * @param interfaces the interfaces to implement in the generated + * class. null and 0-length arrays are equivalent. + */ + public ClassDefinition(Class superclass, + Class[] interfaces) { + + this(superclass, interfaces, null); + } + + /** + * Create a ClassDefinition with the given superclass, + * interfaces and methods to implement or override. Note that + * implementations of + * interface methods and abstract methods will be created implicitly, + * so the only methods that need to be explicity specified are + * non-abstract methods that you wish to override. + * @param interfaces the interfaces to implement in the generated + * class. null and 0-length arrays are equivalent. + * @param methods the list of methods to override. In addition to + * methods specified in this list, abstract methods will be implemented + * in the generated class. + * null and 0-length arrays are equivalent. + */ + public ClassDefinition(Class superclass, + Class[] interfaces, + MethodDefinition[] methods) { + + if (superclass == null) { + throw new IllegalArgumentException("null superclass"); + } + this.superclass = superclass; + this.interfaces = (interfaces==null)? Collections.EMPTY_LIST : + Collections.unmodifiableList(Arrays.asList(interfaces)); + this.methodList = (methods==null)? Collections.EMPTY_LIST : + Collections.unmodifiableList(Arrays.asList(methods)); + } + + // This constructor creates a definition with the given superclass + // and no interfaces or methods. The generated class will have a + // no-argument constructor (and no other constructors). Really only + // for creating BeanInfo's. + ClassDefinition(Class superclass) { + + this(superclass, null); + noargCtor = true; + } + + /** + * Return the superclass of the defined class. + */ + public Class getSuperclass() { + + return superclass; + } + + /** + * Return the interfaces implemented by the defined class. + */ + public Class[] getInterfaces() { + + return (Class[]) interfaces.toArray(new Class[interfaces.size()]); + } + + /** + * Return the methods of the defined class. + */ + public List getMethodDefinitions() { + + return methodList; + } + + boolean getMakeCtorNoarg() { + + return noargCtor; + } + + public int hashCode() { + + return superclass.hashCode() + interfaces.hashCode() + methodList.hashCode(); + } + + public boolean equals(Object other) { + + if (!(other instanceof ClassDefinition)) { + return false; + } + + if (this == other) { + return true; + } + + ClassDefinition rhs = (ClassDefinition) other; + + return this.superclass.equals(rhs.superclass) && + this.interfaces.equals(rhs.interfaces) && + this.methodList.equals(rhs.methodList) && + this.noargCtor == rhs.noargCtor; + } +} Added: trunk/zktradespace/src/main/java/org/zkoss/dynclass/ConstantPool.java =================================================================== --- trunk/zktradespace/src/main/java/org/zkoss/dynclass/ConstantPool.java (rev 0) +++ trunk/zktradespace/src/main/java/org/zkoss/dynclass/ConstantPool.java 2009-03-01 21:29:10 UTC (rev 890) @@ -0,0 +1,188 @@ +package org.zkoss.dynclass; + +import java.util.Map; +import java.util.HashMap; +import java.util.Arrays; +import java.util.List; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; + +import java.io.OutputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +class ConstantPool { + + private final class ByIndexComparator implements Comparator { + + public int compare(Object lhs, Object rhs) { + + Integer lhsI = (Integer) constant2Index.get(lhs); + Integer rhsI = (Integer) constant2Index.get(rhs); + return lhsI.intValue() - rhsI.intValue(); + } + } + + private static final Byte CPutf8 = new Byte((byte)1); + private static final Byte CPint = new Byte((byte)3); + private static final Byte CPfloat = new Byte((byte)4); + private static final Byte CPlong = new Byte((byte)5); + private static final Byte CPdouble = new Byte((byte)6); + private static final Byte CPclass = new Byte((byte)7); + private static final Byte CPstring = new Byte((byte)8); + private static final Byte CPfieldRef = new Byte((byte)9); + private static final Byte CPmethodRef = new Byte((byte)10); + private static final Byte CPintMethodRef = new Byte((byte)11); + private static final Byte CPnameAndType = new Byte((byte)12); + + private Map constant2Index = new HashMap(); + + private int currentSize = 1; + + ConstantPool() { + } + + int getCount() { + + return currentSize; + } + + void writePool(DataOutputStream data) throws IOException { + + List entryList = new ArrayList(constant2Index.size()); + entryList.addAll(constant2Index.keySet()); + + Collections.sort(entryList, new ByIndexComparator()); + + Iterator entryIter = entryList.iterator(); + while (entryIter.hasNext()) { + writeCpEntry(data, (List) entryIter.next()); + } + } + + private void writeCpEntry(DataOutputStream data, + List entry) throws IOException { + + // put constant, write to data + Iterator iter = entry.iterator(); + + byte tag = ((Byte)iter.next()).byteValue(); + data.writeByte(tag); + + while (iter.hasNext()) { + Object value = iter.next(); + if (value instanceof Short) { + data.writeShort(((Short)value).shortValue()); + } + else if (value instanceof Integer) { + data.writeInt(((Integer)value).intValue()); + } + else if (value instanceof String) { + byte[] bytes = ((String)value).getBytes("UTF8"); + data.writeShort((short)bytes.length); + data.write(bytes); + } + else if (value instanceof Float) { + data.writeFloat(((Float)value).floatValue()); + } + else if (value instanceof Double) { + data.writeDouble(((Double)value).doubleValue()); + } + else if (value instanceof Long) { + data.writeLong(((Long)value).longValue()); + } + else { + throw new IOException("Invalid type: " + value); + } + } + } + + private short addConst(Object[] constData) { + + List rep = Arrays.asList(constData); + Integer index = (Integer) constant2Index.get(rep); + if (index != null) { + return index.shortValue(); + } + + short slot = (short) currentSize; + constant2Index.put(rep, new Integer(currentSize)); + currentSize++; + if (constData[0].equals(CPlong) || (constData[0].equals(CPdouble))) { + currentSize++; + } + return slot; + } + + short utf8Const(String src) { + + return addConst(new Object[] { CPutf8, src }); + } + + short intConst(int value) { + + return addConst(new Object[] { CPint, new Integer(value) }); + } + + short floatConst(float value) { + + return addConst(new Object[] { CPfloat, new Float(value) }); + } + + short longConst(long value) { + + return addConst(new Object[] { CPlong, new Long(value) }); + } + + short doubleConst(double value) { + + return addConst(new Object[] { CPdouble, new Double(value) }); + } + + short classConst(String classname) { + + short index = utf8Const(classname); + return addConst(new Object[] { CPclass, new Short(index) }); + } + + short stringConst(String value) { + + short index = utf8Const(value); + return addConst(new Object[] { CPstring, new Short(index) }); + } + + short fieldRefConst(String classname, String name, String descriptor) { + + Short classnameIndex = new Short(classConst(classname)); + Short nameTypeIndex = new Short(nameAndTypeConst(name, descriptor)); + return addConst(new Object[] { CPfieldRef, classnameIndex, nameTypeIndex }); + } + + short methodRefConst(String classname, String name, String descriptor) { + + Short classnameIndex = new Short(classConst(classname)); + Short nameTypeIndex = new Short(nameAndTypeConst(name, descriptor)); + return addConst(new Object[] { CPmethodRef, classnameIndex, nameTypeIndex }); + } + + short intMethodRefConst(String classname, String name, String descriptor) { + + Short classnameIndex = new Short(classConst(classname)); + Short nameTypeIndex = new Short(nameAndTypeConst(name, descriptor)); + return addConst(new Object[] { CPintMethodRef, classnameIndex, nameTypeIndex }); + } + + short nameAndTypeConst(String name, String descriptor) { + + Short nameIndex = new Short(utf8Const(name)); + Short descriptorIndex = new Short(utf8Const(descriptor)); + return addConst(new Object[] { CPnameAndType, nameIndex, descriptorIndex }); + } + + public String toString() { + + return "Constant pool: " + constant2Index; + } +} Added: trunk/zktradespace/src/main/java/org/zkoss/dynclass/DynBeanInfoBase.java =================================================================== --- trunk/zktradespace/src/main/java/org/zkoss/dynclass/DynBeanInfoBase.java (rev 0) +++ trunk/zktradespace/src/main/java/org/zkoss/dynclass/DynBeanInfoBase.java 2009-03-01 21:29:10 UTC (rev 890) @@ -0,0 +1,100 @@ +package org.zkoss.dynclass; + +import java.beans.SimpleBeanInfo; +import java.beans.PropertyDescriptor; +import java.beans.IntrospectionException; + +import java.lang.reflect.Method; + +import java.util.Iterator; +import java.util.List; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +/** + * Base class of BeanInfo for auto-generated bean classes. + * Not for general use! + */ +// Note: this class interprets method names on a class. What this +// class does could be simplified by keeping a name->descriptor +// Map, but that would be even more per-class data to manage. + +public abstract class DynBeanInfoBase extends SimpleBeanInfo { + + private static void checkGetter(Method m) { + + if (!Object.class.equals(m.getReturnType()) || + m.getParameterTypes().length > 0) { + throw new RuntimeException("Not a dynclass method: " + m); + } + } + + private static void checkSetter(Method m) { + + Class[] params = m.getParameterTypes(); + if (!Void.TYPE.equals(m.getReturnType()) || + params.length != 1 || !Object.class.equals(params[0])) { + throw new RuntimeException("Not a dynclass method: " + m); + } + } + + static PropertyDescriptor[] getPropertyDescriptors(Class beanType) { + + // have to read it from the class, to keep the "no extra + // cached data" rule + Map getterMap = new HashMap(); + Map setterMap = new HashMap(); + + Method[] methods = beanType.getDeclaredMethods(); + for (int i=0; i < methods.length; i++) { + Method m = methods[i]; + String name = m.getName(); + String propName = PropertyMethodNameTx.decodeProperty(name, 3); + if (name.startsWith("g")) { + checkGetter(m); + getterMap.put(propName, m); + } + else if (name.startsWith("s")) { + checkSetter(m); + setterMap.put(propName, m); + } + else { + throw new RuntimeException("Not a dynclass bean: " + beanType.getName()); + } + } + + if (getterMap.size() != setterMap.size()) { + throw new RuntimeException("Not a dynclass bean: " + beanType.getName()); + } + + List descriptors = new ArrayList(getterMap.size()); + Iterator iter = getterMap.keySet().iterator(); + while (iter.hasNext()) { + + String prop = (String) iter.next(); + try { + descriptors.add(new PropertyDescriptor(prop, (Method) getterMap.get(prop), (Method) setterMap.get(prop))); + } + catch(IntrospectionException ie) { + throw new RuntimeException("Could not create PropertyDescriptor for " + prop + ":" + ie.getMessage()); + } + } + + return (PropertyDescriptor[]) descriptors.toArray(new PropertyDescriptor[descriptors.size()]); + } + + public PropertyDescriptor[] getPropertyDescriptors() { + + // If this class is FooBeanInfo, then the bean class is Foo + String myName = getClass().getName(); + String beanClassName = myName.substring(0, myName.length() - "BeanInfo".length()); + try { + Class beanType = getClass().getClassLoader().loadClass(beanClassName); + return getPropertyDescriptors(beanType); + } + catch(ClassNotFoundException cnfe) { + throw new RuntimeException("No class for name: " + beanClassName); + } + } +} Added: trunk/zktradespace/src/main/java/org/zkoss/dynclass/DynClassLoader.java =================================================================== --- trunk/zktradespace/src/main/java/org/zkoss/dynclass/DynClassLoader.java (rev 0) +++ trunk/zktradespace/src/main/java/org/zkoss/dynclass/DynClassLoader.java 2009-03-01 21:29:10 UTC (rev 890) @@ -0,0 +1,124 @@ +package org.zkoss.dynclass; + +import java.util.Map; +import java.util.Set; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.HashSet; +import java.util.Collections; +import java.util.List; +import java.util.ArrayList; + +import java.lang.reflect.Method; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; + +/** + * Responsible for loading defined classes, and also creating their instances. + */ +class DynClassLoader extends ClassLoader { + + private int counter = 0; + private int methodCount = 0; + + private Map descToClass = new HashMap(); + private Map classToMethodMap = Collections.synchronizedMap(new HashMap()); + + DynClassLoader() { + + super(Thread.currentThread().getContextClassLoader()); + } + + /** + * Return the total number of methods defined by this classloader. + * This is the best indicator of the amount of memory that is reachable + * from this object. Once a DynClassLoader reaches a certain threshold + * it is usually discarded. + */ + int getMethodCount() { + + return methodCount; + } + + /** + * Create a Class from the given definition. The class will have a + * generated unique name. + */ + synchronized Class defineClass(ClassDefinition def) { + + String name = "GeneratedClass" + (++counter); + return defineClass(def, name); + } + + /** + * Create a Class from the given definition. Caller is responsible for + * making the name unique. + */ + synchronized Class defineClass(ClassDefinition def, String name) { + + Class cls = (Class) descToClass.get(def); + if (cls != null) { + return cls; + } + + Translator tx = new Translator(name, def); + + byte[] bytes = tx.getClassBytes(); + cls = super.defineClass(name, bytes, 0, bytes.length); + + try { + Map idToMethod = translateMethodMap(cls, tx.getIdToDescMap()); + classToMethodMap.put(cls, idToMethod); + } + catch(NoSuchMethodException nsme) { + throw new RuntimeException("Can't find method on newly created class: " + nsme.getMessage()); + } + + descToClass.put(def, cls); + + methodCount += tx.getMethodCount(); + + return cls; + } + + Object instantiate(Class cls, InvocationHandler impl) throws Exception { + + Map idToMethod = (Map) classToMethodMap.get(cls); + if (idToMethod == null) { + String msg; + if (cls.getClassLoader() == this) { + msg = "Strange - no data for: " + cls.getName(); + } + else { + msg = "Not the classloader for: " + cls.getName(); + } + throw new RuntimeException(msg); + } + Constructor ctor = cls.getConstructor(new Class[] { + Handler.class, + }); + return ctor.newInstance(new Object[] { + new Handler(impl, idToMethod), + }); + } + + /** + * idToDesc maps generated ids to MethodDescription instances. + * This method translates these MethodDescription instances to + * actual java Methods. This map is used by the Handler class + * to provide Methods to the client-supplied InvocationHandler. + */ + private Map translateMethodMap(Class cls, Map idToDesc) throws NoSuchMethodException { + + Map newMap = new HashMap(idToDesc.size()); + Iterator iter = idToDesc.keySet().iterator(); + while (iter.hasNext()) { + Object key = iter.next(); + MethodDescription desc = (MethodDescription) idToDesc.get(key); + Method m = desc.getMethodFromClass(cls); + newMap.put(key, m); + } + return Collections.unmodifiableMap(newMap); + } +} Added: trunk/zktradespace/src/main/java/org/zkoss/dynclass/Handler.java =================================================================== --- trunk/zktradespace/src/main/java/org/zkoss/dynclass/Handler.java (rev 0) +++ trunk/zktradespace/src/main/java/org/zkoss/dynclass/Handler.java 2009-03-01 21:29:10 UTC (rev 890) @@ -0,0 +1,37 @@ +package org.zkoss.dynclass; + +import java.util.Map; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; + +/** + * Public only because generated classes use it and they live in another + * package. Not for general use! + */ +public class Handler { + + private InvocationHandler delegate; + private Map idToMethod; + + Handler(InvocationHandler delegate, Map idToMethod) { + + this.delegate = delegate; + this.idToMethod = idToMethod; + } + + public Object handle(Object proxy, String name, Object[] args) throws Throwable { + + Method m = (Method) idToMethod.get(name); + if (m == null) { + throw new RuntimeException("Unknown method id: " + name + + "; known set: " + idToMethod.keySet()); + } + + Object rval; + rval = delegate.invoke(proxy, m, args); + + // what if an exception is thrown that the caller didn't declare? + + return rval; + } +} Added: trunk/zktradespace/src/main/java/org/zkoss/dynclass/MethodDefinition.java =================================================================== --- trunk/zktradespace/src/main/java/org/zkoss/dynclass/MethodDefinition.java (rev 0) +++ trunk/zktradespace/src/main/java/org/zkoss/dynclass/MethodDefinition.java 2009-03-01 21:29:10 UTC (rev 890) @@ -0,0 +1,105 @@ +package org.zkoss.dynclass; + +import java.util.Arrays; +import java.util.List; +import java.util.ArrayList; +import java.util.Collections; + +import java.lang.reflect.Method; + +/** + * Instances of this class provide enough information to declare + * a method. Identifying information is accessed via the + * MethodDescription property of this class. + */ +public final class MethodDefinition { + + private MethodDescription desc; + private Class rtype; + private List exceptions; + + /** + * Create a MethodDefinition whose name, parameter type list, + * exception list and return type are taken from the given + * Method. + */ + public MethodDefinition(Method source) { + + this(source.getName(), + source.getReturnType(), + source.getParameterTypes(), + source.getExceptionTypes()); + } + + /** + * Create a MethodDefinition with the given name, parameter type list, + * and return type, and an empty exception list. + * @param params the parameter type list. null and 0-length arrays + * are equivalent. + */ + public MethodDefinition(String name, + Class rtype, + Class[] params) { + + this(name, rtype, params, null); + } + + /** + * Create a MethodDefinition with the given name, parameter type list, + * and return type, and exception list. + * @param params the parameter type list. null and 0-length arrays + * are equivalent. + * @param exceptions the declared exceptions. null and 0-length arrays + * are equivalent. + */ + public MethodDefinition(String name, + Class rtype, + Class[] params, + Class[] exceptions) { + + desc = new MethodDescription(name, params); + this.rtype = rtype; + if (exceptions == null) { + this.exceptions = Collections.EMPTY_LIST; + } + else { + this.exceptions = Collections.unmodifiableList(Arrays.asList(exceptions)); + } + } + + public MethodDescription getDescription() { + + return desc; + } + + public Class getReturnType() { + + return rtype; + } + + public Class[] getExceptionTypes() { + + return (Class[]) exceptions.toArray(new Class[exceptions.size()]); + } + + public int hashCode() { + + return rtype.hashCode() + desc.hashCode() + exceptions.hashCode(); + } + + public boolean equals(Object other) { + + if (!(other instanceof MethodDefinition)) { + return false; + } + + if (this == other) { + return true; + } + + MethodDefinition rhs = (MethodDefinition) other; + return this.rtype.equals(rhs.rtype) && + this.desc.equals(rhs.desc) && + this.exceptions.equals(rhs.exceptions); + } +} Added: trunk/zktradespace/src/main/java/org/zkoss/dynclass/MethodDescription.java =================================================================== --- trunk/zktradespace/src/main/java/org/zkoss/dynclass/MethodDescription.java (rev 0) +++ trunk/zktradespace/src/main/java/org/zkoss/dynclass/MethodDescription.java 2009-03-01 21:29:10 UTC (rev 890) @@ -0,0 +1,76 @@ +package org.zkoss.dynclass; + +import java.util.List; +import java.util.Collections; +import java.util.Arrays; + +import java.lang.reflect.Method; + +/** + * <p>This class provides sufficient information to uniquely identify a + * method in a class (name and parameters), but not enough to + * fully describe a method (since it doesn't have return type or + * exceptions). For that, you need MethodDefinition.</p> + * + * <p>Usually, MethodDescription instances are not created directly. + * Instead they are accessed through a MethodDefinition.</p> + */ +public final class MethodDescription { + + private String name; + private List params; + + /** + * Create a description with the given name and parameter list. + * @param params the parameter type list. null and 0-length arrays + * are equivalent. + */ + public MethodDescription(String name, Class[] params) { + + this.name = name; + if (params == null) { + this.params = Collections.EMPTY_LIST; + } + else { + this.params = Collections.unmodifiableList(Arrays.asList(params)); + } + } + + public String getName() { + + return name; + } + + public Class[] getParameterTypes() { + + return (Class[]) params.toArray(new Class[params.size()]); + } + + /** + * Inspect the given Class for a Method matching this description. + * Throws NoSuchMethodException if not matching method is found. + */ + public Method getMethodFromClass(Class cls) throws NoSuchMethodException { + + return cls.getMethod(name, getParameterTypes()); + } + + public int hashCode() { + + return name.hashCode() + params.hashCode(); + } + + public boolean equals(Object other) { + + if (!(other instanceof MethodDescription)) { + return false; + } + + if (this == other) { + return true; + } + + MethodDescription rhs = (MethodDescription) other; + return this.name.equals(rhs.name) && this.params.equals(rhs.params); + } +} Added: trunk/zktradespace/src/main/java/org/zkoss/dynclass/PropertyMethodNameTx.java =================================================================== --- trunk/zktradespace/src/main/java/org/zkoss/dynclass/PropertyMethodNameTx.java (rev 0) +++ trunk/zktradespace/src/main/java/org/zkoss/dynclass/PropertyMethodNameTx.java 2009-03-01 21:29:10 UTC (rev 890) @@ -0,0 +1,93 @@ +package org.zkoss.dynclass; + +import java.beans.Introspector; + +/** + * Utility methods to encode / decode arbitrary strings as legal + * Java identifiers. + */ +class PropertyMethodNameTx { + + private static String encodeArbitrary(String str) { + + StringBuffer encoded = new StringBuffer("$"); + int len = str.length(); + for (int i=0; i < len; i++) { + char ch = str.charAt(i); + if (Character.isJavaIdentifierPart(ch) && ch != '$') { + encoded.append(ch); + } + else { + encoded.append('$'); + encoded.append(Integer.toString(ch)); + encoded.append('$'); + } + } + return encoded.toString(); + } + + public static String encodeProperty(String str) { + + // check whether property can be upper-cased, or full encoding + // is required + if (str.length() == 0 || + !Character.isJavaIdentifierStart(str.charAt(0)) || + str.charAt(0) == '$') { + return encodeArbitrary(str); + } + for (int i=str.length()-1; i > 0; i--) { + if (!Character.isJavaIdentifierPart(str.charAt(i))) { + return encodeArbitrary(str); + } + } + + String encoded = Character.toUpperCase(str.charAt(0)) + + str.substring(1); + + if (str.equals(Introspector.decapitalize(encoded))) { + return encoded; + } + return encodeArbitrary(str); + } + + private static String decodeArbitrary(String str, int startIndex) { + + int len = str.length(); + StringBuffer sb = new StringBuffer(len); + + int i = startIndex + 1; + while (i < len) { + + char ch = str.charAt(i++); + if (ch != '$') { + sb.append(ch); + } + else { + // read everything between current char and next '$' + // as decimal value + int val = 0; + try { + ch = str.charAt(i++); + while (ch != '$') { + val *= 10; + val += (ch - '0'); + ch = str.charAt(i++); + } + sb.append((char)val); + } + catch(StringIndexOutOfBoundsException e) { + throw new IllegalArgumentException("Invalid encoded property: " + str); + } + } + } + return sb.toString(); + } + + public static String decodeProperty(String str, int startIndex) { + + if (str.charAt(startIndex) == '$') { + return decodeArbitrary(str, startIndex); + } + return Introspector.decapitalize(str.substring(startIndex)); + } +} Added: trunk/zktradespace/src/main/java/org/zkoss/dynclass/RTCompiler.java =================================================================== --- trunk/zktradespace/src/main/java/org/zkoss/dynclass/RTCompiler.java (rev 0) +++ trunk/zktradespace/src/main/java/org/zkoss/dynclass/RTCompiler.java 2009-03-01 21:29:10 UTC (rev 890) @@ -0,0 +1,229 @@ +package org.zkoss.dynclass; + +import java.io.OutputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import java.util.ArrayList; +import java.util.List; +import java.util.HashMap; +import java.util.Map; +import java.util.Collections; +import java.util.Iterator; + +/** + * My own class-file builder. Not as nifty as others out there but + * not weighed down with licenses either. + */ +class RTCompiler { + + public static final byte LDC = (byte) 0x13; + public static final byte BIPUSH = (byte) 0x10; + + public static final byte ALOAD = (byte) 0x19; + public static final byte ALOAD_0 = (byte) 0x2a; + public static final byte ALOAD_1 = (byte) 0x2b; + public static final byte ASTORE = (byte) 0x3a; + + public static final byte ILOAD = (byte) 0x15; + public static final byte LLOAD = (byte) 0x16; + public static final byte FLOAD = (byte) 0x17; + public static final byte DLOAD = (byte) 0x18; + + public static final byte INVOKESPECIAL = (byte) 0xb7; + public static final byte INVOKEVIRTUAL = (byte) 0xb6; + + public static final byte POP = (byte) 0x57; + public static final byte RETURN = (byte) 0xb1; + public static final byte IRETURN = (byte) 0xac; + public static final byte LRETURN = (byte) 0xad; + public static final byte FRETURN = (byte) 0xae; + public static final byte DRETURN = (byte) 0xaf; + public static final byte ARETURN = (byte) 0xb0; + public static final byte CHECKCAST = (byte) 0xc0; + + public static final byte NEW = (byte) 0xbb; + public static final byte DUP = (byte) 0x59; + + public static final byte GETFIELD = (byte) 0xb4; + public static final byte PUTFIELD = (byte) 0xb5; + + public static final byte ANEWARRAY = (byte) 0xbd; + public static final byte AASTORE = (byte) 0x53; + + public static final byte WIDE = (byte) 0xc4; + + private static final Map primitiveToDescriptor; + static { + HashMap hm = new HashMap(); + hm.put(Boolean.TYPE, "Z"); + hm.put(Byte.TYPE, "B"); + hm.put(Short.TYPE, "S"); + hm.put(Character.TYPE, "C"); + hm.put(Integer.TYPE, "I"); + hm.put(Float.TYPE, "F"); + hm.put(Double.TYPE, "D"); + hm.put(Long.TYPE, "J"); + hm.put(Void.TYPE, "V"); + primitiveToDescriptor = Collections.unmodifiableMap(hm); + } + private ConstantPool cp = new ConstantPool(); + + // constPool indices + private short thisClass; + private short superClass; + private short[] infs; + + private List fieldData = new ArrayList(); + + private List methodData = new ArrayList(); + + static String toInternalForm(Class cls) { + + if (cls.isArray()) { + return "[" + toInternalForm(cls.getComponentType()); + } + return cls.getName().replace('.', '/'); + } + + static String toDescriptor(Class cls) { + + if (cls.isArray()) { + return "[" + toDescriptor(cls.getComponentType()); + } + if (cls.isPrimitive()) { + return (String) primitiveToDescriptor.get(cls); + } + return "L" + toInternalForm(cls) + ";"; + } + + static String toMethodDescriptor(Class returnType, + Class[] paramTypes) { + + StringBuffer descBuffer = new StringBuffer(); + descBuffer.append('('); + for (int i=0; i < paramTypes.length; i++) { + descBuffer.append(toDescriptor(paramTypes[i])); + } + descBuffer.append(')'); + descBuffer.append(toDescriptor(returnType)); + return descBuffer.toString(); + } + + public RTCompiler(String className, + Class superclass, + Class[] interfaces) { + + thisClass = cp.classConst(className.replace('.', '/')); + superClass = cp.classConst(toInternalForm(superclass)); + infs = new short[interfaces.length]; + for (int i=0; i < infs.length; i++) { + infs[i] = cp.classConst(toInternalForm(interfaces[i])); + } + } + + public ConstantPool getConstantPool() { + + return cp; + } + + public void addField(String name, Class type) { + + if (type.equals(Void.TYPE)) { + throw new IllegalArgumentException("void fields not allowed"); + } + short nameIndex = cp.utf8Const(name); + short typeIndex = cp.utf8Const(toDescriptor(type)); + fieldData.add(new Short(nameIndex)); + fieldData.add(new Short(typeIndex)); + } + + public void writeClass(OutputStream os) throws IOException { + + DataOutputStream data = new DataOutputStream(os); + data.writeInt(0xcafebabe); // magic + data.writeShort(3); // got these from hex-dumping a class file + data.writeShort(0x2d); + data.writeShort(cp.getCount()); + cp.writePool(data); + data.writeShort(1); // public flag + data.writeShort(thisClass); + data.writeShort(superClass); + data.writeShort((short)infs.length); + for (int i=0; i < infs.length; i++) { + data.writeShort(infs[i]); + } + + writeFields(data); + + // method land + data.writeShort((short)methodData.size()); + Iterator methodIter = methodData.iterator(); + while (methodIter.hasNext()) { + data.write((byte[])methodIter.next()); + } + + data.writeShort(0); // no attrs + } + + private void writeFields(DataOutputStream data) throws IOException { + + data.writeShort((short)(fieldData.size() / 2)); + Iterator fdi = fieldData.iterator(); + while (fdi.hasNext()) { + data.writeShort(2); // private + data.writeShort(((Short)fdi.next()).shortValue()); + data.writeShort(((Short)fdi.next()).shortValue()); + data.writeShort(0); + } + } + + public void addMethod(String name, + Class returnType, + Class[] paramTypes, + Class[] exceptionTypes, + short maxStack, + short maxLocals, + byte[] code) { + + short methodName = cp.utf8Const(name); + short methodDesc = cp.utf8Const(toMethodDescriptor(returnType, paramTypes)); + + ByteArrayOutputStream methodBytes = new ByteArrayOutputStream(); + DataOutputStream methodInfo = new DataOutputStream(methodBytes); + + try { + methodInfo.writeShort(1); // public + methodInfo.writeShort(methodName); + methodInfo.writeShort(methodDesc); + methodInfo.writeShort(exceptionTypes.length > 0? 2 : 1); // # attrs + + // write code attribute + methodInfo.writeShort(cp.utf8Const("Code")); + methodInfo.writeInt(code.length + 12); + methodInfo.writeShort(maxStack); + methodInfo.writeShort(maxLocals); + methodInfo.writeInt(code.length); + methodInfo.write(code); + methodInfo.writeShort(0); // no exception table + methodInfo.writeShort(0); // no attributes + + if (exceptionTypes.length > 0) { + + methodInfo.writeShort(cp.utf8Const("Exceptions")); + methodInfo.writeInt((exceptionTypes.length + 1) * 2); + methodInfo.writeShort((short)exceptionTypes.length); + for (int i=0; i < exceptionTypes.length; i++) { + String exName = toInternalForm(exceptionTypes[i]); + methodInfo.writeShort(cp.classConst(exName)); + } + } + } + catch(IOException ioe) { + throw new RuntimeException("Exception writing to byte array: " + ioe.getMessage()); + } + + methodData.add(methodBytes.toByteArray()); + } +} Added: trunk/zktradespace/src/main/java/org/zkoss/dynclass/Translator.java =================================================================== --- trunk/zktradespace/src/main/java/org/zkoss/dynclass/Translator.java (rev 0) +++ trunk/zktradespace/src/main/java/org/zkoss/dynclass/Translator.java 2009-03-01 21:29:10 UTC (rev 890) @@ -0,0 +1,435 @@ +package org.zkoss.dynclass; + +import java.util.Map; +import java.util.HashMap; +import java.util.Iterator; +import java.util.HashSet; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.ArrayList; +import java.util.Arrays; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +/** + * Translator is responsible for reading a ClassDefinition and producing + * corresponding bytecode. Could be called "ClassTranslator" but too many + * classes start with Class already... + */ +class Translator { + + private static final String HANDLER_NAME = "Ldynclass/Handler;"; + private static final Map rtConversionInfo; + private static final Map rtToByte; + private static final Collection twoWordTypes; + + static { + HashMap hm = new HashMap(); + hm.put(Boolean.TYPE, new String[] { + "java/lang/Boolean", + "booleanValue", + "Z", + }); + hm.put(Byte.TYPE, new String[] { + "java/lang/Byte", + "byteValue", + "B", + }); + hm.put(Short.TYPE, new String[] { + "java/lang/Short", + "shortValue", + "S", + }); + hm.put(Character.TYPE, new String[] { + "java/lang/Character", + "charValue", + "C", + }); + hm.put(Integer.TYPE, new String[] { + "java/lang/Integer", + "intValue", + "I", + }); + hm.put(Float.TYPE, new String[] { + "java/lang/Float", + "floatValue", + "F", + }); + hm.put(Double.TYPE, new String[] { + "java/lang/Double", + "doubleValue", + "D", + }); + hm.put(Long.TYPE, new String[] { + "java/lang/Long", + "longValue", + "J", + }); + rtConversionInfo = Collections.unmodifiableMap(hm); + + hm = new HashMap(); + hm.put(Boolean.TYPE, new byte[] { RTCompiler.IRETURN , RTCompiler.ILOAD }); + hm.put(Byte.TYPE, new byte[] { RTCompiler.IRETURN , RTCompiler.ILOAD }); + hm.put(Short.TYPE, new byte[] { RTCompiler.IRETURN, RTCompiler.ILOAD }); + hm.put(Character.TYPE, new byte[] { RTCompiler.IRETURN, RTCompiler.ILOAD }); + hm.put(Integer.TYPE, new byte[] { RTCompiler.IRETURN , RTCompiler.ILOAD }); + hm.put(Float.TYPE, new byte[] { RTCompiler.FRETURN, RTCompiler.FLOAD }); + hm.put(Double.TYPE, new byte[] { RTCompiler.DRETURN, RTCompiler.DLOAD }); + hm.put(Long.TYPE, new byte[] { RTCompiler.LRETURN, RTCompiler.LLOAD }); + rtToByte = Collections.unmodifiableMap(hm); + + Class[] twt = { Long.TYPE, Double.TYPE }; + twoWordTypes = Collections.unmodifiableList(Arrays.asList(twt)); + } + private String genClassName; + private ClassDefinition desc; + + private RTCompiler rtc; + private ConstantPool cp; + + private String currentUniqueMethodId; + + private Map idToMethod; + private int methodCount; + + // constant pool index of field reference for the "handler" field + // in the generated class + private short handlerFieldRef = 0; + + // constant pool index of class reference to java.lang.Object + private short objectClassRef = 0; + + // constant pool index of method reference to: + // Handler.handle(Object, String, Object[]) + private short handlerMethodRef = 0; + + Translator(String genClassName, ClassDefinition desc) { + + this.genClassName = genClassName; + this.desc = desc; + createClass(); + } + + String getName() { + + return genClassName; + } + + /** + * Return the bytecodes describing the Class generated by + * this creator. + */ + byte[] getClassBytes() { + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + rtc.writeClass(out); + } + catch(IOException ioe) { + // shouldn't get exceptions from writing to memory + throw new RuntimeException(ioe.getClass() + ":" + ioe.getMessage()); + } + return out.toByteArray(); + } + + int getMethodCount() { + + return methodCount; + } + + Map getIdToDescMap() { + + return idToMethod; + } + + private void addAbstractMethods(List methodList) { + + // First, look for abstract methods on superclass + Class superclass = desc.getSuperclass(); + Method[] superMethods = superclass.getMethods(); + for (int i=0; i < superMethods.length; i++) { + Method m = superMethods[i]; + int md = m.getModifiers(); + if (Modifier.isAbstract(md)) { + methodList.add(new MethodDefinition(m)); + } + } + + // Now look for interface methods that aren't implemented + // on the superclass + Class[] infs = desc.getInterfaces(); + for (int k=0; k < infs.length; k++) { + Class inf = infs[k]; + Method[] infMethods = inf.getMethods(); + for (int j=0; j < infMethods.length; j++) { + Method m = infMethods[j]; + + // does superclass implement this already? + try { + superclass.getMethod(m.getName(), m.getParameterTypes()); + // yes: + continue; + } + catch(NoSuchMethodException nsme) { + methodList.add(new MethodDefinition(m)); + } + } + } + } + + private List getMethodsList() { + + List candidateMethods = new ArrayList(); + candidateMethods.addAll(desc.getMethodDefinitions()); + addAbstractMethods(candidateMethods); + + HashSet methodSet = new HashSet(); // prevent duplicates + ArrayList l = new ArrayList(); + Iterator iter = candidateMethods.iterator(); + while (iter.hasNext()) { + MethodDefinition def = (MethodDefinition) iter.next(); + MethodDescription desc = def.getDescription(); + if (methodSet.contains(desc)) { + continue; + } + methodSet.add(desc); + l.add(def); + } + return l; + } + + private void createClass() { + + rtc = new RTCompiler(genClassName, desc.getSuperclass(), desc.getInterfaces()); + cp = rtc.getConstantPool(); + handlerFieldRef = cp.fieldRefConst(genClassName.replace('.','/'), + "handler", + rtc.toDescriptor(Handler.class)); + //System.out.println("handlerFieldRef=" + handlerFieldRef + + //"; cp=" + cp); + + objectClassRef = cp.classConst(rtc.toInternalForm(Object.class)); + handlerMethodRef = cp.methodRefConst(rtc.toInternalForm(Handler.class), + "handle", + "(Ljava/lang/Object;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/Object;"); + + rtc.addField("handler", Handler.class); + + createConstructor(); + + List methodList = getMethodsList(); + idToMethod = new HashMap(methodList.size()); + + Iterator methodIter = methodList.iterator(); + while (methodIter.hasNext()) { + MethodDefinition def = (MethodDefinition) methodIter.next(); + MethodDescription desc = def.getDescription(); + + currentUniqueMethodId = "mId" + (++methodCount); + idToMethod.put(currentUniqueMethodId, desc); + + createMethod(desc.getName(), + def.getReturnType(), + desc.getParameterTypes(), + def.getExceptionTypes()); + } + } + + private void pushIntConst(ByteArrayOutputStream mb, int value) { + + if (value <= Byte.MAX_VALUE) { + mb.write(rtc.BIPUSH); + mb.write((byte)value); + } + else { + short valueIndex = cp.intConst(value); + mb.write(rtc.LDC); + writeShort(mb, valueIndex); + } + } + + private void loadOrStoreIndex(ByteArrayOutputStream mb, + byte inst, + int index) { + + if (index <= Byte.MAX_VALUE) { + mb.write(inst); + mb.write((byte)index); + } + else { + mb.write(rtc.WIDE); + mb.write(inst); + writeShort(mb, (short)index); + } + } + + private void createMethod(String methodName, + Class returnType, + Class[] params, + Class[] exceptions) ... [truncated message content] |