com.google.gwt.dev.shell.CompilingClassLoader.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gwt.dev.shell.CompilingClassLoader.java

Source

/*
 * Copyright 2008 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.dev.shell;

import com.google.gwt.core.client.GWTBridge;
import com.google.gwt.core.client.GwtScriptOnly;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JArrayType;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dev.javac.CompilationProblemReporter;
import com.google.gwt.dev.javac.CompilationState;
import com.google.gwt.dev.javac.CompilationUnit;
import com.google.gwt.dev.javac.CompiledClass;
import com.google.gwt.dev.javac.JsniMethod;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.shell.rewrite.HasAnnotation;
import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter;
import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.InstanceMethodOracle;
import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SingleJsoImplData;
import com.google.gwt.dev.util.JsniRef;
import com.google.gwt.dev.util.Name;
import com.google.gwt.dev.util.Name.InternalName;
import com.google.gwt.dev.util.Name.SourceOrBinaryName;
import com.google.gwt.dev.util.Util;
import com.google.gwt.dev.util.collect.Lists;
import com.google.gwt.dev.util.log.speedtracer.DevModeEventType;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
import com.google.gwt.util.tools.Utility;

import org.apache.commons.collections.map.AbstractReferenceMap;
import org.apache.commons.collections.map.ReferenceIdentityMap;
import org.apache.commons.collections.map.ReferenceMap;

import java.beans.Beans;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.Stack;
import java.util.TreeSet;
import java.util.concurrent.locks.ReentrantLock;

/**
 * An isolated {@link ClassLoader} for running all user code. All user files are
 * compiled from source code byte a {@link ByteCodeCompiler}. After compilation,
 * some byte code rewriting is performed to support
 * <code>JavaScriptObject</code> and its subtypes.
 * 
 * TODO: we should refactor this class to move the getClassInfoByDispId,
 * getDispId, getMethodDispatch and putMethodDispatch into a separate entity
 * since they really do not interact with the CompilingClassLoader
 * functionality.
 */
public final class CompilingClassLoader extends ClassLoader implements DispatchIdOracle {

    /**
     * Oracle that can answer questions about {@link DispatchClassInfo
     * DispatchClassInfos}.
     */
    private final class DispatchClassInfoOracle {

        /**
         * Class identifier to DispatchClassInfo mapping.
         */
        private final ArrayList<DispatchClassInfo> classIdToClassInfo = new ArrayList<DispatchClassInfo>();

        /**
         * Binary or source class name to DispatchClassInfo map.
         */
        private final Map<String, DispatchClassInfo> classNameToClassInfo = new HashMap<String, DispatchClassInfo>();

        /**
         * Clears out the contents of this oracle.
         */
        public synchronized void clear() {
            classIdToClassInfo.clear();
            classNameToClassInfo.clear();
        }

        /**
         * Returns the {@link DispatchClassInfo} for a given dispatch id.
         * 
         * @param dispId dispatch id
         * @return DispatchClassInfo for the requested dispatch id
         */
        public synchronized DispatchClassInfo getClassInfoByDispId(int dispId) {
            int classId = extractClassIdFromDispId(dispId);

            return classIdToClassInfo.get(classId);
        }

        /**
         * Returns the dispatch id for a given member reference. Member references
         * can be encoded as: "@class::field" or "@class::method(typesigs)".
         * 
         * @param jsniMemberRef a string encoding a JSNI member to use
         * @return integer encoded as ((classId << 16) | memberId)
         */
        public synchronized int getDispId(String jsniMemberRef) {
            /*
             * Map JS toString() onto the Java toString() method.
             */
            if (jsniMemberRef.equals("toString")) {
                jsniMemberRef = "@java.lang.Object::toString()";
            }

            JsniRef parsed = JsniRef.parse(jsniMemberRef);
            if (parsed == null) {
                logger.log(TreeLogger.ERROR,
                        "Malformed JSNI reference '" + jsniMemberRef + "'; expect subsequent failures",
                        new NoSuchFieldError(jsniMemberRef));
                return -1;
            }

            // Do the lookup by class name.
            String className = parsed.className();
            DispatchClassInfo dispClassInfo = getClassInfoFromClassName(className);
            if (dispClassInfo != null) {
                String memberName = parsed.memberSignature();

                /*
                 * Disallow the use of JSNI references to SingleJsoImpl interface
                 * methods. This policy is due to web-mode dispatch implementation
                 * details; resolving the JSNI reference wouldn't be just be a name
                 * replacement, instead it would be necessary to significantly alter the
                 * semantics of the hand-written JS.
                 */
                if (singleJsoImplTypes.contains(canonicalizeClassName(className))) {
                    logger.log(TreeLogger.ERROR,
                            "Invalid JSNI reference to SingleJsoImpl interface (" + className
                                    + "); consider using a trampoline. " + "Expect subsequent failures.",
                            new NoSuchFieldError(jsniMemberRef));
                    return -1;
                }

                int memberId = dispClassInfo.getMemberId(memberName);
                if (memberId < 0) {
                    if (!className.startsWith("java.")) {
                        logger.log(TreeLogger.ERROR,
                                "Member '" + memberName + "' in JSNI reference '" + jsniMemberRef
                                        + "' could not be found; expect subsequent failures",
                                new NoSuchFieldError(memberName));
                    }
                }

                return synthesizeDispId(dispClassInfo.getClassId(), memberId);
            }

            logger.log(TreeLogger.ERROR,
                    "Class '" + className + "' in JSNI reference '" + jsniMemberRef
                            + "' could not be found; expect subsequent failures",
                    new ClassNotFoundException(className));
            return -1;
        }

        /**
         * Extracts the class id from the dispatch id.
         * 
         * @param dispId
         * @return the classId encoded into this dispatch id
         */
        private int extractClassIdFromDispId(int dispId) {
            return (dispId >> 16) & 0xffff;
        }

        /**
         * Returns the {@link java.lang.Class} instance for a given binary class
         * name. It is important to avoid initializing the class because this would
         * potentially cause initializers to be run in a different order than in web
         * mode. Moreover, we may not have injected all of the JSNI code required to
         * initialize the class.
         * 
         * @param binaryClassName the binary name of a class
         * @return {@link java.lang.Class} instance or null if the given binary
         *         class name could not be found
         */
        private Class<?> getClassFromBinaryName(String binaryClassName) {
            int dims = 0;
            while (binaryClassName.endsWith("[]")) {
                dims++;
                binaryClassName = binaryClassName.substring(0, binaryClassName.length() - 2);
            }

            Class<?> clazz = primitiveTypes.get(binaryClassName);
            if (clazz == null) {
                try {
                    clazz = Class.forName(binaryClassName, false, CompilingClassLoader.this);
                } catch (ClassNotFoundException e) {
                }
            }
            // TODO(deprecation): remove this support eventually.
            if (clazz == null && binaryClassName.length() == 1
                    && "ZBCDFIJSV".indexOf(binaryClassName.charAt(0)) >= 0) {
                clazz = getDeprecatedPrimitiveType(binaryClassName.charAt(0));
                assert clazz != null;
            }
            if (dims > 0) {
                return Array.newInstance(clazz, new int[dims]).getClass();
            } else {
                return clazz;
            }
        }

        /**
         * Returns the {@link java.lang.Class} object for a class that matches the
         * source or binary name given.
         * 
         * @param className binary or source name
         * @return {@link java.lang.Class} instance, if found, or null
         */
        private Class<?> getClassFromBinaryOrSourceName(String className) {
            // Try the type oracle first
            JClassType type = typeOracle.findType(SourceOrBinaryName.toSourceName(className));
            if (type != null) {
                // Use the type oracle to compute the exact binary name
                String jniSig = type.getJNISignature();
                jniSig = jniSig.substring(1, jniSig.length() - 1);
                className = InternalName.toBinaryName(jniSig);
            }
            return getClassFromBinaryName(className);
        }

        /**
         * Returns the {@link DispatchClassInfo} associated with the class name.
         * Since we allow both binary and source names to be used in JSNI class
         * references, we need to be able to deal with the fact that multiple
         * permutations of the class name with regards to source or binary forms map
         * on the same {@link DispatchClassInfo}.
         * 
         * @param className binary or source name for a class
         * @return {@link DispatchClassInfo} associated with the binary or source
         *         class name; null if there is none
         */
        private DispatchClassInfo getClassInfoFromClassName(String className) {

            DispatchClassInfo dispClassInfo = classNameToClassInfo.get(className);
            if (dispClassInfo != null) {
                // return the cached value
                return dispClassInfo;
            }

            Class<?> cls = getClassFromBinaryOrSourceName(className);
            if (cls == null) {
                /*
                 * default to return null; mask the specific error and let the caller
                 * handle it
                 */
                return null;
            }

            // Map JSO type references to the appropriate impl class.
            if (classRewriter.isJsoIntf(cls.getName())) {
                cls = getClassFromBinaryName(cls.getName() + "$");
            }

            /*
             * we need to create a new DispatchClassInfo since we have never seen this
             * class before under any source or binary class name
             */
            int classId = classIdToClassInfo.size();

            dispClassInfo = new DispatchClassInfo(cls, classId);
            classIdToClassInfo.add(dispClassInfo);

            /*
             * Whether we created a new DispatchClassInfo or not, we need to add a
             * mapping for this name
             */
            classNameToClassInfo.put(className, dispClassInfo);

            return dispClassInfo;
        }

        @Deprecated
        private Class<?> getDeprecatedPrimitiveType(char c) {
            switch (c) {
            case 'Z':
                return boolean.class;
            case 'B':
                return byte.class;
            case 'C':
                return char.class;
            case 'D':
                return double.class;
            case 'F':
                return float.class;
            case 'I':
                return int.class;
            case 'J':
                return long.class;
            case 'S':
                return short.class;
            case 'V':
                return void.class;
            default:
                return null;
            }
        }

        /**
         * Synthesizes a dispatch identifier for the given class and member ids.
         * 
         * @param classId class index
         * @param memberId member index
         * @return dispatch identifier for the given class and member ids
         */
        private int synthesizeDispId(int classId, int memberId) {
            return (classId << 16) | memberId;
        }
    }

    /**
     * A ClassLoader that will delegate to a parent ClassLoader and fall back to
     * loading bytecode as resources from an alternate parent ClassLoader.
     */
    private static class MultiParentClassLoader extends ClassLoader {
        private final ClassLoader resources;

        public MultiParentClassLoader(ClassLoader parent, ClassLoader resources) {
            super(parent);
            assert parent != null;
            this.resources = resources;
        }

        @Override
        protected synchronized Class<?> findClass(String name) throws ClassNotFoundException {
            String resourceName = name.replace('.', '/') + ".class";
            URL url = resources.getResource(resourceName);
            if (url == null) {
                throw new ClassNotFoundException();
            }
            byte[] bytes = Util.readURLAsBytes(url);
            return defineClass(name, bytes, 0, bytes.length);
        }

        @Override
        protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
            try {
                Class c = findLoadedClass(name);
                if (c != null) {
                    if (resolve) {
                        resolveClass(c);
                    }
                    return c;
                }
                return getParent().loadClass(name);
            } catch (Throwable t) {
                // Make a second attempt not only on ClassNotFoundExceptions, but also errors like 
                // ClassCircularityError
                Class c = findClass(name);
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }
    }

    /**
     * Implements {@link InstanceMethodOracle} on behalf of the
     * {@link HostedModeClassRewriter}. Implemented using {@link TypeOracle}.
     */
    private class MyInstanceMethodOracle implements InstanceMethodOracle {

        private final Map<String, Set<JClassType>> signatureToDeclaringClasses = new HashMap<String, Set<JClassType>>();

        public MyInstanceMethodOracle(Set<JClassType> jsoTypes, JClassType javaLangObject,
                SingleJsoImplData jsoData) {

            // Record that the JSO implements its own methods
            for (JClassType type : jsoTypes) {
                for (JMethod method : type.getMethods()) {
                    if (!method.isStatic()) {
                        assert !method.isAbstract() : "Abstract method in JSO type " + method;
                        add(type, method);
                    }
                }
            }

            /*
             * Record the implementing types for methods defined in SingleJsoImpl
             * interfaces. We have to make this pass because of possible variance in
             * the return types between the abstract method declaration in the
             * interface and the concrete method.
             */
            for (String intfName : jsoData.getSingleJsoIntfTypes()) {
                // We only store the name in the data block to keep it lightweight
                JClassType intf = typeOracle.findType(Name.InternalName.toSourceName(intfName));
                JClassType jso = typeOracle.getSingleJsoImpl(intf);
                for (JMethod method : intf.getMethods()) {
                    JClassType implementingJso = findImplementingTypeForMethod(jso, method);
                    assert implementingJso != null : "Jso should contain method: " + method.getJsniSignature();
                    add(implementingJso, method);
                }
            }

            // Object clobbers everything.
            for (JMethod method : javaLangObject.getMethods()) {
                if (!method.isStatic()) {
                    String signature = createSignature(method);
                    Set<JClassType> declaringClasses = new HashSet<JClassType>();
                    signatureToDeclaringClasses.put(signature, declaringClasses);
                    declaringClasses.add(javaLangObject);
                }
            }
        }

        public String findOriginalDeclaringClass(String desc, String signature) {
            // Lookup the method.
            Set<JClassType> declaringClasses = signatureToDeclaringClasses.get(signature);
            assert declaringClasses != null : "No classes for " + signature;
            if (declaringClasses.size() == 1) {
                // Shortcut: if there's only one answer, it must be right.
                return createDescriptor(declaringClasses.iterator().next());
            }
            // Must check for assignability.
            String sourceName = desc.replace('/', '.');
            sourceName = sourceName.replace('$', '.');
            JClassType declaredType = typeOracle.findType(sourceName);

            // Check if I declare this directly.
            if (declaringClasses.contains(declaredType)) {
                return desc;
            }

            // Check to see what type I am assignable to.
            for (JClassType possibleSupertype : declaringClasses) {
                if (declaredType.isAssignableTo(possibleSupertype)) {
                    return createDescriptor(possibleSupertype);
                }
            }
            throw new IllegalArgumentException(
                    "Could not resolve signature '" + signature + "' from class '" + desc + "'");
        }

        /**
         * Record that a given JSO type contains the concrete implementation of a
         * (possibly abstract) method.
         */
        private void add(JClassType type, JMethod method) {
            String signature = createSignature(method);
            Set<JClassType> declaringClasses = signatureToDeclaringClasses.get(signature);
            if (declaringClasses == null) {
                declaringClasses = new HashSet<JClassType>();
                signatureToDeclaringClasses.put(signature, declaringClasses);
            }
            declaringClasses.add(type);
        }

        private String createDescriptor(JClassType type) {
            String jniSignature = type.getJNISignature();
            return jniSignature.substring(1, jniSignature.length() - 1);
        }

        private String createSignature(JMethod method) {
            StringBuffer sb = new StringBuffer(method.getName());
            sb.append('(');
            for (JParameter param : method.getParameters()) {
                sb.append(param.getType().getJNISignature());
            }
            sb.append(')');
            sb.append(method.getReturnType().getJNISignature());
            String signature = sb.toString();
            return signature;
        }
    }

    /**
     * Cook up the data we need to support JSO subtypes that implement interfaces
     * with methods. This includes the set of SingleJsoImpl interfaces actually
     * implemented by a JSO type, the mangled method names, and the names of the
     * Methods that should actually implement the virtual functions.
     * 
     * Given the current implementation of JSO$ and incremental execution of
     * rebinds, it's not possible for Generators to produce additional
     * JavaScriptObject subtypes, so this data can remain static.
     */
    private class MySingleJsoImplData implements SingleJsoImplData {
        private final SortedSet<String> mangledNames = new TreeSet<String>();
        private final Map<String, List<com.google.gwt.dev.asm.commons.Method>> mangledNamesToDeclarations = new HashMap<String, List<com.google.gwt.dev.asm.commons.Method>>();
        private final Map<String, List<com.google.gwt.dev.asm.commons.Method>> mangledNamesToImplementations = new HashMap<String, List<com.google.gwt.dev.asm.commons.Method>>();
        private final Set<String> unmodifiableIntfNames = Collections.unmodifiableSet(singleJsoImplTypes);
        private final SortedSet<String> unmodifiableNames = Collections.unmodifiableSortedSet(mangledNames);

        public MySingleJsoImplData() {
            // Loop over all interfaces with JSO implementations
            typeLoop: for (JClassType type : typeOracle.getSingleJsoImplInterfaces()) {
                assert type.isInterface() == type : "Expecting interfaces only";

                /*
                 * By preemptively adding all possible mangled names by which a method
                 * could be called, we greatly simplify the logic necessary to rewrite
                 * the call-site.
                 * 
                 * interface A {void m();}
                 * 
                 * interface B extends A {void z();}
                 * 
                 * becomes
                 * 
                 * c_g_p_A_m() -> JsoA$.m$()
                 * 
                 * c_g_p_B_m() -> JsoA$.m$()
                 * 
                 * c_g_p_B_z() -> JsoB$.z$()
                 */
                for (JMethod intfMethod : type.getOverridableMethods()) {
                    assert intfMethod.isAbstract() : "Expecting only abstract methods";

                    /*
                     * It is necessary to locate the implementing type on a per-method
                     * basis. Consider the case of
                     * 
                     * @SingleJsoImpl interface C extends A, B {}
                     * 
                     * Methods inherited from interfaces A and B must be dispatched to
                     * their respective JSO implementations.
                     */
                    JClassType implementingType = typeOracle.getSingleJsoImpl(intfMethod.getEnclosingType());

                    if (implementingType == null || implementingType.isAnnotationPresent(GwtScriptOnly.class)) {
                        /*
                         * This means that there is no concrete implementation of the
                         * interface by a JSO. Any implementation that might be created by a
                         * Generator won't be a JSO subtype, so we'll just ignore it as an
                         * actionable type. Were Generators ever able to create new JSO
                         * subtypes, we'd have to speculatively rewrite the callsite.
                         */
                        continue typeLoop;
                    }

                    /*
                     * Record the type as being actionable.
                     */
                    singleJsoImplTypes.add(canonicalizeClassName(getBinaryName(type)));

                    /*
                     * The mangled name adds the current interface like
                     * 
                     * com_foo_Bar_methodName
                     */
                    String mangledName = getBinaryName(type).replace('.', '_') + "_" + intfMethod.getName();
                    mangledNames.add(mangledName);

                    /*
                     * Handle virtual overrides by finding the method that we would
                     * normally invoke and using its declaring class as the dispatch
                     * target.
                     */
                    JMethod implementingMethod;
                    while ((implementingMethod = findOverloadUsingErasure(implementingType, intfMethod)) == null) {
                        implementingType = implementingType.getSuperclass();
                    }
                    // implementingmethod and implementingType cannot be null here

                    /*
                     * Create a pseudo-method declaration for the interface method. This
                     * should look something like
                     * 
                     * ReturnType method$ (ParamType, ParamType)
                     * 
                     * This must be kept in sync with the WriteJsoImpl class.
                     */
                    {
                        String decl = getBinaryOrPrimitiveName(intfMethod.getReturnType().getErasedType()) + " "
                                + intfMethod.getName() + "(";
                        for (JParameter param : intfMethod.getParameters()) {
                            decl += ",";
                            decl += getBinaryOrPrimitiveName(param.getType().getErasedType());
                        }
                        decl += ")";

                        com.google.gwt.dev.asm.commons.Method declaration = com.google.gwt.dev.asm.commons.Method
                                .getMethod(decl);
                        addToMap(mangledNamesToDeclarations, mangledName, declaration);
                    }

                    /*
                     * Cook up the a pseudo-method declaration for the concrete type. This
                     * should look something like
                     * 
                     * ReturnType method$ (JsoType, ParamType, ParamType)
                     * 
                     * This must be kept in sync with the WriteJsoImpl class.
                     */
                    {
                        String returnName = getBinaryOrPrimitiveName(
                                implementingMethod.getReturnType().getErasedType());
                        String jsoName = getBinaryOrPrimitiveName(implementingType);

                        String decl = returnName + " " + intfMethod.getName() + "$ (" + jsoName;
                        for (JParameter param : implementingMethod.getParameters()) {
                            decl += ",";
                            decl += getBinaryOrPrimitiveName(param.getType().getErasedType());
                        }
                        decl += ")";

                        com.google.gwt.dev.asm.commons.Method toImplement = com.google.gwt.dev.asm.commons.Method
                                .getMethod(decl);
                        addToMap(mangledNamesToImplementations, mangledName, toImplement);
                    }
                }
            }

            if (logger.isLoggable(TreeLogger.SPAM)) {
                TreeLogger dumpLogger = logger.branch(TreeLogger.SPAM, "SingleJsoImpl method mappings");
                for (Map.Entry<String, List<com.google.gwt.dev.asm.commons.Method>> entry : mangledNamesToImplementations
                        .entrySet()) {
                    dumpLogger.log(TreeLogger.SPAM, entry.getKey() + " -> " + entry.getValue());
                }
            }
        }

        public List<com.google.gwt.dev.asm.commons.Method> getDeclarations(String mangledName) {
            List<com.google.gwt.dev.asm.commons.Method> toReturn = mangledNamesToDeclarations.get(mangledName);
            return toReturn == null ? null : Collections.unmodifiableList(toReturn);
        }

        public List<com.google.gwt.dev.asm.commons.Method> getImplementations(String mangledName) {
            List<com.google.gwt.dev.asm.commons.Method> toReturn = mangledNamesToImplementations.get(mangledName);
            return toReturn == null ? toReturn : Collections.unmodifiableList(toReturn);
        }

        public SortedSet<String> getMangledNames() {
            return unmodifiableNames;
        }

        public Set<String> getSingleJsoIntfTypes() {
            return unmodifiableIntfNames;
        }

        /**
         * Assumes that the usual case is a 1:1 mapping.
         */
        private <K, V> void addToMap(Map<K, List<V>> map, K key, V value) {
            List<V> list = map.get(key);
            if (list == null) {
                map.put(key, Lists.create(value));
            } else {
                List<V> maybeOther = Lists.add(list, value);
                if (maybeOther != list) {
                    map.put(key, maybeOther);
                }
            }
        }

        /**
         * Looks for a concrete implementation of <code>intfMethod</code> in
         * <code>implementingType</code>.
         */
        private JMethod findOverloadUsingErasure(JClassType implementingType, JMethod intfMethod) {

            int numParams = intfMethod.getParameters().length;
            JType[] erasedTypes = new JType[numParams];
            for (int i = 0; i < numParams; i++) {
                erasedTypes[i] = intfMethod.getParameters()[i].getType().getErasedType();
            }

            outer: for (JMethod method : implementingType.getOverloads(intfMethod.getName())) {
                JParameter[] params = method.getParameters();
                if (params.length != numParams) {
                    continue;
                }
                for (int i = 0; i < numParams; i++) {
                    if (params[i].getType().getErasedType() != erasedTypes[i]) {
                        continue outer;
                    }
                }
                return method;
            }
            return null;
        }
    }

    /**
     * Only loads bootstrap classes, specifically excluding classes from the classpath. 
     */
    private static final ClassLoader bootstrapClassLoader = new ClassLoader(null) {
    };

    /**
     * The names of the bridge classes.
     */
    private static final Map<String, Class<?>> BRIDGE_CLASS_NAMES = new HashMap<String, Class<?>>();

    /**
     * The set of classes exposed into user space that actually live in hosted
     * space (thus, they bridge across the spaces).
     */
    private static final Class<?>[] BRIDGE_CLASSES = new Class<?>[] { ShellJavaScriptHost.class, GWTBridge.class };

    private static final boolean CLASS_DUMP = Boolean.getBoolean("gwt.dev.classDump");

    private static final String CLASS_DUMP_PATH = System.getProperty("gwt.dev.classDumpPath", "rewritten-classes");

    private static boolean emmaAvailable = false;

    private static EmmaStrategy emmaStrategy;

    /**
     * Caches the byte code for {@link JavaScriptHost}.
     */
    private static byte[] javaScriptHostBytes;

    private static final Map<String, Class<?>> primitiveTypes = new HashMap<String, Class<?>>();

    static {
        primitiveTypes.put(boolean.class.getSimpleName(), boolean.class);
        primitiveTypes.put(byte.class.getSimpleName(), boolean.class);
        primitiveTypes.put(char.class.getSimpleName(), boolean.class);
        primitiveTypes.put(double.class.getSimpleName(), boolean.class);
        primitiveTypes.put(float.class.getSimpleName(), boolean.class);
        primitiveTypes.put(int.class.getSimpleName(), boolean.class);
        primitiveTypes.put(long.class.getSimpleName(), boolean.class);
        primitiveTypes.put(short.class.getSimpleName(), boolean.class);
        primitiveTypes.put(void.class.getSimpleName(), boolean.class);
    }

    static {
        for (Class<?> c : BRIDGE_CLASSES) {
            BRIDGE_CLASS_NAMES.put(c.getName(), c);
        }
        /*
         * Specific support for bridging to Emma since the user classloader is
         * generally completely isolated.
         * 
         * We are looking for a specific emma class "com.vladium.emma.rt.RT". If
         * that changes in the future, this code would need to be updated as well.
         */
        try {
            Class<?> emmaBridge = Class.forName(EmmaStrategy.EMMA_RT_CLASSNAME, false,
                    Thread.currentThread().getContextClassLoader());
            BRIDGE_CLASS_NAMES.put(EmmaStrategy.EMMA_RT_CLASSNAME, emmaBridge);
            emmaAvailable = true;
        } catch (ClassNotFoundException ignored) {
        }
        emmaStrategy = EmmaStrategy.get(emmaAvailable);
    }

    private static void classDump(String name, byte[] bytes) {
        String packageName, className;
        int pos = name.lastIndexOf('.');
        if (pos < 0) {
            packageName = "";
            className = name;
        } else {
            packageName = name.substring(0, pos);
            className = name.substring(pos + 1);
        }

        File dir = new File(CLASS_DUMP_PATH + File.separator + packageName.replace('.', File.separatorChar));
        if (!dir.exists()) {
            // No need to check mkdirs result because an IOException will occur anyway
            dir.mkdirs();
        }

        File file = new File(dir, className + ".class");
        FileOutputStream fileOutput = null;
        try {
            fileOutput = new FileOutputStream(file);
            fileOutput.write(bytes);
            fileOutput.close();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fileOutput != null) {
                try {
                    fileOutput.close();
                } catch (IOException e) {
                    // oh well, we tried
                }
            }
        }
    }

    /**
     * Magic: {@link JavaScriptHost} was never compiled because it's a part of the
     * hosted mode infrastructure. However, unlike {@link #BRIDGE_CLASSES},
     * {@code JavaScriptHost} needs a separate copy per inside the ClassLoader for
     * each module.
     */
    private static void ensureJavaScriptHostBytes(TreeLogger logger) throws UnableToCompleteException {

        if (javaScriptHostBytes != null) {
            return;
        }

        String className = JavaScriptHost.class.getName();
        try {
            String path = className.replace('.', '/') + ".class";
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            URL url = cl.getResource(path);
            if (url != null) {
                javaScriptHostBytes = getClassBytesFromStream(url.openStream());
            } else {
                logger.log(TreeLogger.ERROR,
                        "Could not find required bootstrap class '" + className + "' in the classpath", null);
                throw new UnableToCompleteException();
            }
        } catch (IOException e) {
            logger.log(TreeLogger.ERROR, "Error reading class bytes for " + className, e);
            throw new UnableToCompleteException();
        }
    }

    private static JClassType findImplementingTypeForMethod(JClassType type, JMethod method) {
        JType[] methodParamTypes = method.getErasedParameterTypes();
        while (type != null) {
            for (JMethod candidate : type.getMethods()) {
                if (hasMatchingErasedSignature(method, methodParamTypes, candidate)) {
                    return type;
                }
            }
            type = type.getSuperclass();
        }
        return null;
    }

    private static byte[] getClassBytesFromStream(InputStream is) throws IOException {
        try {
            byte classBytes[] = new byte[is.available()];
            int read = 0;
            while (read < classBytes.length) {
                read += is.read(classBytes, read, classBytes.length - read);
            }
            return classBytes;
        } finally {
            Utility.close(is);
        }
    }

    private static boolean hasMatchingErasedSignature(JMethod a, JType[] aParamTypes, JMethod b) {
        if (!a.getName().equals(b.getName())) {
            return false;
        }

        JType[] bParamTypes = b.getErasedParameterTypes();
        if (aParamTypes.length != bParamTypes.length) {
            return false;
        }

        for (int i = 0; i < aParamTypes.length; ++i) {
            if (aParamTypes[i] != bParamTypes[i]) {
                return false;
            }
        }

        return true;
    }

    /**
     * The set of units whose JSNI has already been injected.
     */
    private Set<CompilationUnit> alreadyInjected = new HashSet<CompilationUnit>();

    private final HostedModeClassRewriter classRewriter;

    private CompilationState compilationState;

    private final DispatchClassInfoOracle dispClassInfoOracle = new DispatchClassInfoOracle();

    private Class<?> gwtClass, javaScriptHostClass;

    /**
     * Used by {@link #findClass(String)} to prevent reentrant JSNI injection.
     */
    private boolean isInjectingClass = false;

    private final ReentrantLock loadLock = new ReentrantLock();

    private final TreeLogger logger;

    private final Set<String> scriptOnlyClasses = new HashSet<String>();

    private ClassLoader scriptOnlyClassLoader;

    private ShellJavaScriptHost shellJavaScriptHost;

    private final Set<String> singleJsoImplTypes = new HashSet<String>();

    /**
     * Used by {@link #findClass(String)} to prevent reentrant JSNI injection.
     */
    private Stack<CompilationUnit> toInject = new Stack<CompilationUnit>();

    private final TypeOracle typeOracle;

    @SuppressWarnings("unchecked")
    private final Map<Object, Object> weakJavaWrapperCache = new ReferenceIdentityMap(AbstractReferenceMap.WEAK,
            AbstractReferenceMap.WEAK);

    @SuppressWarnings("unchecked")
    private final Map<Integer, Object> weakJsoCache = new ReferenceMap(AbstractReferenceMap.HARD,
            AbstractReferenceMap.WEAK);

    public CompilingClassLoader(TreeLogger logger, CompilationState compilationState,
            ShellJavaScriptHost javaScriptHost) throws UnableToCompleteException {
        super(null);
        this.logger = logger;
        this.compilationState = compilationState;
        this.shellJavaScriptHost = javaScriptHost;
        this.typeOracle = compilationState.getTypeOracle();

        // Assertions are always on in hosted mode.
        setDefaultAssertionStatus(true);

        ensureJavaScriptHostBytes(logger);

        // Create a class rewriter based on all the subtypes of the JSO class.
        JClassType jsoType = typeOracle.findType(JsValueGlue.JSO_CLASS);
        if (jsoType != null) {

            // Create a set of binary names.
            Set<JClassType> jsoTypes = new HashSet<JClassType>();
            JClassType[] jsoSubtypes = jsoType.getSubtypes();
            Collections.addAll(jsoTypes, jsoSubtypes);
            jsoTypes.add(jsoType);

            Set<String> jsoTypeNames = new HashSet<String>();
            Map<String, List<String>> jsoSuperTypes = new HashMap<String, List<String>>();
            for (JClassType type : jsoTypes) {
                List<String> types = new ArrayList<String>();
                types.add(getBinaryName(type.getSuperclass()));
                for (JClassType impl : type.getImplementedInterfaces()) {
                    types.add(getBinaryName(impl));
                }

                String binaryName = getBinaryName(type);
                jsoTypeNames.add(binaryName);
                jsoSuperTypes.put(binaryName, types);
            }

            SingleJsoImplData singleJsoImplData = new MySingleJsoImplData();

            MyInstanceMethodOracle mapper = new MyInstanceMethodOracle(jsoTypes, typeOracle.getJavaLangObject(),
                    singleJsoImplData);
            classRewriter = new HostedModeClassRewriter(jsoTypeNames, jsoSuperTypes, singleJsoImplData, mapper);
        } else {
            // If we couldn't find the JSO class, we don't need to do any rewrites.
            classRewriter = null;
        }
    }

    /**
     * Retrieves the mapped JSO for a given unique id, provided the id was
     * previously cached and the JSO has not been garbage collected.
     * 
     * @param uniqueId the previously stored unique id
     * @return the mapped JSO, or <code>null</code> if the id was not previously
     *         mapped or if the JSO has been garbage collected
     */
    public Object getCachedJso(int uniqueId) {
        return weakJsoCache.get(uniqueId);
    }

    /**
     * Returns the {@link DispatchClassInfo} for a given dispatch id.
     * 
     * @param dispId dispatch identifier
     * @return {@link DispatchClassInfo} for a given dispatch id or null if one
     *         does not exist
     */
    public DispatchClassInfo getClassInfoByDispId(int dispId) {
        return dispClassInfoOracle.getClassInfoByDispId(dispId);
    }

    /**
     * Returns the dispatch id for a JSNI member reference.
     * 
     * @param jsniMemberRef a JSNI member reference
     * @return dispatch id or -1 if the JSNI member reference could not be found
     */
    public int getDispId(String jsniMemberRef) {
        return dispClassInfoOracle.getDispId(jsniMemberRef);
    }

    /**
     * Retrieves the mapped wrapper for a given Java Object, provided the wrapper
     * was previously cached and has not been garbage collected.
     * 
     * @param javaObject the Object being wrapped
     * @return the mapped wrapper, or <code>null</code> if the Java object mapped
     *         or if the wrapper has been garbage collected
     */
    public Object getWrapperForObject(Object javaObject) {
        return weakJavaWrapperCache.get(javaObject);
    }

    /**
     * Weakly caches a given JSO by unique id. A cached JSO can be looked up by
     * unique id until it is garbage collected.
     * 
     * @param uniqueId a unique id associated with the JSO
     * @param jso the value to cache
     */
    public void putCachedJso(int uniqueId, Object jso) {
        weakJsoCache.put(uniqueId, jso);
    }

    /**
     * Weakly caches a wrapper for a given Java Object.
     * 
     * @param javaObject the Object being wrapped
     * @param wrapper the mapped wrapper
     */
    public void putWrapperForObject(Object javaObject, Object wrapper) {
        weakJavaWrapperCache.put(javaObject, wrapper);
    }

    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        if (className == null) {
            throw new ClassNotFoundException("null class name", new NullPointerException());
        }

        if (className.equals("com.google.gwt.core.ext.debug.JsoEval")) {
            // In addition to the system ClassLoader, we let JsoEval be available
            // from this CompilingClassLoader in case that's where the debugger
            // happens to look.
            return ClassLoader.getSystemClassLoader().loadClass(className);
        }

        loadLock.lock();
        try {

            if (scriptOnlyClasses.contains(className)) {
                // Allow the child ClassLoader to handle this
                throw new ClassNotFoundException();
            }

            // Check for a bridge class that spans hosted and user space.
            if (BRIDGE_CLASS_NAMES.containsKey(className)) {
                return BRIDGE_CLASS_NAMES.get(className);
            }

            // Get the bytes, compiling if necessary.
            byte[] classBytes = findClassBytes(className);
            if (classBytes == null) {
                throw new ClassNotFoundException(className);
            }

            if (HasAnnotation.hasAnnotation(classBytes, GwtScriptOnly.class)) {
                scriptOnlyClasses.add(className);
                maybeInitializeScriptOnlyClassLoader();

                /*
                 * Release the lock before side-loading from scriptOnlyClassLoader. This prevents deadlock
                 * conditions when a class from scriptOnlyClassLoader ends up trying to call back into this
                 * classloader from another thread. 
                 */
                loadLock.unlock();

                // Also don't run the static initializer to lower the risk of deadlock. 
                return Class.forName(className, false, scriptOnlyClassLoader);
            }

            /*
             * Prevent reentrant problems where classes that need to be injected have
             * circular dependencies on one another via JSNI and inheritance. This check
             * ensures that a class's supertype can refer to the subtype (static
             * members, etc) via JSNI references by ensuring that the Class for the
             * subtype will have been defined before injecting the JSNI for the
             * supertype.
             */
            boolean localInjection;
            if (!isInjectingClass) {
                localInjection = isInjectingClass = true;
            } else {
                localInjection = false;
            }

            Class<?> newClass = defineClass(className, classBytes, 0, classBytes.length);
            if (className.equals(JavaScriptHost.class.getName())) {
                javaScriptHostClass = newClass;
                updateJavaScriptHost();
            }

            /*
            * We have to inject the JSNI code after defining the class, since dispId
            * assignment is based around reflection on Class objects. Don't inject JSNI
            * when loading a JSO interface class; just wait until the implementation
            * class is loaded.
            */
            if (!classRewriter.isJsoIntf(className)) {
                CompilationUnit unit = getUnitForClassName(canonicalizeClassName(className));
                if (unit != null) {
                    toInject.push(unit);
                }
            }

            if (localInjection) {
                try {
                    /*
                    * Can't use an iterator here because calling injectJsniFor may cause
                    * additional entries to be added.
                    */
                    while (toInject.size() > 0) {
                        CompilationUnit unit = toInject.remove(0);
                        if (!alreadyInjected.contains(unit)) {
                            injectJsniMethods(unit);
                            alreadyInjected.add(unit);
                        }
                    }
                } finally {
                    isInjectingClass = false;
                }
            }

            if (className.equals("com.google.gwt.core.client.GWT")) {
                gwtClass = newClass;
                updateGwtClass();
            }

            return newClass;
        } finally {
            if (loadLock.isLocked()) {
                loadLock.unlock();
            }
        }
    }

    /**
     * Remove some of the excess locking that we'd normally inherit from loadClass.
     */
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        // at design time we want to provide parent ClassLoader, so keep default implementation
        if (Beans.isDesignTime()) {
            return super.loadClass(name, resolve);
        }

        Class c = findLoadedClass(name);
        if (c != null) {
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }

        assert getParent() == null;

        try {
            c = bootstrapClassLoader.loadClass(name);
        } catch (ClassNotFoundException e) {
            c = findClass(name);
        }

        if (resolve) {
            resolveClass(c);
        }

        return c;
    }

    void clear() {
        // Release our references to the shell.
        shellJavaScriptHost = null;
        scriptOnlyClasses.clear();
        scriptOnlyClassLoader = null;
        updateJavaScriptHost();
        weakJsoCache.clear();
        weakJavaWrapperCache.clear();
        dispClassInfoOracle.clear();
    }

    /**
     * Convert a binary class name into a resource-like name.
     */
    private String canonicalizeClassName(String className) {
        String lookupClassName = className.replace('.', '/');
        // A JSO impl class ends with $, strip it
        if (classRewriter != null && classRewriter.isJsoImpl(className)) {
            lookupClassName = lookupClassName.substring(0, lookupClassName.length() - 1);
        }
        return lookupClassName;
    }

    @SuppressWarnings("deprecation")
    private byte[] findClassBytes(String className) {
        if (JavaScriptHost.class.getName().equals(className)) {
            // No need to rewrite.
            return javaScriptHostBytes;
        }

        if (classRewriter != null && classRewriter.isJsoIntf(className)) {
            // Generate a synthetic JSO interface class.
            byte[] newBytes = classRewriter.writeJsoIntf(className);
            if (CLASS_DUMP) {
                classDump(className, newBytes);
            }
            return newBytes;
        }

        // A JSO impl class needs the class bytes for the original class.
        String lookupClassName = canonicalizeClassName(className);

        CompiledClass compiledClass = compilationState.getClassFileMap().get(lookupClassName);

        CompilationUnit unit = (compiledClass == null) ? getUnitForClassName(lookupClassName)
                : compiledClass.getUnit();
        if (emmaAvailable) {
            /*
             * build the map for anonymous classes. Do so only if unit has anonymous
             * classes, jsni methods, is not super-source and the map has not been
             * built before.
             */
            List<JsniMethod> jsniMethods = (unit == null) ? null : unit.getJsniMethods();
            if (unit != null && !unit.isSuperSource() && !unit.isGenerated() && unit.hasAnonymousClasses()
                    && jsniMethods != null && jsniMethods.size() > 0 && !unit.createdClassMapping()) {
                if (!unit.constructAnonymousClassMappings(logger)) {
                    logger.log(TreeLogger.ERROR,
                            "Our heuristic for mapping anonymous classes between compilers "
                                    + "failed. Unsafe to continue because the wrong jsni code "
                                    + "could end up running. className = " + className);
                    return null;
                }
            }
        }

        byte classBytes[] = null;
        if (compiledClass != null) {
            classBytes = compiledClass.getBytes();
            if (!compiledClass.getUnit().isSuperSource()) {
                classBytes = emmaStrategy.getEmmaClassBytes(classBytes, lookupClassName,
                        compiledClass.getUnit().getLastModified());
            } else {
                if (logger.isLoggable(TreeLogger.SPAM)) {
                    logger.log(TreeLogger.SPAM,
                            "no emma instrumentation for " + lookupClassName + " because it is from super-source");
                }
            }
        } else if (emmaAvailable) {
            /*
             * TypeOracle does not know about this class. Most probably, this class
             * was referenced in one of the classes loaded from disk. Check if we can
             * find it on disk. Typically this is a synthetic class added by the
             * compiler.
             */
            if (typeHasCompilationUnit(lookupClassName) && CompilationUnit.isClassnameGenerated(className)) {
                /*
                 * modification time = 0 ensures that whatever is on the disk is always
                 * loaded.
                 */
                if (logger.isLoggable(TreeLogger.DEBUG)) {
                    logger.log(TreeLogger.DEBUG, "EmmaStrategy: loading " + lookupClassName
                            + " from disk even though TypeOracle does not know about it");
                }
                classBytes = emmaStrategy.getEmmaClassBytes(null, lookupClassName, 0);
            }
        }
        if (classBytes != null && classRewriter != null) {
            Map<String, String> anonymousClassMap = Collections.emptyMap();
            if (unit != null) {
                anonymousClassMap = unit.getAnonymousClassMap();
            }
            byte[] newBytes = classRewriter.rewrite(typeOracle, className, classBytes, anonymousClassMap);
            if (CLASS_DUMP) {
                if (!Arrays.equals(classBytes, newBytes)) {
                    classDump(className, newBytes);
                }
            }
            classBytes = newBytes;
        }

        if (unit != null && unit.isError()) {
            // Compile worked, but the unit had some kind of error (JSNI?)
            CompilationProblemReporter.reportErrors(logger, unit, false);
        }

        return classBytes;
    }

    private String getBinaryName(JClassType type) {
        String name = type.getPackage().getName() + '.';
        name += type.getName().replace('.', '$');
        return name;
    }

    private String getBinaryOrPrimitiveName(JType type) {
        JArrayType asArray = type.isArray();
        JClassType asClass = type.isClassOrInterface();
        JPrimitiveType asPrimitive = type.isPrimitive();
        if (asClass != null) {
            return getBinaryName(asClass);
        } else if (asPrimitive != null) {
            return asPrimitive.getQualifiedSourceName();
        } else if (asArray != null) {
            JType componentType = asArray.getComponentType();
            return getBinaryOrPrimitiveName(componentType) + "[]";
        } else {
            throw new InternalCompilerException("Cannot create binary name for " + type.getQualifiedSourceName());
        }
    }

    /**
     * Returns the compilationUnit corresponding to the className. For nested
     * classes, the unit corresponding to the top level type is returned.
     * 
     * Since a file might have several top-level types, search using classFileMap.
     */
    private CompilationUnit getUnitForClassName(String className) {
        String mainTypeName = className;
        int index = mainTypeName.length();
        CompiledClass cc = null;
        while (cc == null && index != -1) {
            mainTypeName = mainTypeName.substring(0, index);
            cc = compilationState.getClassFileMap().get(mainTypeName);
            index = mainTypeName.lastIndexOf('$');
        }
        return cc == null ? null : cc.getUnit();
    }

    private void injectJsniMethods(CompilationUnit unit) {
        if (unit == null || unit.getJsniMethods() == null) {
            return;
        }
        Event event = SpeedTracerLogger.start(DevModeEventType.LOAD_JSNI, "unit", unit.getTypeName());
        try {
            shellJavaScriptHost.createNativeMethods(logger, unit.getJsniMethods(), this);
        } finally {
            event.end();
        }
    }

    private void maybeInitializeScriptOnlyClassLoader() {
        if (scriptOnlyClassLoader == null) {
            scriptOnlyClassLoader = new MultiParentClassLoader(this,
                    Thread.currentThread().getContextClassLoader());
        }
    }

    private boolean typeHasCompilationUnit(String className) {
        return getUnitForClassName(className) != null;
    }

    /**
     * Tricky one, this. Reaches over into this modules's JavaScriptHost class and
     * sets its static 'host' field to our module space.
     * 
     * @see JavaScriptHost
     */
    private void updateGwtClass() {
        if (gwtClass == null) {
            return;
        }
        Throwable caught;
        try {
            GWTBridgeImpl bridge;
            if (shellJavaScriptHost == null) {
                bridge = null;
            } else {
                bridge = new GWTBridgeImpl(shellJavaScriptHost);
            }
            final Class<?>[] paramTypes = new Class[] { GWTBridge.class };
            Method setBridgeMethod = gwtClass.getDeclaredMethod("setBridge", paramTypes);
            setBridgeMethod.setAccessible(true);
            setBridgeMethod.invoke(gwtClass, new Object[] { bridge });
            return;
        } catch (SecurityException e) {
            caught = e;
        } catch (NoSuchMethodException e) {
            caught = e;
        } catch (IllegalArgumentException e) {
            caught = e;
        } catch (IllegalAccessException e) {
            caught = e;
        } catch (InvocationTargetException e) {
            caught = e.getTargetException();
        }
        throw new RuntimeException("Error initializing GWT bridge", caught);
    }

    /**
     * Tricky one, this. Reaches over into this modules's JavaScriptHost class and
     * sets its static 'host' field to our module space.
     * 
     * @see JavaScriptHost
     */
    private void updateJavaScriptHost() {
        if (javaScriptHostClass == null) {
            return;
        }
        Throwable caught;
        try {
            final Class<?>[] paramTypes = new Class[] { ShellJavaScriptHost.class };
            Method setHostMethod = javaScriptHostClass.getMethod("setHost", paramTypes);
            setHostMethod.invoke(javaScriptHostClass, new Object[] { shellJavaScriptHost });
            return;
        } catch (SecurityException e) {
            caught = e;
        } catch (NoSuchMethodException e) {
            caught = e;
        } catch (IllegalArgumentException e) {
            caught = e;
        } catch (IllegalAccessException e) {
            caught = e;
        } catch (InvocationTargetException e) {
            caught = e.getTargetException();
        }
        throw new RuntimeException("Error initializing JavaScriptHost", caught);
    }
}