com.google.gwt.dev.jjs.ast.JProgram.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gwt.dev.jjs.ast.JProgram.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.jjs.ast;

import com.google.gwt.dev.jjs.Correlation.Literal;
import com.google.gwt.dev.jjs.CorrelationFactory;
import com.google.gwt.dev.jjs.CorrelationFactory.DummyCorrelationFactory;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.jjs.SourceOrigin;
import com.google.gwt.dev.jjs.ast.JField.Disposition;
import com.google.gwt.dev.jjs.ast.js.JsCastMap;
import com.google.gwt.dev.jjs.ast.js.JsniMethodBody;
import com.google.gwt.dev.jjs.impl.CodeSplitter;
import com.google.gwt.dev.util.collect.Lists;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Root for the AST representing an entire Java program.
 */
public class JProgram extends JNode {
    private static final class ArrayTypeComparator implements Comparator<JArrayType>, Serializable {
        public int compare(JArrayType o1, JArrayType o2) {
            int comp = o1.getDims() - o2.getDims();
            if (comp != 0) {
                return comp;
            }
            return o1.getName().compareTo(o2.getName());
        }
    }

    public static final Set<String> CODEGEN_TYPES_SET = new LinkedHashSet<String>(
            Arrays.asList("com.google.gwt.lang.Array", "com.google.gwt.lang.Cast",
                    "com.google.gwt.lang.CollapsedPropertyHolder", "com.google.gwt.lang.Exceptions",
                    "com.google.gwt.lang.LongLib", "com.google.gwt.lang.Stats", "com.google.gwt.lang.Util"));

    /*
     * Types which are not referenced by any Java code, but are required to exist
     * after Java optimizations have run in order to be used by backend
     * code-generation. These classes and their members, are considered live
     * by ControlFlowAnalysis, at all times. Immortal types always live in the
     * initial fragment and their definitions are hoisted to appear before all
     * other types. Only static methods and fields are allowed, and no clinits
     * are run. Field initializers must be primitives, literals, or one of
     * JSO.createObject() or JSO.createArray().
     *
     * Classes are inserted into the JsAST in the order they appear in the Set.
     */
    public static final Set<String> IMMORTAL_CODEGEN_TYPES_SET = new LinkedHashSet<String>(
            Arrays.asList("com.google.gwt.lang.SeedUtil"));

    public static final Set<String> INDEX_TYPES_SET = new LinkedHashSet<String>(
            Arrays.asList("java.io.Serializable", "java.lang.Object", "java.lang.String", "java.lang.Class",
                    "java.lang.CharSequence", "java.lang.Cloneable", "java.lang.Comparable", "java.lang.Enum",
                    "java.lang.Iterable", "java.util.Iterator", "java.lang.AssertionError", "java.lang.Boolean",
                    "java.lang.Byte", "java.lang.Character", "java.lang.Short", "java.lang.Integer",
                    "java.lang.Long", "java.lang.Float", "java.lang.Double", "java.lang.Throwable",
                    "com.google.gwt.core.client.GWT", JProgram.JAVASCRIPTOBJECT,
                    "com.google.gwt.lang.ClassLiteralHolder", "com.google.gwt.core.client.RunAsyncCallback",
                    "com.google.gwt.core.client.impl.AsyncFragmentLoader", "com.google.gwt.core.client.impl.Impl",
                    "com.google.gwt.lang.EntryMethodHolder", "com.google.gwt.core.client.prefetch.RunAsyncCode"));

    public static final String JAVASCRIPTOBJECT = "com.google.gwt.core.client.JavaScriptObject";

    static final Map<String, Set<String>> traceMethods = new HashMap<String, Set<String>>();

    private static final Comparator<JArrayType> ARRAYTYPE_COMPARATOR = new ArrayTypeComparator();

    private static final int IS_ARRAY = 2;

    private static final int IS_CLASS = 3;

    private static final int IS_INTERFACE = 1;

    private static final int IS_NULL = 0;

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

    @Deprecated
    private static final Map<String, JPrimitiveType> primitiveTypesDeprecated = new HashMap<String, JPrimitiveType>();

    static {
        CODEGEN_TYPES_SET.addAll(IMMORTAL_CODEGEN_TYPES_SET);
        INDEX_TYPES_SET.addAll(CODEGEN_TYPES_SET);

        /*
         * The format to trace methods is a colon-separated list of
         * "className.methodName", such as "Hello.onModuleLoad:Foo.bar". You can
         * fully-qualify a class to disambiguate classes, and you can also append
         * the JSNI signature of the method to disambiguate overloads, ala
         * "Foo.bar(IZ)".
         */
        String toTrace = System.getProperty("gwt.jjs.traceMethods");
        if (toTrace != null) {
            String[] split = toTrace.split(":");
            for (String str : split) {
                int pos = str.lastIndexOf('.');
                if (pos > 0) {
                    String className = str.substring(0, pos);
                    String methodName = str.substring(pos + 1);
                    Set<String> set = traceMethods.get(className);
                    if (set == null) {
                        set = new HashSet<String>();
                        traceMethods.put(className, set);
                    }
                    set.add(methodName);
                }
            }
        }

        primitiveTypes.put(JPrimitiveType.BOOLEAN.getName(), JPrimitiveType.BOOLEAN);
        primitiveTypes.put(JPrimitiveType.BYTE.getName(), JPrimitiveType.BYTE);
        primitiveTypes.put(JPrimitiveType.CHAR.getName(), JPrimitiveType.CHAR);
        primitiveTypes.put(JPrimitiveType.DOUBLE.getName(), JPrimitiveType.DOUBLE);
        primitiveTypes.put(JPrimitiveType.FLOAT.getName(), JPrimitiveType.FLOAT);
        primitiveTypes.put(JPrimitiveType.INT.getName(), JPrimitiveType.INT);
        primitiveTypes.put(JPrimitiveType.LONG.getName(), JPrimitiveType.LONG);
        primitiveTypes.put(JPrimitiveType.SHORT.getName(), JPrimitiveType.SHORT);
        primitiveTypes.put(JPrimitiveType.VOID.getName(), JPrimitiveType.VOID);

        primitiveTypesDeprecated.put(JPrimitiveType.BOOLEAN.getJsniSignatureName(), JPrimitiveType.BOOLEAN);
        primitiveTypesDeprecated.put(JPrimitiveType.BYTE.getJsniSignatureName(), JPrimitiveType.BYTE);
        primitiveTypesDeprecated.put(JPrimitiveType.CHAR.getJsniSignatureName(), JPrimitiveType.CHAR);
        primitiveTypesDeprecated.put(JPrimitiveType.DOUBLE.getJsniSignatureName(), JPrimitiveType.DOUBLE);
        primitiveTypesDeprecated.put(JPrimitiveType.FLOAT.getJsniSignatureName(), JPrimitiveType.FLOAT);
        primitiveTypesDeprecated.put(JPrimitiveType.INT.getJsniSignatureName(), JPrimitiveType.INT);
        primitiveTypesDeprecated.put(JPrimitiveType.LONG.getJsniSignatureName(), JPrimitiveType.LONG);
        primitiveTypesDeprecated.put(JPrimitiveType.SHORT.getJsniSignatureName(), JPrimitiveType.SHORT);
        primitiveTypesDeprecated.put(JPrimitiveType.VOID.getJsniSignatureName(), JPrimitiveType.VOID);
    }

    /**
     * Helper to create an assignment, used to initalize fields, etc.
     */
    public static JExpressionStatement createAssignmentStmt(SourceInfo info, JExpression lhs, JExpression rhs) {
        JBinaryOperation assign = new JBinaryOperation(info, lhs.getType(), JBinaryOperator.ASG, lhs, rhs);
        return assign.makeStatement();
    }

    public static JLocal createLocal(SourceInfo info, String name, JType type, boolean isFinal,
            JMethodBody enclosingMethodBody) {
        assert (name != null);
        assert (type != null);
        assert (enclosingMethodBody != null);
        JLocal x = new JLocal(info, name, type, isFinal, enclosingMethodBody);
        enclosingMethodBody.addLocal(x);
        return x;
    }

    public static JParameter createParameter(SourceInfo info, String name, JType type, boolean isFinal,
            boolean isThis, JMethod enclosingMethod) {
        assert (name != null);
        assert (type != null);
        assert (enclosingMethod != null);

        JParameter x = new JParameter(info, name, type, isFinal, isThis, enclosingMethod);

        enclosingMethod.addParam(x);
        return x;
    }

    public static List<JDeclaredType> deserializeTypes(ObjectInputStream stream)
            throws IOException, ClassNotFoundException {
        @SuppressWarnings("unchecked")
        List<JDeclaredType> types = (List<JDeclaredType>) stream.readObject();
        for (JDeclaredType type : types) {
            type.readMembers(stream);
        }
        for (JDeclaredType type : types) {
            type.readMethodBodies(stream);
        }
        return types;
    }

    public static String getJsniSig(JMethod method) {
        return getJsniSig(method, true);
    }

    public static String getJsniSig(JMethod method, boolean addReturnType) {
        StringBuilder sb = new StringBuilder();
        sb.append(method.getName());
        sb.append("(");
        for (int i = 0; i < method.getOriginalParamTypes().size(); ++i) {
            JType type = method.getOriginalParamTypes().get(i);
            sb.append(type.getJsniSignatureName());
        }
        sb.append(")");
        if (addReturnType) {
            sb.append(method.getOriginalReturnType().getJsniSignatureName());
        }
        return sb.toString();
    }

    public static boolean isClinit(JMethod method) {
        JDeclaredType enclosingType = method.getEnclosingType();
        if ((enclosingType != null) && (method == enclosingType.getMethods().get(0))) {
            assert (method.getName().equals("$clinit"));
            return true;
        } else {
            return false;
        }
    }

    public static boolean isTracingEnabled() {
        return traceMethods.size() > 0;
    }

    /**
     * The same as {@link #lastFragmentLoadingBefore(int, int...)}, except that
     * all of the parameters must be passed explicitly. The instance method should
     * be preferred whenever a JProgram instance is available.
     * 
     * @param initialSeq The initial split point sequence of the program
     * @param numSps The number of split points in the program
     * @param firstFragment The first fragment to consider
     * @param restFragments The rest of the fragments to consider
     */
    public static int lastFragmentLoadingBefore(List<Integer> initialSeq, int numSps, int firstFragment,
            int... restFragments) {
        int latest = firstFragment;
        for (int frag : restFragments) {
            latest = pairwiseLastFragmentLoadingBefore(initialSeq, numSps, latest, frag);
        }
        return latest;
    }

    public static void serializeTypes(List<JDeclaredType> types, ObjectOutputStream stream) throws IOException {
        stream.writeObject(types);
        for (JDeclaredType type : types) {
            type.writeMembers(stream);
        }
        for (JDeclaredType type : types) {
            type.writeMethodBodies(stream);
        }
    }

    /**
     * The main logic behind {@link #lastFragmentLoadingBefore(int, int...)} and
     * {@link #lastFragmentLoadingBefore(List, int, int, int...)}.
     */
    private static int pairwiseLastFragmentLoadingBefore(List<Integer> initialSeq, int numSps, int frag1,
            int frag2) {
        if (frag1 == frag2) {
            return frag1;
        }

        if (frag1 == 0) {
            return 0;
        }

        if (frag2 == 0) {
            return 0;
        }

        // See if either is in the initial sequence
        int initPos1 = initialSeq.indexOf(frag1);
        int initPos2 = initialSeq.indexOf(frag2);

        // If both are in the initial sequence, then pick the earlier
        if (initPos1 >= 0 && initPos2 >= 0) {
            if (initPos1 < initPos2) {
                return frag1;
            }
            return frag2;
        }

        // If exactly one is in the initial sequence, then it's the earlier one
        if (initPos1 >= 0) {
            return frag1;
        }
        if (initPos2 >= 0) {
            return frag2;
        }

        assert (initPos1 < 0 && initPos2 < 0);
        assert (frag1 != frag2);

        // They are both leftovers or exclusive. Leftovers goes first in all cases.
        return CodeSplitter.getLeftoversFragmentNumber(numSps);
    }

    public final List<JClassType> codeGenTypes = new ArrayList<JClassType>();
    public final List<JClassType> immortalCodeGenTypes = new ArrayList<JClassType>();

    public final JTypeOracle typeOracle = new JTypeOracle(this);

    /**
     * Special serialization treatment.
     */
    private transient List<JDeclaredType> allTypes = new ArrayList<JDeclaredType>();

    private final HashMap<JType, JArrayType> arrayTypes = new HashMap<JType, JArrayType>();

    private IdentityHashMap<JReferenceType, JsCastMap> castMaps;

    private Map<JType, JField> classLiteralFields;

    /**
     * A factory to create correlations.
     */
    private final CorrelationFactory correlator;

    private final List<JMethod> entryMethods = new ArrayList<JMethod>();

    private final Map<String, JField> indexedFields = new HashMap<String, JField>();

    private final Map<String, JMethod> indexedMethods = new HashMap<String, JMethod>();

    private final Map<String, JDeclaredType> indexedTypes = new HashMap<String, JDeclaredType>();

    private final Map<JMethod, JMethod> instanceToStaticMap = new IdentityHashMap<JMethod, JMethod>();

    private Map<JReferenceType, Integer> queryIdsByType;

    /**
     * Filled in by ReplaceRunAsync, once the numbers are known.
     */
    private List<JRunAsync> runAsyncs = Lists.create();

    private List<Integer> splitPointInitialSequence = Lists.create();

    private final Map<JMethod, JMethod> staticToInstanceMap = new IdentityHashMap<JMethod, JMethod>();

    private JClassType typeClass;

    private JInterfaceType typeJavaIoSerializable;

    private JInterfaceType typeJavaLangCloneable;

    private JClassType typeJavaLangEnum;

    private JClassType typeJavaLangObject;

    private final Map<String, JDeclaredType> typeNameMap = new HashMap<String, JDeclaredType>();

    private List<JReferenceType> typesByQueryId;

    private JClassType typeSpecialClassLiteralHolder;

    private JClassType typeSpecialJavaScriptObject;

    private JClassType typeString;

    public JProgram() {
        this(DummyCorrelationFactory.INSTANCE);
    }

    /**
     * Constructor.
     * 
     * @param correlator Controls whether or not SourceInfo nodes created via the
     *          JProgram will record descendant information. Enabling this feature
     *          will collect extra data during the compilation cycle, but at a
     *          cost of memory and object allocations.
     */
    public JProgram(CorrelationFactory correlator) {
        super(correlator.makeSourceInfo(SourceOrigin.create(0, JProgram.class.getName())));

        this.correlator = correlator;
    }

    public void addEntryMethod(JMethod entryPoint) {
        assert !entryMethods.contains(entryPoint);
        entryMethods.add(entryPoint);
    }

    public void addType(JDeclaredType type) {
        allTypes.add(type);
        String name = type.getName();
        putIntoTypeMap(name, type);

        if (CODEGEN_TYPES_SET.contains(name)) {
            codeGenTypes.add((JClassType) type);
        }

        if (IMMORTAL_CODEGEN_TYPES_SET.contains(name)) {
            immortalCodeGenTypes.add((JClassType) type);
        }

        if (INDEX_TYPES_SET.contains(name)) {
            indexedTypes.put(type.getShortName(), type);
            for (JMethod method : type.getMethods()) {
                if (!method.isPrivate()) {
                    indexedMethods.put(type.getShortName() + '.' + method.getName(), method);
                }
            }
            for (JField field : type.getFields()) {
                indexedFields.put(type.getShortName() + '.' + field.getName(), field);
            }
            if (name.equals("java.lang.Object")) {
                typeJavaLangObject = (JClassType) type;
            } else if (name.equals("java.lang.String")) {
                typeString = (JClassType) type;
            } else if (name.equals("java.lang.Enum")) {
                typeJavaLangEnum = (JClassType) type;
            } else if (name.equals("java.lang.Class")) {
                typeClass = (JClassType) type;
            } else if (name.equals(JAVASCRIPTOBJECT)) {
                typeSpecialJavaScriptObject = (JClassType) type;
            } else if (name.equals("com.google.gwt.lang.ClassLiteralHolder")) {
                typeSpecialClassLiteralHolder = (JClassType) type;
            } else if (name.equals("java.lang.Cloneable")) {
                typeJavaLangCloneable = (JInterfaceType) type;
            } else if (name.equals("java.io.Serializable")) {
                typeJavaIoSerializable = (JInterfaceType) type;
            }
        }
    }

    public JClassType createClass(SourceInfo info, String name, boolean isAbstract, boolean isFinal) {
        JClassType x = new JClassType(info, name, isAbstract, isFinal);
        addType(x);
        return x;
    }

    public JConstructor createConstructor(SourceInfo info, JClassType enclosingType) {
        JConstructor x = new JConstructor(info, enclosingType);
        x.setBody(new JMethodBody(info));
        if (indexedTypes.containsValue(enclosingType)) {
            indexedMethods.put(enclosingType.getShortName() + '.' + enclosingType.getShortName(), x);
        }

        enclosingType.addMethod(x);
        return x;
    }

    public JEnumType createEnum(SourceInfo info, String name, boolean isAbstract) {
        JEnumType x = new JEnumType(info, name, isAbstract);
        x.setSuperClass(getTypeJavaLangEnum());

        allTypes.add(x);
        putIntoTypeMap(name, x);

        return x;
    }

    public JField createEnumField(SourceInfo info, String name, JEnumType enclosingType, JClassType type,
            int ordinal) {
        assert (name != null);
        assert (type != null);
        assert (ordinal >= 0);

        JEnumField x = new JEnumField(info, name, ordinal, enclosingType, type);
        enclosingType.addField(x);
        return x;
    }

    public JField createField(SourceInfo info, String name, JDeclaredType enclosingType, JType type,
            boolean isStatic, Disposition disposition) {
        assert (name != null);
        assert (enclosingType != null);
        assert (type != null);

        JField x = new JField(info, name, enclosingType, type, isStatic, disposition);

        if (indexedTypes.containsValue(enclosingType)) {
            indexedFields.put(enclosingType.getShortName() + '.' + name, x);
        }

        enclosingType.addField(x);
        return x;
    }

    public JInterfaceType createInterface(SourceInfo info, String name) {
        JInterfaceType x = new JInterfaceType(info, name);
        addType(x);
        return x;
    }

    public JMethod createMethod(SourceInfo info, String name, JDeclaredType enclosingType, JType returnType,
            boolean isAbstract, boolean isStatic, boolean isFinal, AccessModifier access, boolean isNative) {
        assert (name != null);
        assert (enclosingType != null);
        assert (returnType != null);
        assert (!isAbstract || !isNative);
        JMethod x = new JMethod(info, name, enclosingType, returnType, isAbstract, isStatic, isFinal, access);
        if (isNative) {
            x.setBody(new JsniMethodBody(info));
        } else if (!isAbstract) {
            x.setBody(new JMethodBody(info));
        }

        if (access != AccessModifier.PRIVATE && indexedTypes.containsValue(enclosingType)) {
            indexedMethods.put(enclosingType.getShortName() + '.' + name, x);
        }

        enclosingType.addMethod(x);
        return x;
    }

    /**
     * Create a SourceInfo object when the source is derived from a physical
     * location.
     */
    public SourceInfo createSourceInfo(int startPos, int endPos, int startLine, String fileName) {
        return correlator.makeSourceInfo(SourceOrigin.create(startPos, endPos, startLine, fileName));
    }

    /**
     * Create a SourceInfo object when the source is derived from a physical
     * location.
     */
    public SourceInfo createSourceInfo(int startLine, String fileName) {
        return correlator.makeSourceInfo(SourceOrigin.create(startLine, fileName));
    }

    /**
     * Create a SourceInfo object when the source is created by the compiler
     * itself.
     */
    public SourceInfo createSourceInfoSynthetic(Class<?> caller) {
        // TODO: consider using Java stack frames to discover the caller's file
        // and line number.
        return createSourceInfo(0, caller.getName());
    }

    /**
     * Return the least upper bound of a set of types. That is, the smallest type
     * that is a supertype of all the input types.
     */
    public JReferenceType generalizeTypes(Collection<? extends JReferenceType> types) {
        assert (types != null);
        assert (!types.isEmpty());
        Iterator<? extends JReferenceType> it = types.iterator();
        JReferenceType curType = it.next();
        while (it.hasNext()) {
            curType = generalizeTypes(curType, it.next());
        }
        return curType;
    }

    /**
     * Return the least upper bound of two types. That is, the smallest type that
     * is a supertype of both types.
     */
    public JReferenceType generalizeTypes(JReferenceType type1, JReferenceType type2) {
        if (type1 == type2) {
            return type1;
        }

        if (type1 instanceof JNonNullType && type2 instanceof JNonNullType) {
            // Neither can be null.
            type1 = type1.getUnderlyingType();
            type2 = type2.getUnderlyingType();
            return generalizeTypes(type1, type2).getNonNull();
        } else if (type1 instanceof JNonNullType) {
            // type2 can be null, so the result can be null
            type1 = type1.getUnderlyingType();
        } else if (type2 instanceof JNonNullType) {
            // type1 can be null, so the result can be null
            type2 = type2.getUnderlyingType();
        }
        assert !(type1 instanceof JNonNullType);
        assert !(type2 instanceof JNonNullType);

        int classify1 = classifyType(type1);
        int classify2 = classifyType(type2);

        if (classify1 == IS_NULL) {
            return type2;
        }

        if (classify2 == IS_NULL) {
            return type1;
        }

        if (classify1 == classify2) {

            // same basic kind of type
            if (classify1 == IS_INTERFACE) {

                if (typeOracle.canTriviallyCast(type1, type2)) {
                    return type2;
                }

                if (typeOracle.canTriviallyCast(type2, type1)) {
                    return type1;
                }

                // unrelated
                return typeJavaLangObject;

            } else if (classify1 == IS_ARRAY) {

                JArrayType aType1 = (JArrayType) type1;
                JArrayType aType2 = (JArrayType) type2;
                int dims1 = aType1.getDims();
                int dims2 = aType2.getDims();

                int minDims = Math.min(dims1, dims2);
                /*
                 * At a bare minimum, any two arrays generalize to an Object array with
                 * one less dim than the lesser of the two; that is, int[][][][] and
                 * String[][][] generalize to Object[][]. If minDims is 1, then they
                 * just generalize to Object.
                 */
                JReferenceType minimalGeneralType;
                if (minDims > 1) {
                    minimalGeneralType = getTypeArray(typeJavaLangObject, minDims - 1);
                } else {
                    minimalGeneralType = typeJavaLangObject;
                }

                if (dims1 == dims2) {

                    // Try to generalize by leaf types
                    JType leafType1 = aType1.getLeafType();
                    JType leafType2 = aType2.getLeafType();

                    if (!(leafType1 instanceof JReferenceType) || !(leafType2 instanceof JReferenceType)) {
                        return minimalGeneralType;
                    }

                    /*
                     * Both are reference types; the result is the generalization of the
                     * leaf types combined with the number of dims; that is, Foo[] and
                     * Bar[] generalize to X[] where X is the generalization of Foo and
                     * Bar.
                     */
                    JReferenceType leafRefType1 = (JReferenceType) leafType1;
                    JReferenceType leafRefType2 = (JReferenceType) leafType2;
                    JReferenceType leafGeneralization = generalizeTypes(leafRefType1, leafRefType2);
                    return getTypeArray(leafGeneralization, dims1);

                } else {

                    // Conflicting number of dims

                    // int[][] and Object[] generalize to Object[]
                    JArrayType lesser = dims1 < dims2 ? aType1 : aType2;
                    if (lesser.getLeafType() == typeJavaLangObject) {
                        return lesser;
                    }

                    // Totally unrelated
                    return minimalGeneralType;
                }

            } else {

                assert (classify1 == IS_CLASS);
                JClassType class1 = (JClassType) type1;
                JClassType class2 = (JClassType) type2;

                /*
                 * see how far each type is from object; walk the one who's farther up
                 * until they're even; then walk them up together until they meet (worst
                 * case at Object)
                 */
                int distance1 = countSuperTypes(class1);
                int distance2 = countSuperTypes(class2);
                for (; distance1 > distance2; --distance1) {
                    class1 = class1.getSuperClass();
                }

                for (; distance1 < distance2; --distance2) {
                    class2 = class2.getSuperClass();
                }

                while (class1 != class2) {
                    class1 = class1.getSuperClass();
                    class2 = class2.getSuperClass();
                }

                return class1;
            }
        } else {

            // different kinds of types
            int lesser = Math.min(classify1, classify2);
            int greater = Math.max(classify1, classify2);

            JReferenceType tLesser = classify1 < classify2 ? type1 : type2;
            JReferenceType tGreater = classify1 > classify2 ? type1 : type2;

            if (lesser == IS_INTERFACE && greater == IS_CLASS) {

                // just see if the class implements the interface
                if (typeOracle.canTriviallyCast(tGreater, tLesser)) {
                    return tLesser;
                }

                // unrelated
                return typeJavaLangObject;

            } else if (greater == IS_ARRAY
                    && ((tLesser == typeJavaLangCloneable) || (tLesser == typeJavaIoSerializable))) {
                return tLesser;
            } else {

                // unrelated: the best commonality between an interface and array, or
                // between an array and a class is Object
                return typeJavaLangObject;
            }
        }
    }

    /**
     * Returns a sorted list of array types, so the returned set can be iterated
     * over without introducing nondeterminism.
     */
    public List<JArrayType> getAllArrayTypes() {
        ArrayList<JArrayType> result = new ArrayList<JArrayType>(arrayTypes.values());
        Collections.sort(result, ARRAYTYPE_COMPARATOR);
        return result;
    }

    public JsCastMap getCastMap(JReferenceType referenceType) {
        // ensure jsonCastableTypeMaps has been initialized
        // it might not have been if the CastNormalizer has not been run
        if (castMaps == null) {
            initTypeInfo(null);
        }
        return castMaps.get(referenceType);
    }

    public JField getClassLiteralField(JType type) {
        return classLiteralFields.get(isJavaScriptObject(type) ? getJavaScriptObject() : type);
    }

    public String getClassLiteralName(JType type) {
        return type.getJavahSignatureName() + "_classLit";
    }

    public List<JDeclaredType> getDeclaredTypes() {
        return allTypes;
    }

    public List<JMethod> getEntryMethods() {
        return entryMethods;
    }

    public int getFragmentCount() {
        // Initial fragment is the +1.
        return runAsyncs.size() + 1;
    }

    public JDeclaredType getFromTypeMap(String qualifiedBinaryOrSourceName) {
        String srcTypeName = qualifiedBinaryOrSourceName.replace('$', '.');

        return typeNameMap.get(srcTypeName);
    }

    public JField getIndexedField(String string) {
        JField field = indexedFields.get(string);
        if (field == null) {
            throw new InternalCompilerException("Unable to locate index field: " + string);
        }
        return field;
    }

    public JMethod getIndexedMethod(String string) {
        JMethod method = indexedMethods.get(string);
        if (method == null) {
            throw new InternalCompilerException("Unable to locate index method: " + string);
        }
        return method;
    }

    public Collection<JMethod> getIndexedMethods() {
        return Collections.unmodifiableCollection(indexedMethods.values());
    }

    public JDeclaredType getIndexedType(String string) {
        JDeclaredType type = indexedTypes.get(string);
        if (type == null) {
            throw new InternalCompilerException("Unable to locate index type: " + string);
        }
        return type;
    }

    public JClassType getJavaScriptObject() {
        return typeSpecialJavaScriptObject;
    }

    public JExpression getLiteralAbsentArrayDimension() {
        return JAbsentArrayDimension.INSTANCE;
    }

    public JBooleanLiteral getLiteralBoolean(boolean value) {
        return JBooleanLiteral.get(value);
    }

    public JCharLiteral getLiteralChar(char value) {
        return JCharLiteral.get(value);
    }

    public JDoubleLiteral getLiteralDouble(double d) {
        return JDoubleLiteral.get(d);
    }

    public JFloatLiteral getLiteralFloat(float f) {
        return JFloatLiteral.get(f);
    }

    public JIntLiteral getLiteralInt(int value) {
        return JIntLiteral.get(value);
    }

    public JLongLiteral getLiteralLong(long value) {
        return JLongLiteral.get(value);
    }

    public JNullLiteral getLiteralNull() {
        return JNullLiteral.INSTANCE;
    }

    public JStringLiteral getLiteralString(SourceInfo sourceInfo, char[] s) {
        return getLiteralString(sourceInfo, String.valueOf(s));
    }

    public JStringLiteral getLiteralString(SourceInfo sourceInfo, String s) {
        sourceInfo.addCorrelation(sourceInfo.getCorrelator().by(Literal.STRING));
        return new JStringLiteral(sourceInfo, s, typeString);
    }

    public JField getNullField() {
        return JField.NULL_FIELD;
    }

    public JMethod getNullMethod() {
        return JMethod.NULL_METHOD;
    }

    public int getQueryId(JReferenceType elementType) {
        assert (elementType == elementType.getUnderlyingType());
        Integer integer = queryIdsByType.get(elementType);
        if (integer == null) {
            return 0;
        }

        return integer.intValue();
    }

    public List<JRunAsync> getRunAsyncs() {
        return runAsyncs;
    }

    public List<Integer> getSplitPointInitialSequence() {
        return splitPointInitialSequence;
    }

    public JMethod getStaticImpl(JMethod method) {
        return instanceToStaticMap.get(method);
    }

    public JArrayType getTypeArray(JType elementType) {
        JArrayType arrayType = arrayTypes.get(elementType);
        if (arrayType == null) {
            arrayType = new JArrayType(elementType);
            arrayTypes.put(elementType, arrayType);
        }
        return arrayType;
    }

    public JArrayType getTypeArray(JType leafType, int dimensions) {
        assert dimensions > 0;
        assert (!(leafType instanceof JArrayType));
        JArrayType result = getTypeArray(leafType);
        while (dimensions > 1) {
            result = getTypeArray(result);
            --dimensions;
        }
        return result;
    }

    public JClassType getTypeClassLiteralHolder() {
        return typeSpecialClassLiteralHolder;
    }

    /**
     * Returns the JType corresponding to a JSNI type reference.
     */
    public JType getTypeFromJsniRef(String className) {
        int dim = 0;
        while (className.endsWith("[]")) {
            dim++;
            className = className.substring(0, className.length() - 2);
        }

        JType type = primitiveTypes.get(className);
        if (type == null) {
            type = getFromTypeMap(className);
        }
        // TODO(deprecation): remove support for this.
        if (type == null) {
            type = primitiveTypesDeprecated.get(className);
        }
        if (type == null || dim == 0) {
            return type;
        } else {
            return getTypeArray(type, dim);
        }
    }

    public JClassType getTypeJavaLangClass() {
        return typeClass;
    }

    public JClassType getTypeJavaLangEnum() {
        return typeJavaLangEnum;
    }

    public JClassType getTypeJavaLangObject() {
        return typeJavaLangObject;
    }

    public JClassType getTypeJavaLangString() {
        return typeString;
    }

    public JNullType getTypeNull() {
        return JNullType.INSTANCE;
    }

    public JPrimitiveType getTypePrimitiveBoolean() {
        return JPrimitiveType.BOOLEAN;
    }

    public JPrimitiveType getTypePrimitiveByte() {
        return JPrimitiveType.BYTE;
    }

    public JPrimitiveType getTypePrimitiveChar() {
        return JPrimitiveType.CHAR;
    }

    public JPrimitiveType getTypePrimitiveDouble() {
        return JPrimitiveType.DOUBLE;
    }

    public JPrimitiveType getTypePrimitiveFloat() {
        return JPrimitiveType.FLOAT;
    }

    public JPrimitiveType getTypePrimitiveInt() {
        return JPrimitiveType.INT;
    }

    public JPrimitiveType getTypePrimitiveLong() {
        return JPrimitiveType.LONG;
    }

    public JPrimitiveType getTypePrimitiveShort() {
        return JPrimitiveType.SHORT;
    }

    public List<JReferenceType> getTypesByQueryId() {
        return typesByQueryId;
    }

    public JPrimitiveType getTypeVoid() {
        return JPrimitiveType.VOID;
    }

    public void initTypeInfo(IdentityHashMap<JReferenceType, JsCastMap> instantiatedCastableTypesMap) {
        castMaps = instantiatedCastableTypesMap;
        if (castMaps == null) {
            castMaps = new IdentityHashMap<JReferenceType, JsCastMap>();
        }
    }

    public boolean isJavaLangString(JType type) {
        return type == typeString || type == typeString.getNonNull();
    }

    public boolean isJavaScriptObject(JType type) {
        if (type instanceof JReferenceType && typeSpecialJavaScriptObject != null) {
            return typeOracle.canTriviallyCast((JReferenceType) type, typeSpecialJavaScriptObject);
        }
        return false;
    }

    public boolean isStaticImpl(JMethod method) {
        return staticToInstanceMap.containsKey(method);
    }

    /**
     * Given a sequence of fragment numbers, return the latest fragment number
     * possible that does not load later than any of these. It might be one of the
     * supplied fragments, or it might be a common predecessor.
     */
    public int lastFragmentLoadingBefore(int firstFragment, int... restFragments) {
        return lastFragmentLoadingBefore(splitPointInitialSequence, runAsyncs.size(), firstFragment, restFragments);
    }

    public void putIntoTypeMap(String qualifiedBinaryName, JDeclaredType type) {
        // Make it into a source type name.
        String srcTypeName = qualifiedBinaryName.replace('$', '.');
        typeNameMap.put(srcTypeName, type);
    }

    public void putStaticImpl(JMethod method, JMethod staticImpl) {
        instanceToStaticMap.put(method, staticImpl);
        staticToInstanceMap.put(staticImpl, method);
        if (method.isTrace()) {
            staticImpl.setTrace();
        }
    }

    public void recordClassLiteralFields(Map<JType, JField> classLiteralFields) {
        this.classLiteralFields = classLiteralFields;
    }

    public void recordQueryIds(Map<JReferenceType, Integer> queryIdsByType, List<JReferenceType> typesByQueryId) {
        this.queryIdsByType = queryIdsByType;
        this.typesByQueryId = typesByQueryId;
    }

    public void removeStaticImplMapping(JMethod staticImpl) {
        JMethod instanceMethod = staticToInstanceMap.remove(staticImpl);
        if (instanceMethod != null) {
            instanceToStaticMap.remove(instanceMethod);
        }
    }

    public void setRunAsyncs(List<JRunAsync> runAsyncs) {
        this.runAsyncs = Lists.normalizeUnmodifiable(runAsyncs);
    }

    public void setSplitPointInitialSequence(List<Integer> list) {
        assert splitPointInitialSequence.isEmpty();
        splitPointInitialSequence = new ArrayList<Integer>(list);
    }

    /**
     * If <code>method</code> is a static impl method, returns the instance method
     * that <code>method</code> is the implementation of. Otherwise, returns
     * <code>null</code>.
     */
    public JMethod staticImplFor(JMethod method) {
        return staticToInstanceMap.get(method);
    }

    /**
     * Return the greatest lower bound of two types. That is, return the largest
     * type that is a subtype of both inputs.
     */
    public JReferenceType strongerType(JReferenceType type1, JReferenceType type2) {
        if (type1 == type2) {
            return type1;
        }

        if (type1 instanceof JNullType || type2 instanceof JNullType) {
            return JNullType.INSTANCE;
        }

        if (type1 instanceof JNonNullType != type2 instanceof JNonNullType) {
            // If either is non-nullable, the result should be non-nullable.
            return strongerType(type1.getNonNull(), type2.getNonNull());
        }

        if (typeOracle.canTriviallyCast(type1, type2)) {
            return type1;
        }

        if (typeOracle.canTriviallyCast(type2, type1)) {
            return type2;
        }

        // cannot determine a strong type, just return the first one (this makes two
        // "unrelated" interfaces work correctly in TypeTightener
        return type1;
    }

    public void traverse(JVisitor visitor, Context ctx) {
        if (visitor.visit(this, ctx)) {
            visitor.accept(allTypes);
        }
        visitor.endVisit(this, ctx);
    }

    private int classifyType(JReferenceType type) {
        assert !(type instanceof JNonNullType);
        if (type instanceof JNullType) {
            return IS_NULL;
        } else if (type instanceof JInterfaceType) {
            return IS_INTERFACE;
        } else if (type instanceof JArrayType) {
            return IS_ARRAY;
        } else if (type instanceof JClassType) {
            return IS_CLASS;
        }
        throw new InternalCompilerException("Unknown reference type");
    }

    private int countSuperTypes(JClassType type) {
        int count = 0;
        while ((type = type.getSuperClass()) != null) {
            ++count;
        }
        return count;
    }

    /**
     * See notes in {@link #writeObject(ObjectOutputStream)}.
     * 
     * @see #writeObject(ObjectOutputStream)
     */
    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
        allTypes = deserializeTypes(stream);
        stream.defaultReadObject();
    }

    /**
     * Serializing the Java AST is a multi-step process to avoid blowing out the
     * stack.
     * 
     * <ol>
     * <li>Write all declared types in a lightweight manner to establish object
     * identity for types</li>
     * <li>Write all fields; write all methods in a lightweight manner to
     * establish object identity for methods</li>
     * <li>Write all method bodies</li>
     * <li>Write everything else, which will mostly refer to already-serialized
     * objects.</li>
     * <li>Write the bodies of the entry methods (unlike all other methods, these
     * are not contained by any type.</li>
     * </ol>
     * 
     * The goal of this process to to avoid "running away" with the stack. Without
     * special logic here, lots of things would reference types, method body code
     * would reference both types and other methods, and really, really long
     * recursion chains would result.
     */
    private void writeObject(ObjectOutputStream stream) throws IOException {
        serializeTypes(allTypes, stream);
        stream.defaultWriteObject();
    }
}