| /* |
| * 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.MinimalRebuildCache; |
| import com.google.gwt.dev.common.InliningMode; |
| import com.google.gwt.dev.jjs.Correlation.Literal; |
| 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.impl.GwtAstBuilder; |
| import com.google.gwt.dev.jjs.impl.JjsUtils; |
| import com.google.gwt.dev.jjs.impl.TypeCategory; |
| import com.google.gwt.dev.jjs.impl.codesplitter.FragmentPartitioningResult; |
| import com.google.gwt.dev.js.CoverageInstrumentor; |
| import com.google.gwt.dev.util.StringInterner; |
| import com.google.gwt.dev.util.log.speedtracer.CompilerEventType; |
| import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger; |
| import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event; |
| import com.google.gwt.thirdparty.guava.common.base.CaseFormat; |
| import com.google.gwt.thirdparty.guava.common.base.Function; |
| import com.google.gwt.thirdparty.guava.common.base.Predicate; |
| import com.google.gwt.thirdparty.guava.common.collect.BiMap; |
| import com.google.gwt.thirdparty.guava.common.collect.Collections2; |
| import com.google.gwt.thirdparty.guava.common.collect.HashBiMap; |
| import com.google.gwt.thirdparty.guava.common.collect.ImmutableList; |
| import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap; |
| import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet; |
| import com.google.gwt.thirdparty.guava.common.collect.Iterables; |
| import com.google.gwt.thirdparty.guava.common.collect.Lists; |
| import com.google.gwt.thirdparty.guava.common.collect.Maps; |
| import com.google.gwt.thirdparty.guava.common.collect.Sets; |
| |
| import java.io.IOException; |
| import java.io.ObjectInputStream; |
| import java.io.ObjectOutputStream; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.EnumSet; |
| 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 implements ArrayTypeCreator { |
| |
| /** |
| * Encapsulates all information necessary to deal with native represented types in an |
| * generic fashion used throughout GWT. This can be extended later to deal with say, unboxed |
| * Integer if desired. |
| */ |
| public enum DispatchType { |
| // These this list can be extended by creating the appropriate fields/methods on Cast, |
| // as well as extending the TypeCategory enum and updating EqualityNormalizer. |
| // The order in which these native types appear is the inverse as the way they are |
| // checked by devirtualized method. |
| BOOLEAN(true), |
| DOUBLE(true), |
| STRING(true), |
| |
| // non-native represented type values. |
| HAS_JAVA_VIRTUAL_DISPATCH(false), JAVA_ARRAY(false), JSO(false); |
| |
| private final String castMapField; |
| private final TypeCategory typeCategory; |
| private final String className; |
| |
| DispatchType(boolean nativeType) { |
| if (nativeType) { |
| // These field are initialized to methods that are by-convention |
| // The conventions are: |
| // Cast.[boxedTypeName]CastMap for cast map fields |
| // TypedCategory.TYPE_JAVA_LANG_[BoxedTypeName] |
| String methodName = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, name()); |
| this.castMapField = "Cast." + methodName + "CastMap"; |
| this.typeCategory = TypeCategory.valueOf("TYPE_JAVA_LANG_" + name()); |
| String simpleClassName = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, name()); |
| this.className = "java.lang." + simpleClassName; |
| } else { |
| this.castMapField = null; |
| this.typeCategory = null; |
| this.className = null; |
| } |
| } |
| |
| public String getCastMapField() { |
| return castMapField; |
| } |
| |
| public TypeCategory getTypeCategory() { |
| return typeCategory; |
| } |
| |
| public String getClassName() { |
| return className; |
| } |
| } |
| |
| private static final class TreeStatistics extends JVisitor { |
| private int nodeCount = 0; |
| |
| public int getNodeCount() { |
| return nodeCount; |
| } |
| |
| @Override |
| public boolean visit(JNode x, Context ctx) { |
| nodeCount++; |
| return true; |
| } |
| } |
| |
| public static final Set<String> CODEGEN_TYPES_SET = Sets.newLinkedHashSet(Arrays.asList( |
| "com.google.gwt.lang.Array", "com.google.gwt.lang.Cast", "com.google.gwt.lang.Exceptions", |
| "com.google.gwt.lang.LongLib", "com.google.gwt.lang.Stats", "com.google.gwt.lang.Util", |
| "java.lang.Object")); |
| |
| /* |
| * 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 = Sets.newLinkedHashSet(Arrays.asList( |
| "com.google.gwt.lang.CollapsedPropertyHolder", |
| "com.google.gwt.lang.Runtime", |
| "com.google.gwt.lang.ModuleUtils")); |
| |
| public static final String JAVASCRIPTOBJECT = "com.google.gwt.core.client.JavaScriptObject"; |
| |
| public static final String CLASS_LITERAL_HOLDER = "com.google.gwt.lang.ClassLiteralHolder"; |
| |
| /** |
| * Types whose entire implementation is synthesized at compile time. |
| */ |
| public static final Set<String> SYNTHETIC_TYPE_NAMES = Sets.newHashSet(CLASS_LITERAL_HOLDER); |
| |
| private static final Comparator<JArrayType> ARRAYTYPE_COMPARATOR = |
| new Comparator<JArrayType>() { |
| @Override |
| public int compare(JArrayType o1, JArrayType o2) { |
| int comp = o1.getDims() - o2.getDims(); |
| if (comp != 0) { |
| return comp; |
| } |
| return o1.getName().compareTo(o2.getName()); |
| } |
| }; |
| |
| private static final Map<String, JPrimitiveType> primitiveTypes = Maps.newHashMap(); |
| |
| @Deprecated |
| private static final Map<String, JPrimitiveType> primitiveTypesDeprecated = Maps.newHashMap(); |
| |
| static { |
| if (CoverageInstrumentor.isCoverageEnabled()) { |
| IMMORTAL_CODEGEN_TYPES_SET.add("com.google.gwt.lang.CoverageUtil"); |
| } |
| CODEGEN_TYPES_SET.addAll(IMMORTAL_CODEGEN_TYPES_SET); |
| |
| 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 initialize fields, etc. |
| */ |
| public static JExpressionStatement createAssignmentStmt(SourceInfo info, JExpression lhs, |
| JExpression rhs) { |
| return createAssignment(info, lhs, rhs).makeStatement(); |
| } |
| |
| public static JBinaryOperation createAssignment(SourceInfo info, JExpression lhs, |
| JExpression rhs) { |
| return new JBinaryOperation(info, lhs.getType(), JBinaryOperator.ASG, lhs, rhs); |
| } |
| |
| 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.addLocal(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 getFullName(JMethod method) { |
| return method.getEnclosingType().getName() + "." + method.getJsniSignature(false, true); |
| } |
| |
| public static boolean isClinit(JMethod method) { |
| JDeclaredType enclosingType = method.getEnclosingType(); |
| |
| boolean isClinit = enclosingType != JClassType.NULL_CLASS && |
| method == enclosingType.getClinitMethod(); |
| assert !isClinit || method.getName().equals(GwtAstBuilder.CLINIT_METHOD_NAME); |
| return isClinit; |
| } |
| |
| public static boolean isInit(JMethod method) { |
| JDeclaredType enclosingType = method.getEnclosingType(); |
| |
| if (method.isStatic()) { |
| // Hack, check the name. |
| return method.getName().equals(GwtAstBuilder.STATIC_INIT_METHOD_NAME); |
| } |
| |
| boolean isInit = enclosingType != null && method == enclosingType.getInitMethod(); |
| assert !isInit || method.getName().equals(GwtAstBuilder.INIT_NAME_METHOD_NAME); |
| return isInit; |
| } |
| |
| 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); |
| } |
| } |
| |
| public final List<JClassType> codeGenTypes = Lists.newArrayList(); |
| |
| public final List<JClassType> immortalCodeGenTypes = Lists.newArrayList(); |
| |
| public final JTypeOracle typeOracle; |
| |
| /** |
| * Special serialization treatment. |
| */ |
| // TODO(stalcup): make this a set, or take special care to make updates unique when lazily loading |
| // in types. At the moment duplicates are accumulating. |
| private transient List<JDeclaredType> allTypes = Lists.newArrayList(); |
| |
| private final Map<JType, JArrayType> arrayTypes = Maps.newHashMap(); |
| |
| private Map<JReferenceType, JCastMap> castMaps; |
| |
| private BiMap<JType, JField> classLiteralFieldsByType; |
| |
| private final List<JMethod> entryMethods = Lists.newArrayList(); |
| |
| private final Map<String, JField> indexedFields = Maps.newHashMap(); |
| |
| private final Map<String, JMethod> indexedMethods = Maps.newHashMap(); |
| |
| /** |
| * An index of types, from type name to type instance. |
| */ |
| private final Map<String, JDeclaredType> indexedTypes = Maps.newHashMap(); |
| |
| /** |
| * The set of names of types (beyond the basic INDEX_TYPES_SET) whose instance should be indexed |
| * when seen. |
| */ |
| private final Set<String> typeNamesToIndex = buildInitialTypeNamesToIndex(); |
| |
| private final Map<JMethod, JMethod> instanceToStaticMap = Maps.newIdentityHashMap(); |
| |
| // wrap up .add here, and filter out forced source |
| private Set<String> referenceOnlyTypeNames = Sets.newHashSet(); |
| |
| /** |
| * Filled in by ReplaceRunAsync, once the numbers are known. |
| */ |
| private List<JRunAsync> runAsyncs = Lists.newArrayList(); |
| |
| private LinkedHashSet<JRunAsync> initialAsyncSequence = Sets.newLinkedHashSet(); |
| |
| private List<Integer> initialFragmentIdSequence = Lists.newArrayList(); |
| |
| private final Map<JMethod, JMethod> staticToInstanceMap = Maps.newIdentityHashMap(); |
| |
| private final Map<String, JDeclaredType> typeNameMap = Maps.newHashMap(); |
| |
| private Map<JField, JType> typesByClassLiteralField; |
| |
| private JClassType typeClass; |
| private JClassType typeJavaLangObject; |
| private JArrayType typeJavaLangObjectArray; |
| private JClassType typeSpecialClassLiteralHolder; |
| private JClassType typeSpecialJavaScriptObject; |
| |
| private JClassType typeString; |
| |
| private FragmentPartitioningResult fragmentPartitioningResult; |
| |
| private Map<JClassType, DispatchType> dispatchTypeByNativeType; |
| |
| /** |
| * Add a pinned method. |
| */ |
| public void addPinnedMethod(JMethod method) { |
| method.setInliningMode(InliningMode.DO_NOT_INLINE); |
| method.disallowDevirtualization(); |
| } |
| |
| public JProgram(MinimalRebuildCache minimalRebuildCache) { |
| super(SourceOrigin.UNKNOWN); |
| typeOracle = new JTypeOracle(this, minimalRebuildCache); |
| } |
| |
| public void addEntryMethod(JMethod entryPoint) { |
| assert !entryMethods.contains(entryPoint); |
| entryMethods.add(entryPoint); |
| } |
| |
| /** |
| * Adds the given type name to the set of type names (beyond the basic INDEX_TYPES_SET) whose |
| * instance should be indexed when seen. |
| */ |
| public void addIndexedTypeName(String typeName) { |
| typeNamesToIndex.add(typeName); |
| } |
| |
| public void addReferenceOnlyType(JDeclaredType type) { |
| referenceOnlyTypeNames.add(type.getName()); |
| } |
| |
| 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)) { |
| // Immortal types by definition won't run clinits. |
| type.setClinitTarget(null); |
| immortalCodeGenTypes.add((JClassType) type); |
| } |
| |
| if (!typeNamesToIndex.contains(name)) { |
| return; |
| } |
| |
| indexedTypes.put(type.getShortName(), type); |
| for (JMethod method : type.getMethods()) { |
| if (!method.isPrivate()) { |
| indexedMethods.put(JjsUtils.getIndexedName(method), method); |
| } |
| } |
| for (JField field : type.getFields()) { |
| indexedFields.put(JjsUtils.getIndexedName(field), field); |
| } |
| switch (name) { |
| case "java.lang.Object": |
| typeJavaLangObject = (JClassType) type; |
| typeJavaLangObjectArray = getOrCreateArrayType(type, 1); |
| break; |
| case "java.lang.String": |
| typeString = (JClassType) type; |
| break; |
| case "java.lang.Class": |
| typeClass = (JClassType) type; |
| break; |
| case JAVASCRIPTOBJECT: |
| typeSpecialJavaScriptObject = (JClassType) type; |
| break; |
| case CLASS_LITERAL_HOLDER: |
| typeSpecialClassLiteralHolder = (JClassType) type; |
| break; |
| } |
| } |
| |
| public static boolean isRepresentedAsNative(final String className) { |
| return Iterables.any(Arrays.asList(DispatchType.values()), new Predicate<DispatchType>() { |
| @Override |
| public boolean apply(DispatchType dispatchType) { |
| return className.equals(dispatchType.getClassName()); |
| } |
| }); |
| } |
| |
| public boolean isRepresentedAsNativeJsPrimitive(JType type) { |
| return getRepresentedAsNativeTypes().contains(type); |
| } |
| |
| public Set<JClassType> getRepresentedAsNativeTypes() { |
| return getRepresentedAsNativeTypesDispatchMap().keySet(); |
| } |
| |
| public Map<JClassType, DispatchType> getRepresentedAsNativeTypesDispatchMap() { |
| if (dispatchTypeByNativeType == null) { |
| ImmutableMap.Builder<JClassType, DispatchType> builder = |
| new ImmutableMap.Builder<JClassType, DispatchType>(); |
| for (DispatchType dispatchType : DispatchType.values()) { |
| if (dispatchType.getClassName() == null) { |
| continue; |
| } |
| JClassType classType = (JClassType) getFromTypeMap(dispatchType.getClassName()); |
| assert classType != null : "Class " + dispatchType.getClassName() + " has not been loaded"; |
| builder.put(classType, dispatchType); |
| } |
| dispatchTypeByNativeType = builder.build(); |
| } |
| return dispatchTypeByNativeType; |
| } |
| |
| public EnumSet<DispatchType> getDispatchType(JReferenceType type) { |
| if (!typeOracle.isInstantiatedType(type)) { |
| return EnumSet.noneOf(DispatchType.class); |
| } |
| |
| // Object methods can be dispatched to all four possible classes. |
| if (type == getTypeJavaLangObject()) { |
| return EnumSet.allOf(DispatchType.class); |
| } |
| |
| if (type.isArrayType()) { |
| // A variable of type Object[] could contain an instance of native JsType[], the latter |
| // is treated as a JSO for devirtualization purposes. |
| return EnumSet.of(DispatchType.JSO, DispatchType.JAVA_ARRAY); |
| } |
| EnumSet<DispatchType> dispatchSet = EnumSet.noneOf(DispatchType.class); |
| DispatchType dispatchType = getRepresentedAsNativeTypesDispatchMap().get(type); |
| if (dispatchType != null) { |
| dispatchSet = EnumSet.of(dispatchType); |
| } else if (typeOracle.isDualJsoInterface(type) || type.isJsNative()) { |
| // If it is an interface implemented both by JSOs and regular Java Objects; native JsTypes |
| // are considered JSOs for object method devirtualization. |
| dispatchSet = EnumSet.of(DispatchType.HAS_JAVA_VIRTUAL_DISPATCH, DispatchType.JSO); |
| } else if (typeOracle.isSingleJsoImpl(type) || type.isJsoType()) { |
| // If it is either an interface implemented by JSOs or JavaScriptObject or one of its |
| // subclasses. |
| dispatchSet = EnumSet.of(DispatchType.JSO); |
| } |
| |
| for (JDeclaredType potentialNativeDispatchType : getRepresentedAsNativeTypes()) { |
| if (potentialNativeDispatchType == type) { |
| continue; |
| } |
| |
| if (typeOracle.isInstantiatedType(potentialNativeDispatchType) |
| && typeOracle.isSuperClassOrInterface(potentialNativeDispatchType, type)) { |
| dispatchSet.add(getRepresentedAsNativeTypesDispatchMap().get(potentialNativeDispatchType)); |
| dispatchSet.add(DispatchType.HAS_JAVA_VIRTUAL_DISPATCH); |
| } |
| } |
| return dispatchSet; |
| } |
| |
| /** |
| * Return the greatest lower bound of two types. That is, return the largest |
| * type that is a subtype of both inputs. If none exists return {@code thisType}. |
| */ |
| public JReferenceType strengthenType(JReferenceType thisType, JReferenceType thatType) { |
| if (thisType == thatType) { |
| return thisType; |
| } |
| |
| if (thisType.isNullType() || thatType.isNullType()) { |
| return JReferenceType.NULL_TYPE; |
| } |
| |
| if (!thisType.canBeNull() || !thatType.canBeNull()) { |
| JReferenceType thisTypeNonNull = thisType.strengthenToNonNull(); |
| JReferenceType thatTypeNonNull = thatType.strengthenToNonNull(); |
| // .strengthenToNonNull does not guarantee that the resulting type is non null (e.g. JSOs). |
| |
| // If either is non-nullable, the result should be non-nullable, unless it is a type that |
| // can not be made non-nullable, like a JSO. |
| if (thisType != thisTypeNonNull || thatType != thatTypeNonNull) { |
| return strengthenType(thisTypeNonNull, thatTypeNonNull); |
| } |
| } |
| |
| if (typeOracle.castSucceedsTrivially(thisType, thatType)) { |
| return thisType; |
| } |
| |
| if (typeOracle.castSucceedsTrivially(thatType, thisType)) { |
| return thatType; |
| } |
| |
| // This types are incompatible; ideally this code should not be reached, but there are two |
| // situations where this happens: |
| // 1 - unrelated interfaces; |
| // 2 - unsafe code. |
| // The original type is preserved in this case. |
| return thisType; |
| } |
| |
| /** |
| * Return a minimal upper bound of a set of types. That is, a type |
| * that is a supertype of all the input types and is as close as possible to the |
| * input types. |
| * <p> |
| * NOTE: Ideally we would like to return the least upper bound but it does not exit as |
| * the Java type hierarchy is not really a lattice. |
| * <p> |
| * Hence, this function depends on the collection order. E.g. |
| * <p> |
| * {@code |
| * I O |
| * |\ / \ |
| * | A B |
| * \ / |
| * \ / |
| * C |
| * } |
| * <p> |
| * where I is an interface an {O,A,B,C} are classes. |
| * <p> |
| * generalizeTypes({A,C}) could either be I or O. |
| * <p> |
| * In particular generalizeTypes({I,A,C}) = I and generalizeTypes({A,C,I}) = O. |
| * |
| */ |
| public JReferenceType generalizeTypes(Iterable<JReferenceType> types) { |
| Iterator<JReferenceType> it = types.iterator(); |
| if (!it.hasNext()) { |
| return JReferenceType.NULL_TYPE; |
| } |
| JReferenceType curType = it.next(); |
| while (it.hasNext()) { |
| curType = generalizeTypes(curType, it.next()); |
| if (curType == typeJavaLangObject) { |
| break; |
| } |
| } |
| return curType; |
| } |
| |
| /** |
| * Return the least upper bound of two types. That is, the "smallest" type that |
| * is a supertype of both types. In this lattice there the smallest element might no exist, there |
| * might be multiple minimal elements neither of which is smaller than the others. E.g. |
| * <p> |
| * {@code |
| * I J |
| * | \ /| |
| * | \ / | |
| * | x | |
| * | / \ | |
| * | / \ | |
| * A B |
| * } |
| * <p> |
| * where I and J are interfaces, A and B are classes and both A and B implement I and J. In this |
| * case both I and J are generalizing the types A and B. |
| */ |
| private JReferenceType generalizeTypes(JReferenceType thisType, JReferenceType thatType) { |
| |
| if (!thisType.canBeNull() && !thatType.canBeNull()) { |
| // Nullability is an orthogonal property, so remove non_nullability and perform the |
| // generalization on the nullable types, and if both were NOT nullable then strengthen the |
| // result to NOT nullable. |
| // |
| // not_nullable(A) v not_nullable(B) = not_nullable(A v B) |
| JReferenceType nulllableGeneralizer = |
| generalizeTypes(thisType.weakenToNullable(), thatType.weakenToNullable()); |
| return nulllableGeneralizer.strengthenToNonNull(); |
| } |
| thisType = thisType.weakenToNullable(); |
| thatType = thatType.weakenToNullable(); |
| |
| // From here on nullability does not need to be considered. |
| |
| // Generalization for exact types is as follows. |
| // exact(A) v null = exact(A) |
| // A v null = A |
| if (thatType.isNullType()) { |
| return thisType; |
| } |
| |
| // null v exact(A) = exact(A) |
| // null v A = A |
| if (thisType.isNullType()) { |
| return thatType; |
| } |
| |
| // exact(A) v exact(A) = exact(A) |
| // A v A = A |
| if (thisType == thatType) { |
| return thisType; |
| } |
| |
| // exact(A) v exact(B) = A v B |
| // A v exact(B) = A v B |
| // exact(A) v B = A v B |
| // A v B = A v B |
| return generalizeUnderlyingTypes(thisType.getUnderlyingType(), thatType.getUnderlyingType()); |
| } |
| |
| private JReferenceType generalizeUnderlyingTypes( |
| JReferenceType thisType, JReferenceType thatType) { |
| |
| // We should not have any analysis properties from this point forward. |
| assert thisType == thisType.getUnderlyingType() && thatType == thatType.getUnderlyingType(); |
| |
| if (thisType == thatType) { |
| return thisType; |
| } |
| |
| if (thisType instanceof JInterfaceType && thatType instanceof JInterfaceType) { |
| return generalizeInterfaces((JInterfaceType) thisType, (JInterfaceType) thatType); |
| } |
| |
| if (thisType instanceof JArrayType && thatType instanceof JArrayType) { |
| return generalizeArrayTypes((JArrayType) thisType, (JArrayType) thatType); |
| } |
| |
| if (thisType instanceof JClassType && thatType instanceof JClassType) { |
| return generalizeClasses((JClassType) thisType, (JClassType) thatType); |
| } |
| |
| JInterfaceType interfaceType = thisType instanceof JInterfaceType ? (JInterfaceType) thisType : |
| (thatType instanceof JInterfaceType ? (JInterfaceType) thatType : null); |
| |
| JReferenceType nonInterfaceType = interfaceType == thisType ? thatType : thisType; |
| |
| // See if the class or the array is castable to the interface type. |
| if (interfaceType != null && |
| typeOracle.castSucceedsTrivially(nonInterfaceType, interfaceType)) { |
| return interfaceType; |
| } |
| |
| // unrelated: the best commonality is Object |
| return typeJavaLangObject; |
| } |
| |
| private JReferenceType generalizeArrayTypes(JArrayType thisArrayType, JArrayType thatArrayType) { |
| assert thisArrayType != thatArrayType; |
| |
| int thisDims = thisArrayType.getDims(); |
| int thatDims = thatArrayType.getDims(); |
| |
| int minDims = Math.min(thisDims, thatDims); |
| /* |
| * 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 = (minDims == 1) ? typeJavaLangObject : |
| getOrCreateArrayType(typeJavaLangObject, minDims - 1); |
| |
| if (thisDims == thatDims) { |
| |
| // Try to generalize by leaf types |
| JType thisLeafType = thisArrayType.getLeafType(); |
| JType thatLeafType = thatArrayType.getLeafType(); |
| |
| if (!(thisLeafType instanceof JReferenceType) || !(thatLeafType 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. |
| * |
| * Never generalize arrays to arrays of {@link JAnalysisDecoratedType}. One of the reasons is |
| * that array initialization is not accounted for in {@link TypeTightener}. |
| */ |
| JReferenceType leafGeneralization = generalizeTypes( |
| (JReferenceType) thisLeafType, (JReferenceType) thatLeafType).getUnderlyingType(); |
| return getOrCreateArrayType(leafGeneralization, thisDims); |
| } |
| |
| // Different number of dims |
| if (typeOracle.castSucceedsTrivially(thatArrayType, thisArrayType)) { |
| return thisArrayType; |
| } |
| |
| if (typeOracle.castSucceedsTrivially(thisArrayType, thatArrayType)) { |
| return thatArrayType; |
| } |
| |
| // Totally unrelated |
| return minimalGeneralType; |
| } |
| |
| private JReferenceType generalizeInterfaces(JInterfaceType thisInterface, |
| JInterfaceType thatInterface) { |
| if (typeOracle.castSucceedsTrivially(thisInterface, thatInterface)) { |
| return thatInterface; |
| } |
| |
| if (typeOracle.castSucceedsTrivially(thatInterface, thisInterface)) { |
| return thisInterface; |
| } |
| |
| // unrelated |
| return typeJavaLangObject; |
| } |
| |
| private JReferenceType generalizeClasses(JClassType thisClass, JClassType thatClass) { |
| /* |
| * 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(thisClass); |
| int distance2 = countSuperTypes(thatClass); |
| for (; distance1 > distance2; --distance1) { |
| thisClass = thisClass.getSuperClass(); |
| } |
| |
| for (; distance1 < distance2; --distance2) { |
| thatClass = thatClass.getSuperClass(); |
| } |
| |
| while (thisClass != thatClass) { |
| thisClass = thisClass.getSuperClass(); |
| thatClass = thatClass.getSuperClass(); |
| } |
| |
| return thisClass; |
| } |
| |
| /** |
| * Returns a sorted list of array types, so the returned set can be iterated |
| * over without introducing nondeterminism. |
| */ |
| public List<JArrayType> getAllArrayTypes() { |
| List<JArrayType> result = Lists.newArrayList(arrayTypes.values()); |
| Collections.sort(result, ARRAYTYPE_COMPARATOR); |
| return result; |
| } |
| |
| /** |
| * Returns an expression that evaluates to an array class literal at runtime. |
| * <p> |
| * Note: This version can only be called after {@link |
| * com.google.gwt.dev.jjs.impl.ImplementClassLiteralsAsFields} has been run. |
| */ |
| public JExpression createArrayClassLiteralExpression(SourceInfo sourceInfo, |
| JClassLiteral leafTypeClassLiteral, int dimensions) { |
| JField leafTypeClassLiteralField = leafTypeClassLiteral.getField(); |
| assert leafTypeClassLiteralField != null : "Array leaf type must have a class literal field; " |
| + "either ImplementClassLiteralsAsField has not run yet or or there is an error computing" |
| + "live class literals."; |
| |
| return new JMethodCall(sourceInfo, null, getIndexedMethod( |
| RuntimeConstants.ARRAY_GET_CLASS_LITERAL_FOR_ARRAY), |
| new JFieldRef(sourceInfo, null, leafTypeClassLiteralField, |
| leafTypeClassLiteralField.getEnclosingType()), getLiteralInt(dimensions)); |
| } |
| |
| public Map<JReferenceType, JCastMap> getCastMap() { |
| return Collections.unmodifiableMap(castMaps); |
| } |
| |
| public JCastMap getCastMap(JReferenceType referenceType) { |
| // ensure jsonCastableTypeMaps has been initialized |
| // it might not have been if the ImplementCastsAndTypeChecks has not been run |
| if (castMaps == null) { |
| initTypeInfo(null); |
| } |
| return castMaps.get(referenceType); |
| } |
| |
| public JField getClassLiteralField(JType type) { |
| return classLiteralFieldsByType.get( |
| type.isJsoType() ? getJavaScriptObject() : type); |
| } |
| |
| 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 FragmentPartitioningResult getFragmentPartitioningResult() { |
| return fragmentPartitioningResult; |
| } |
| |
| // TODO(stalcup): this is a blatant bug. there's no unambiguous way to convert from binary name to |
| // source name. JProgram needs to index types both ways. |
| 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 Set<JField> getIndexedFields() { |
| return ImmutableSet.copyOf(indexedFields.values()); |
| } |
| |
| 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 Set<JMethod> getIndexedMethods() { |
| return ImmutableSet.copyOf(indexedMethods.values()); |
| } |
| |
| public JMethod getIndexedMethodOrNull(String string) { |
| return indexedMethods.get(string); |
| } |
| |
| 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 Collection<JDeclaredType> getIndexedTypes() { |
| return Collections.unmodifiableCollection(indexedTypes.values()); |
| } |
| |
| public LinkedHashSet<JRunAsync> getInitialAsyncSequence() { |
| return initialAsyncSequence; |
| } |
| |
| public List<Integer> getInitialFragmentIdSequence() { |
| return initialFragmentIdSequence; |
| } |
| |
| public JClassType getJavaScriptObject() { |
| return typeSpecialJavaScriptObject; |
| } |
| |
| public JLiteral getLiteral(Object value) { |
| return getLiteral(SourceOrigin.UNKNOWN, value); |
| } |
| |
| public JLiteral getLiteral(SourceInfo info, Object value) { |
| if (value == null) { |
| return getLiteralNull(); |
| } |
| if (value instanceof String) { |
| return getStringLiteral(info, (String) value); |
| } |
| if (value instanceof Integer) { |
| return getLiteralInt((Integer) value); |
| } |
| if (value instanceof Long) { |
| return getLiteralLong((Long) value); |
| } |
| if (value instanceof Character) { |
| return getLiteralChar((Character) value); |
| } |
| if (value instanceof Boolean) { |
| return getLiteralBoolean((Boolean) value); |
| } |
| if (value instanceof Double) { |
| return getLiteralDouble((Double) value); |
| } |
| if (value instanceof Float) { |
| return getLiteralFloat((Float) value); |
| } |
| throw new IllegalArgumentException("Unknown literal type for " + value); |
| } |
| |
| 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(double 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 getStringLiteral(SourceInfo sourceInfo, String s) { |
| sourceInfo.addCorrelation(sourceInfo.getCorrelator().by(Literal.STRING)); |
| return new JStringLiteral(sourceInfo, StringInterner.get().intern(s), typeString); |
| } |
| |
| public List<JDeclaredType> getModuleDeclaredTypes() { |
| List<JDeclaredType> moduleDeclaredTypes = Lists.newArrayList(); |
| for (JDeclaredType type : allTypes) { |
| if (isReferenceOnly(type)) { |
| continue; |
| } |
| moduleDeclaredTypes.add(type); |
| } |
| return moduleDeclaredTypes; |
| } |
| |
| public int getNodeCount() { |
| Event countEvent = SpeedTracerLogger.start(CompilerEventType.OPTIMIZE, "phase", "countNodes"); |
| TreeStatistics treeStats = new TreeStatistics(); |
| treeStats.accept(this); |
| int numNodes = treeStats.getNodeCount(); |
| countEvent.end(); |
| return numNodes; |
| } |
| |
| public JField getNullField() { |
| return JField.NULL_FIELD; |
| } |
| |
| public JMethod getNullMethod() { |
| return JMethod.NULL_METHOD; |
| } |
| |
| public List<JRunAsync> getRunAsyncs() { |
| return runAsyncs; |
| } |
| |
| public int getCommonAncestorFragmentId(int thisFragmentId, int thatFragmentId) { |
| return fragmentPartitioningResult.getCommonAncestorFragmentId(thisFragmentId, thatFragmentId); |
| } |
| |
| public Collection<JType> getSubclasses(JType type) { |
| return Collections2.transform(typeOracle.getSubClassNames(type.getName()), |
| new Function<String, JType>() { |
| @Override |
| public JType apply(String typeName) { |
| return getFromTypeMap(typeName); |
| } |
| } |
| ); |
| } |
| |
| public JMethod getStaticImpl(JMethod method) { |
| JMethod staticImpl = instanceToStaticMap.get(method); |
| assert staticImpl == null || staticImpl.getEnclosingType().getMethods().contains(staticImpl); |
| return staticImpl; |
| } |
| |
| public JArrayType getTypeArray(JType elementType) { |
| JArrayType arrayType = arrayTypes.get(elementType); |
| if (arrayType == null) { |
| arrayType = new JArrayType(elementType); |
| arrayTypes.put(elementType, arrayType); |
| } |
| return arrayType; |
| } |
| |
| // TODO(dankurka): Why does JProgram synthezise array types on the fly |
| // Look into refactoring JProgram to get rid of this responsibility |
| @Override |
| public JArrayType getOrCreateArrayType(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 JType getTypeByClassLiteralField(JField field) { |
| return typesByClassLiteralField.get(field); |
| } |
| |
| 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 getOrCreateArrayType(type, dim); |
| } |
| } |
| |
| public JClassType getTypeJavaLangClass() { |
| return typeClass; |
| } |
| |
| public JClassType getTypeJavaLangObject() { |
| return typeJavaLangObject; |
| } |
| |
| public JArrayType getTypeJavaLangObjectArray() { |
| return typeJavaLangObjectArray; |
| } |
| |
| public JClassType getTypeJavaLangString() { |
| return typeString; |
| } |
| |
| public Set<String> getTypeNamesToIndex() { |
| return typeNamesToIndex; |
| } |
| |
| 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 JPrimitiveType getTypeVoid() { |
| return JPrimitiveType.VOID; |
| } |
| |
| public void initTypeInfo(Map<JReferenceType, JCastMap> castMapForType) { |
| castMaps = castMapForType; |
| if (castMaps == null) { |
| castMaps = Maps.newIdentityHashMap(); |
| } |
| } |
| |
| public boolean isUntypedArrayType(JType type) { |
| if (!type.isArrayType()) { |
| return false; |
| } |
| |
| JArrayType arrayType = (JArrayType) type; |
| return arrayType.getLeafType().isJsNative(); |
| } |
| |
| public boolean isJavaLangString(JType type) { |
| assert type != null; |
| return type.getUnderlyingType() == typeString; |
| } |
| |
| public boolean isJavaLangObject(JType type) { |
| assert type != null; |
| return type.getUnderlyingType() == typeJavaLangObject; |
| } |
| |
| public boolean isReferenceOnly(JDeclaredType type) { |
| if (type != null) { |
| return referenceOnlyTypeNames.contains(type.getName()); |
| } |
| return false; |
| } |
| |
| public boolean isStaticImpl(JMethod method) { |
| return staticToInstanceMap.containsKey(method); |
| } |
| |
| /** |
| * If the type is a JSO or an array of JSOs it returns cggcc.JavaScriptObject or an array of |
| * cggcc.JavaScriptObject respectively; otherwise returns {@code type}. |
| */ |
| public JType normalizeJsoType(JType type) { |
| type = type.getUnderlyingType(); |
| |
| if (type instanceof JArrayType) { |
| return getOrCreateArrayType(normalizeJsoType(((JArrayType) type).getLeafType()), |
| ((JArrayType) type).getDims()); |
| } |
| |
| if (type.isJsoType()) { |
| return getJavaScriptObject(); |
| } |
| return type; |
| } |
| |
| 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); |
| } |
| |
| public void recordClassLiteralFields(Map<JType, JField> classLiteralFields) { |
| this.classLiteralFieldsByType = HashBiMap.create(classLiteralFields); |
| this.typesByClassLiteralField = classLiteralFieldsByType.inverse(); |
| } |
| |
| public void removeStaticImplMapping(JMethod staticImpl) { |
| JMethod instanceMethod = staticToInstanceMap.remove(staticImpl); |
| if (instanceMethod != null) { |
| instanceToStaticMap.remove(instanceMethod); |
| } |
| } |
| |
| public void removeReferenceOnlyType(JDeclaredType type) { |
| referenceOnlyTypeNames.remove(type.getName()); |
| } |
| |
| public void setFragmentPartitioningResult(FragmentPartitioningResult result) { |
| fragmentPartitioningResult = result; |
| } |
| |
| public void setInitialFragmentIdSequence(List<Integer> initialFragmentIdSequence) { |
| this.initialFragmentIdSequence = initialFragmentIdSequence; |
| } |
| |
| public void setRunAsyncs(List<JRunAsync> runAsyncs) { |
| this.runAsyncs = ImmutableList.copyOf(runAsyncs); |
| } |
| |
| public void setInitialAsyncSequence(LinkedHashSet<JRunAsync> initialAsyncSequence) { |
| assert this.initialAsyncSequence.isEmpty(); |
| initialFragmentIdSequence = Lists.newArrayList(); |
| // TODO(rluble): hack for now the initial fragments correspond to the initial runAsyncIds. |
| initialFragmentIdSequence.addAll( |
| Collections2.transform(initialAsyncSequence, |
| new Function<JRunAsync, Integer>() { |
| @Override |
| public Integer apply(JRunAsync runAsync) { |
| return runAsync.getRunAsyncId(); |
| } |
| })); |
| this.initialAsyncSequence = initialAsyncSequence; |
| } |
| |
| /** |
| * If {@code method} is a static impl method, returns the instance method |
| * that {@code method} is the implementation of. Otherwise, returns{@code null}. |
| */ |
| public JMethod instanceMethodForStaticImpl(JMethod method) { |
| return staticToInstanceMap.get(method); |
| } |
| |
| @Override |
| public void traverse(JVisitor visitor, Context ctx) { |
| if (visitor.visit(this, ctx)) { |
| visitModuleTypes(visitor); |
| } |
| visitor.endVisit(this, ctx); |
| } |
| |
| /** |
| * Builds the starter set of type names that should be indexed when seen during addType(). This |
| * set is a thread safe instance variable and external logic is free to modify it as further |
| * requirements are discovered. |
| */ |
| private static Set<String> buildInitialTypeNamesToIndex() { |
| Set<String> typeNamesToIndex = Sets.newHashSet(); |
| typeNamesToIndex.addAll(ImmutableList.of("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", |
| JAVASCRIPTOBJECT, CLASS_LITERAL_HOLDER, "com.google.gwt.core.client.RunAsyncCallback", |
| "com.google.gwt.core.client.impl.AsyncFragmentLoader", |
| "com.google.gwt.core.client.impl.Impl", |
| "com.google.gwt.core.client.prefetch.RunAsyncCode")); |
| typeNamesToIndex.addAll(CODEGEN_TYPES_SET); |
| return typeNamesToIndex; |
| } |
| |
| public void visitAllTypes(JVisitor visitor) { |
| visitor.accept(allTypes); |
| } |
| |
| public void visitModuleTypes(JVisitor visitor) { |
| for (JDeclaredType type : allTypes) { |
| if (isReferenceOnly(type)) { |
| continue; |
| } |
| visitor.accept(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(); |
| } |
| } |