diff src/com/go/trove/util/MergedClass.java @ 0:3dc0c5604566

Initial checkin of blitz 2.0 fcs - no installer yet.
author Dan Creswell <dan.creswell@gmail.com>
date Sat, 21 Mar 2009 11:00:06 +0000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/go/trove/util/MergedClass.java	Sat Mar 21 11:00:06 2009 +0000
@@ -0,0 +1,858 @@
+/* ====================================================================
+ * Trove - Copyright (c) 1997-2000 Walt Disney Internet Group
+ * ====================================================================
+ * The Tea Software License, Version 1.1
+ *
+ * Copyright (c) 2000 Walt Disney Internet Group. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by the
+ *        Walt Disney Internet Group (http://opensource.go.com/)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Tea", "TeaServlet", "Kettle", "Trove" and "BeanDoc" must
+ *    not be used to endorse or promote products derived from this
+ *    software without prior written permission. For written
+ *    permission, please contact opensource@dig.com.
+ *
+ * 5. Products derived from this software may not be called "Tea",
+ *    "TeaServlet", "Kettle" or "Trove", nor may "Tea", "TeaServlet",
+ *    "Kettle", "Trove" or "BeanDoc" appear in their name, without prior
+ *    written permission of the Walt Disney Internet Group.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE WALT DISNEY INTERNET GROUP OR ITS
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ====================================================================
+ *
+ * For more information about Tea, please see http://opensource.go.com/.
+ */
+
+package com.go.trove.util;
+
+import java.lang.reflect.*;
+import java.util.*;
+import java.io.OutputStream;
+import java.io.IOException;
+import com.go.trove.classfile.*;
+
+/******************************************************************************
+ * Merges several classes together, producing a new class that has all of the
+ * methods of the combined classes. All methods in the combined class delegate
+ * to instances of the source classes. If multiple classes implement the same
+ * method, the first one provided is used. The merged class implements all of
+ * the interfaces provided by the source classes or interfaces.
+ *
+ * <p>This class performs a function almost the same as the Proxy class
+ * introduced in JDK1.3. It differs in that it supports classes as well as
+ * interfaces as input, and it binds to wrapped objects without using
+ * runtime reflection. It is less flexible than Proxy in that there isn't way
+ * to customize the method delegation, and so it isn't suitable for creating
+ * dynamically generated interface implementations.
+ *
+ * @author Brian S O'Neill
+ * @version
+ * <!--$$Revision: 1.1 $-->, <!--$$JustDate:--> 01/02/20 <!-- $-->
+ */
+public class MergedClass {
+    // Maps ClassInjectors to Maps that map to merged class names.
+    // Map<ClassInjector, Map<MultiKey, String>>
+    // The MultiKey is composed of class names and method prefixes. By storing
+    // class names into the maps instead of classes, the classes may be
+    // reclaimed by the garbage collector.
+    private static Map cMergedMap;
+
+    static {
+        try {
+            cMergedMap = new IdentityMap(7);
+        }
+        catch (LinkageError e) {
+            cMergedMap = new HashMap(7);
+        }
+        catch (Exception e) {
+            // Microsoft VM sometimes throws an undeclared
+            // ClassNotFoundException instead of doing the right thing and
+            // throwing some form of a LinkageError if the class couldn't
+            // be found.
+            cMergedMap = new HashMap(7);
+        }
+    }
+
+    /**
+     * Returns the constructor for a class that merges all of the given source
+     * classes. The constructor's parameter types match the source classes.
+     * An IllegalArgumentException is thrown if any of the given conditions
+     * are not satisfied:
+     *
+     * <ul>
+     * <li>None of the given classes can represent a primitive type
+     * <li>A source class can only be provided once
+     * <li>Any non-public classes must be in the same common package
+     * <li>Duplicate methods cannot have conflicting return types
+     * <li>The given classes must be loadable from the given injector
+     * <li>At most 254 classes may be merged
+     * </ul>
+     *
+     * Note: Because a constructor is limited to 254 parameters, if more than
+     * 254 classes are to be merged together, call {@link #getConstructor2}.
+     *
+     * @param injector ClassInjector that will receive class definition
+     * @param classes Source classes used to derive merged class
+     */
+    public static Constructor getConstructor(ClassInjector injector,
+                                             Class[] classes)
+        throws IllegalArgumentException
+    {
+        return getConstructor(injector, classes, null);
+    }
+
+    /**
+     * Returns the constructor for a class that merges all of the given source
+     * classes. The constructor's parameter types match the source classes.
+     * An IllegalArgumentException is thrown if any of the given conditions
+     * are not satisfied:
+     *
+     * <ul>
+     * <li>None of the given classes can represent a primitive type
+     * <li>A source class can only be provided once, unless paired with a 
+     * unique prefix.
+     * <li>Any non-public classes must be in the same common package
+     * <li>Duplicate methods cannot have conflicting return types, unless a
+     * prefix is provided.
+     * <li>The given classes must be loadable from the given injector
+     * <li>At most 254 classes may be merged
+     * </ul>
+     *
+     * To help resolve ambiguities, a method prefix can be specified for each
+     * passed in class. For each prefixed method, the non-prefixed method is
+     * also generated, unless that method conflicts with the return type of
+     * another method. If any passed in classes are or have interfaces, then
+     * those interfaces will be implemented only if there are no conflicts.
+     *
+     * <p>The array of prefixes may be null, have null elements or
+     * be shorter than the array of classes. A prefix of "" is treated as null.
+     * <p>
+     * Note: Because a constructor is limited to 254 parameters, if more than
+     * 254 classes are to be merged together, call {@link #getConstructor2}.
+     *
+     * @param injector ClassInjector that will receive class definition
+     * @param classes Source classes used to derive merged class
+     * @param prefixes Optional prefixes to apply to methods of each generated
+     * class to eliminate duplicate method names
+     */
+    public static Constructor getConstructor(ClassInjector injector,
+                                             Class[] classes,
+                                             String[] prefixes)
+        throws IllegalArgumentException
+    {
+        if (classes.length > 254) {
+            throw new IllegalArgumentException
+                ("More than 254 merged classes: " + classes.length);
+        }
+
+        Class clazz = getMergedClass(injector, classes, prefixes);
+
+        try {
+            return clazz.getConstructor(classes);
+        }
+        catch (NoSuchMethodException e) {
+            throw new InternalError(e.toString());
+        }
+    }
+
+    /**
+     * Returns the constructor for a class that merges all of the given source
+     * classes. The constructor accepts one parameter type: an
+     * {@link InstanceFactory}. Merged instances are requested only when
+     * first needed. An IllegalArgumentException is thrown if any of the given
+     * conditions are not satisfied:
+     *
+     * <ul>
+     * <li>None of the given classes can represent a primitive type
+     * <li>A source class can only be provided once
+     * <li>Any non-public classes must be in the same common package
+     * <li>Duplicate methods cannot have conflicting return types
+     * <li>The given classes must be loadable from the given injector
+     * </ul>
+     *
+     * @param injector ClassInjector that will receive class definition
+     * @param classes Source classes used to derive merged class
+     */
+    public static Constructor getConstructor2(ClassInjector injector,
+                                              Class[] classes)
+        throws IllegalArgumentException
+    {
+        return getConstructor2(injector, classes, null);
+    }
+
+    /**
+     * Returns the constructor for a class that merges all of the given source
+     * classes. The constructor accepts one parameter type: an
+     * {@link InstanceFactory}. Merged instances are requested only when
+     * first needed. An IllegalArgumentException is thrown if any of the given
+     * conditions are not satisfied:
+     *
+     * <ul>
+     * <li>None of the given classes can represent a primitive type
+     * <li>A source class can only be provided once, unless paired with a 
+     * unique prefix.
+     * <li>Any non-public classes must be in the same common package
+     * <li>Duplicate methods cannot have conflicting return types, unless a
+     * prefix is provided.
+     * <li>The given classes must be loadable from the given injector
+     * </ul>
+     *
+     * To help resolve ambiguities, a method prefix can be specified for each
+     * passed in class. For each prefixed method, the non-prefixed method is
+     * also generated, unless that method conflicts with the return type of
+     * another method. If any passed in classes are or have interfaces, then
+     * those interfaces will be implemented only if there are no conflicts.
+     *
+     * <p>The array of prefixes may be null, have null elements or
+     * be shorter than the array of classes. A prefix of "" is treated as null.
+     * 
+     * @param injector ClassInjector that will receive class definition
+     * @param classes Source classes used to derive merged class
+     * @param prefixes Optional prefixes to apply to methods of each generated
+     * class to eliminate duplicate method names
+     */
+    public static Constructor getConstructor2(ClassInjector injector,
+                                              Class[] classes,
+                                              String[] prefixes)
+        throws IllegalArgumentException
+    {
+        Class clazz = getMergedClass(injector, classes, prefixes);
+
+        try {
+            return clazz.getConstructor(new Class[]{InstanceFactory.class});
+        }
+        catch (NoSuchMethodException e) {
+            throw new InternalError(e.toString());
+        }
+    }
+
+    private static Class getMergedClass(ClassInjector injector,
+                                        Class[] classes,
+                                        String[] prefixes)
+        throws IllegalArgumentException
+    {
+        ClassEntry[] classEntries = new ClassEntry[classes.length];
+        for (int i=0; i<classes.length; i++) {
+            // Load the classes from the ClassInjector, just like they will be
+            // when the generated class is resolved.
+            try {
+                classes[i] = injector.loadClass(classes[i].getName());
+            }
+            catch (ClassNotFoundException e) {
+                throw new IllegalArgumentException
+                    ("Unable to load class from injector: " + classes[i]);
+            }
+
+            if (prefixes == null || i >= prefixes.length) {
+                classEntries[i] = new ClassEntry(classes[i]);
+            }
+            else {
+                String prefix = prefixes[i];
+                if (prefix != null && prefix.length() == 0) {
+                    prefix = null;
+                }
+                classEntries[i] = new ClassEntry(classes[i], prefix);
+            }
+        }
+
+        return getMergedClass(injector, classEntries);
+    }
+
+    /**
+     * Creates a class with a public constructor whose parameter types match
+     * the given source classes. Another constructor is also created that
+     * accepts an InstanceFactory.
+     */
+    private static synchronized Class getMergedClass(ClassInjector injector,
+                                                     ClassEntry[] classEntries)
+        throws IllegalArgumentException
+    {
+        Map classListMap = (Map)cMergedMap.get(injector);
+        if (classListMap == null) {
+            classListMap = new HashMap(7);
+            cMergedMap.put(injector, classListMap);
+        }
+
+        Object key = generateKey(classEntries);
+        String mergedName = (String)classListMap.get(key);
+        if (mergedName != null) {
+            try {
+                return injector.loadClass(mergedName);
+            }
+            catch (ClassNotFoundException e) {
+            }
+        }
+
+        ClassFile cf;
+        try {
+            cf = createClassFile(injector, classEntries);
+        }
+        catch (IllegalArgumentException e) {
+            e.fillInStackTrace();
+            throw e;
+        }
+
+        /*
+        try {
+            java.io.FileOutputStream out =
+                new java.io.FileOutputStream(cf.getClassName() + ".class");
+            cf.writeTo(out);
+            out.close();
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+        }
+        */
+
+        try {
+            OutputStream stream = injector.getStream(cf.getClassName());
+            cf.writeTo(stream);
+            stream.close();
+        }
+        catch (IOException e) {
+            throw new InternalError(e.toString());
+        }
+
+        Class merged;
+        try {
+            merged = injector.loadClass(cf.getClassName());
+        }
+        catch (ClassNotFoundException e) {
+            throw new InternalError(e.toString());
+        }
+
+        classListMap.put(key, merged.getName());
+        return merged;
+    }
+
+    private static Object generateKey(ClassEntry[] classEntries) {
+        int length = classEntries.length;
+        Object[] mainElements = new Object[length];
+        for (int i=0; i<length; i++) {
+            ClassEntry classEntry = classEntries[i];
+            mainElements[i] = new MultiKey(new Object[] {
+                classEntry.getClazz().getName(),
+                classEntry.getMethodPrefix()
+            });
+        }
+        return new MultiKey(mainElements);
+    }
+
+    private static ClassFile createClassFile(ClassInjector injector,
+                                             ClassEntry[] classEntries)
+        throws IllegalArgumentException
+    {
+        Set classSet = new HashSet(classEntries.length * 2 + 1);
+        Set nonConflictingClasses = new HashSet(classEntries.length * 2 + 1);
+        String commonPackage = null;
+        Map methodMap = new HashMap();
+
+        for (int i=0; i<classEntries.length; i++) {
+            ClassEntry classEntry = classEntries[i];
+            Class clazz = classEntry.getClazz();
+            
+            if (clazz.isPrimitive()) {
+                throw new IllegalArgumentException
+                    ("Merged classes cannot be primitive: " + clazz);
+            }
+
+            if (!classSet.add(classEntry)) {
+                throw new IllegalArgumentException
+                    ("Class is specified more than once: " + clazz);
+            }
+
+            if (!Modifier.isPublic(clazz.getModifiers())) {
+                String classPackage = clazz.getName();
+                int index = classPackage.lastIndexOf('.');
+                if (index < 0) {
+                    classPackage = "";
+                }
+                else {
+                    classPackage = classPackage.substring(0, index);
+                }
+
+                if (commonPackage == null) {
+                    commonPackage = classPackage;
+                }
+                else if (!commonPackage.equals(classPackage)) {
+                    throw new IllegalArgumentException
+                        ("Not all non-public classes defined in same " +
+                         "package: " + commonPackage);
+                }
+            }
+
+            // Innocent until proven guilty.
+            nonConflictingClasses.add(classEntry);
+
+            Method[] methods = clazz.getMethods();
+            String prefix = classEntry.getMethodPrefix();
+
+            for (int j=0; j<methods.length; j++) {
+                Method method = methods[j];
+                String name = method.getName();
+                // Workaround for JDK1.2 bug #4187388.
+                if ("<clinit>".equals(name)) {
+                    continue;
+                }
+
+                MethodEntry methodEntry = new MethodEntry(method, name);
+                MethodEntry existing = (MethodEntry)methodMap.get(methodEntry);
+
+                if (existing == null) {
+                    methodMap.put(methodEntry, methodEntry);
+                }
+                else if (existing.returnTypeDiffers(methodEntry)) {
+                    nonConflictingClasses.remove(classEntry);
+                    if (prefix == null) {
+                        throw new IllegalArgumentException
+                            ("Conflicting return types: " +
+                             existing + ", " + methodEntry);
+                    }
+                }
+
+                if (prefix != null) {
+                    name = prefix + name;
+                
+                    methodEntry = new MethodEntry(method, name);
+                    existing = (MethodEntry)methodMap.get(methodEntry);
+
+                    if (existing == null) {
+                        methodMap.put(methodEntry, methodEntry);
+                    }
+                    else if (existing.returnTypeDiffers(methodEntry)) {
+                        nonConflictingClasses.remove(classEntry);
+                        throw new IllegalArgumentException
+                            ("Conflicting return types: " +
+                             existing + ", " + methodEntry);
+                    }
+                }
+            }
+        }
+
+        int id = 0;
+        Iterator it = classSet.iterator();
+        while (it.hasNext()) {
+            id = id * 31 + it.next().hashCode();
+        }
+
+        String className = "MergedClass$";
+        try {
+            while (true) {
+                className = "MergedClass$" + (id & 0xffffffffL);
+                if (commonPackage != null && commonPackage.length() > 0) {
+                    className = commonPackage + '.' + className;
+                }
+                try {
+                    injector.loadClass(className);
+                }
+                catch (LinkageError e) {
+                }
+                id++;
+            }
+        }
+        catch (ClassNotFoundException e) {
+        }
+
+        ClassFile cf = new ClassFile(className);
+        cf.getAccessFlags().setFinal(true);
+        cf.markSynthetic();
+
+        for (int i=0; i<classEntries.length; i++) {
+            ClassEntry classEntry = classEntries[i];
+            if (nonConflictingClasses.contains(classEntry)) {
+                addAllInterfaces(cf, classEntry.getClazz());
+            }
+        }
+
+        AccessFlags privateAccess = new AccessFlags();
+        privateAccess.setPrivate(true);
+        AccessFlags privateFinalAccess = new AccessFlags();
+        privateFinalAccess.setPrivate(true);
+        privateFinalAccess.setFinal(true);
+        AccessFlags publicAccess = new AccessFlags();
+        publicAccess.setPublic(true);
+
+        // Add field to store optional InstanceFactory.
+        TypeDescriptor instanceFactoryType =
+            new TypeDescriptor(InstanceFactory.class);
+        cf.addField(privateAccess,
+                    "instanceFactory", instanceFactoryType).markSynthetic();
+
+        Method instanceFactoryMethod;
+        try {
+            instanceFactoryMethod =
+                InstanceFactory.class.getMethod
+                ("getInstance", new Class[]{int.class});
+        }
+        catch (NoSuchMethodException e) {
+            throw new InternalError(e.toString());
+        }
+
+        // Define fields which point to wrapped objects, and define methods
+        // to access the fields.
+        String[] fieldNames = new String[classEntries.length];
+        TypeDescriptor[] types = new TypeDescriptor[classEntries.length];
+        for (int i=0; i<classEntries.length; i++) {
+            Class clazz = classEntries[i].getClazz();
+            String fieldName = "m$" + i;
+            TypeDescriptor type = new TypeDescriptor(clazz);
+            cf.addField(privateAccess, fieldName, type).markSynthetic();
+            fieldNames[i] = fieldName;
+            types[i] = type;
+            
+            // Create method that returns field, calling the InstanceFactory
+            // if necessary to initialize for the first time.
+            MethodInfo mi = cf.addMethod
+                (privateFinalAccess, fieldName, type, null);
+            mi.markSynthetic();
+            CodeBuilder builder = new CodeBuilder(mi);
+            builder.loadThis();
+            builder.loadField(fieldName, type);
+            builder.dup();
+            Label isNull = builder.createLabel();
+            builder.ifNullBranch(isNull, true);
+            // Return the initialized field.
+            builder.returnValue(Object.class);
+            isNull.setLocation();
+            // Discard null field value.
+            builder.pop();
+            builder.loadThis();
+            builder.loadField("instanceFactory", instanceFactoryType);
+            builder.dup();
+            Label haveInstanceFactory = builder.createLabel();
+            builder.ifNullBranch(haveInstanceFactory, false);
+            // No instanceFactory: return null.
+            builder.loadConstant(null);
+            builder.returnValue(Object.class);
+            haveInstanceFactory.setLocation();
+            builder.loadConstant(i);
+            builder.invoke(instanceFactoryMethod);
+            builder.checkCast(type);
+            builder.dup();
+            builder.loadThis();
+            builder.swap();
+            builder.storeField(fieldName, type);
+            builder.returnValue(Object.class);
+        }
+
+        // Define a constructor that initializes fields from an Object array.
+        if (classEntries.length <= 254) {
+            MethodInfo mi = cf.addConstructor(publicAccess, types);
+            
+            CodeBuilder builder = new CodeBuilder(mi);
+            builder.loadThis();
+            builder.invokeSuperConstructor(null);
+            LocalVariable[] params = builder.getParameters();
+            
+            for (int i=0; i<classEntries.length; i++) {
+                builder.loadThis();
+                builder.loadLocal(params[i]);
+                builder.storeField(fieldNames[i], types[i]);
+            }
+            
+            builder.returnVoid();
+            builder = null;
+        }
+
+        // Define a constructor that saves an InstanceFactory.
+        MethodInfo mi = cf.addConstructor
+            (publicAccess, new TypeDescriptor[]{instanceFactoryType});
+
+        CodeBuilder builder = new CodeBuilder(mi);
+        builder.loadThis();
+        builder.invokeSuperConstructor(null);
+        builder.loadThis();
+        builder.loadLocal(builder.getParameters()[0]);
+        builder.storeField("instanceFactory", instanceFactoryType);
+
+        builder.returnVoid();
+        builder = null;
+
+        Set methodSet = methodMap.keySet();
+
+        // Define all the wrapper methods.
+        for (int i=0; i<classEntries.length; i++) {
+            ClassEntry classEntry = classEntries[i];
+            String prefix = classEntry.getMethodPrefix();
+            String fieldName = fieldNames[i];
+            TypeDescriptor type = types[i];
+
+            Method[] methods = classEntry.getClazz().getMethods();
+            for (int j=0; j<methods.length; j++) {
+                Method method = methods[j];
+                // Workaround for JDK1.2 bug #4187388.
+                if ("<clinit>".equals(method.getName())) {
+                    continue;
+                }
+
+                MethodEntry methodEntry = new MethodEntry(method);
+                if (methodSet.contains(methodEntry)) {
+                    methodSet.remove(methodEntry);
+                    addWrapperMethod(cf, methodEntry, fieldName, type);
+                }
+
+                if (prefix != null) {
+                    methodEntry = new MethodEntry
+                        (method, prefix + method.getName());
+                    if (methodSet.contains(methodEntry)) {
+                        methodSet.remove(methodEntry);
+                        addWrapperMethod(cf, methodEntry, fieldName, type);
+                    }
+                }
+            }
+        }
+
+        return cf;
+    }
+
+    private static void addAllInterfaces(ClassFile cf, Class clazz) {
+        if (clazz == null) {
+            return;
+        }
+
+        if (clazz.isInterface()) {
+            cf.addInterface(clazz);
+        }
+
+        addAllInterfaces(cf, clazz.getSuperclass());
+
+        Class[] interfaces = clazz.getInterfaces();
+        for (int i=0; i<interfaces.length; i++) {
+            addAllInterfaces(cf, interfaces[i]);
+        }
+    }
+
+    private static void addWrapperMethod(ClassFile cf,
+                                         MethodEntry methodEntry,
+                                         String fieldName,
+                                         TypeDescriptor type) {
+        Method method = methodEntry.getMethod();
+
+        // Don't override any methods in Object, especially final ones.
+        if (isDefinedInObject(method)) {
+            return;
+        }
+
+        AccessFlags flags = new AccessFlags(method.getModifiers());
+        flags.setAbstract(false);
+        flags.setFinal(true);
+        flags.setSynchronized(false);
+        flags.setNative(false);
+        flags.setStatic(false);
+
+        AccessFlags staticFlags = (AccessFlags)flags.clone();
+        staticFlags.setStatic(true);
+
+        TypeDescriptor ret = new TypeDescriptor(method.getReturnType());
+
+        Class[] paramClasses = method.getParameterTypes();
+        TypeDescriptor[] params = new TypeDescriptor[paramClasses.length];
+        for (int i=0; i<params.length; i++) {
+            params[i] = new TypeDescriptor(paramClasses[i]);
+        }
+
+        MethodInfo mi;
+        if (Modifier.isStatic(method.getModifiers())) {
+            mi = cf.addMethod
+                (staticFlags, methodEntry.getName(), ret, params);
+        }
+        else {
+            mi = cf.addMethod(flags, methodEntry.getName(), ret, params);
+        }
+        
+        // Exception stuff...
+        Class[] exceptions = method.getExceptionTypes();
+        for (int i=0; i<exceptions.length; i++) {
+            mi.addException(exceptions[i].getName());
+        }
+
+        // Delegate to wrapped object.
+        CodeBuilder builder = new CodeBuilder(mi);
+
+        if (!Modifier.isStatic(method.getModifiers())) {
+            builder.loadThis();
+            builder.loadField(fieldName, type);
+            Label isNonNull = builder.createLabel();
+            builder.dup();
+            builder.ifNullBranch(isNonNull, false);
+            // Discard null field value.
+            builder.pop();
+            // Call the field access method, which in turn calls the
+            // InstanceFactory.
+            builder.loadThis();
+            builder.invokePrivate(fieldName, type, null);
+            isNonNull.setLocation();
+        }
+        LocalVariable[] locals = builder.getParameters();
+        for (int i=0; i<locals.length; i++) {
+            builder.loadLocal(locals[i]);
+        }
+        builder.invoke(method);
+
+        if (method.getReturnType() == void.class) {
+            builder.returnVoid();
+        }
+        else {
+            builder.returnValue(method.getReturnType());
+        }
+    }
+
+    private static boolean isDefinedInObject(Method method) {
+        if (method.getDeclaringClass() == Object.class) {
+            return true;
+        }
+
+        Class[] types = method.getParameterTypes();
+        String name = method.getName();
+        
+        if (types.length == 0) {
+            return 
+                "hashCode".equals(name) ||
+                "clone".equals(name) ||
+                "toString".equals(name) ||
+                "finalize".equals(name);
+        }
+        else {
+            return
+                types.length == 1 &&
+                types[0] == Object.class && 
+                "equals".equals(name);
+        }
+    }
+
+    private MergedClass() {
+    }
+
+    /**
+     * InstanceFactory allows merged class instances to be requested only
+     * when first needed.
+     */
+    public interface InstanceFactory {
+        /**
+         * Return a merged class instance by index. This index corresponds to
+         * the class index used when defining the MergedClass.
+         */
+        public Object getInstance(int index);
+    }
+
+    private static class ClassEntry {
+        private final Class mClazz;
+        private final String mPrefix;
+
+        public ClassEntry(Class clazz) {
+            this(clazz, null);
+        }
+        
+        public ClassEntry(Class clazz, String prefix) {
+            mClazz = clazz;
+            mPrefix = prefix;
+        }
+
+        public Class getClazz() {
+            return mClazz;
+        }
+
+        public String getMethodPrefix() {
+            return mPrefix;
+        }
+
+        public int hashCode() {
+            int hash = mClazz.getName().hashCode();
+            return (mPrefix == null) ? hash : hash ^ mPrefix.hashCode();
+        }
+
+        public boolean equals(Object other) {
+            if (other instanceof ClassEntry) {
+                ClassEntry classEntry = (ClassEntry)other;
+                if (mClazz == classEntry.mClazz) {
+                    if (mPrefix == null) {
+                        return classEntry.mPrefix == null;
+                    }
+                    else {
+                        return mPrefix.equals(classEntry.mPrefix);
+                    }
+                }
+            }
+            return false;
+        }
+
+        public String toString() {
+            return mClazz.toString();
+        }
+    }
+
+    private static class MethodEntry {
+        private final Method mMethod;
+        private final String mName;
+        private List mParams;
+        private int mHashCode;
+
+        public MethodEntry(Method method) {
+            this(method, method.getName());
+        }
+
+        public MethodEntry(Method method, String name) {
+            mMethod = method;
+            mName = name;
+            mParams = Arrays.asList(method.getParameterTypes());
+            mHashCode = mName.hashCode() ^ mParams.hashCode();
+        }
+
+        public Method getMethod() {
+            return mMethod;
+        }
+
+        public String getName() {
+            return mName;
+        }
+
+        public boolean returnTypeDiffers(MethodEntry methodEntry) {
+            return getMethod().getReturnType() !=
+                methodEntry.getMethod().getReturnType();
+        }
+
+        public int hashCode() {
+            return mHashCode;
+        }
+
+        public boolean equals(Object other) {
+            if (!(other instanceof MethodEntry)) {
+                return false;
+            }
+            MethodEntry methodEntry = (MethodEntry)other;
+            return mName.equals(methodEntry.mName) &&
+                mParams.equals(methodEntry.mParams);
+        }
+
+        public String toString() {
+            return mMethod.toString();
+        }
+    }
+}