/* * 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.javac; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.typeinfo.JPrimitiveType; import com.google.gwt.core.ext.typeinfo.JType; import com.google.gwt.dev.asm.ClassReader; import com.google.gwt.dev.asm.ClassVisitor; import com.google.gwt.dev.asm.Opcodes; import com.google.gwt.dev.asm.Type; import com.google.gwt.dev.asm.signature.SignatureReader; import com.google.gwt.dev.asm.util.TraceClassVisitor; import com.google.gwt.dev.javac.asm.CollectAnnotationData; import com.google.gwt.dev.javac.asm.CollectAnnotationData.AnnotationData; import com.google.gwt.dev.javac.asm.CollectClassData; import com.google.gwt.dev.javac.asm.CollectClassData.AnnotationEnum; import com.google.gwt.dev.javac.asm.CollectFieldData; import com.google.gwt.dev.javac.asm.CollectMethodData; import com.google.gwt.dev.javac.asm.CollectTypeParams; import com.google.gwt.dev.javac.asm.ResolveClassSignature; import com.google.gwt.dev.javac.asm.ResolveMethodSignature; import com.google.gwt.dev.javac.asm.ResolveTypeSignature; import com.google.gwt.dev.javac.typemodel.JAbstractMethod; import com.google.gwt.dev.javac.typemodel.JArrayType; import com.google.gwt.dev.javac.typemodel.JClassType; import com.google.gwt.dev.javac.typemodel.JField; import com.google.gwt.dev.javac.typemodel.JGenericType; import com.google.gwt.dev.javac.typemodel.JMethod; import com.google.gwt.dev.javac.typemodel.JPackage; import com.google.gwt.dev.javac.typemodel.JParameterizedType; import com.google.gwt.dev.javac.typemodel.JRawType; import com.google.gwt.dev.javac.typemodel.JRealClassType; import com.google.gwt.dev.javac.typemodel.JTypeParameter; import com.google.gwt.dev.javac.typemodel.JWildcardType; import com.google.gwt.dev.javac.typemodel.TypeOracle; import com.google.gwt.dev.javac.typemodel.TypeOracleBuilder; import com.google.gwt.dev.util.Name; import com.google.gwt.dev.util.Name.InternalName; 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 java.io.PrintWriter; import java.lang.annotation.Annotation; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * Builds or rebuilds a {@link com.google.gwt.core.ext.typeinfo.TypeOracle} from * a set of compilation units. */ public class TypeOracleMediator extends TypeOracleBuilder { /** * A container to hold all the information we need to add one type to the * TypeOracle. */ public static class TypeData { /** * Bytecode from compiled Java source. */ private final byte[] byteCode; /** * Prepared information about this class. */ private CollectClassData classData; /** * See {@link Type#getInternalName()}. */ private final String internalName; /** * A timestamp as returned from {@link System#currentTimeMillis()} * * TODO(zundel): currently unused, add to JType later. */ private final long lastModifiedTime; /** * Package name. */ private final String packageName; /** * URL to fetch the source file from a class loader. If a method arg name is * requested, we may need to go back to this file and compile it. * * TODO(zundel): currently unused, add to JRealClassType later. */ private final String sourceFileResourceName; /** * See {@link JType#getQualifiedSourceName()}. */ private final String sourceName; protected TypeData(String packageName, String sourceName, String internalName, String sourceFileResourceName, byte[] classBytes, long lastModifiedTime) { this.packageName = packageName; this.sourceName = sourceName; this.internalName = internalName; this.sourceFileResourceName = sourceFileResourceName; this.byteCode = classBytes; this.lastModifiedTime = lastModifiedTime; } /** * Collects data about a class which only needs the bytecode and no TypeOracle * data structures. This is used to make the initial shallow identity pass for * creating JRealClassType/JGenericType objects. */ synchronized CollectClassData getCollectClassData() { if (classData == null) { ClassReader reader = new ClassReader(byteCode); classData = new CollectClassData(); ClassVisitor cv = classData; if (TRACE_CLASSES) { cv = new TraceClassVisitor(cv, new PrintWriter(System.out)); } reader.accept(cv, 0); } return classData; } } /** * This context keeps common data so we don't have to pass it around between * methods for one pass of * {@link TypeOracleMediator#addNewTypes(TreeLogger, Collection, MethodArgNamesLookup)} * . */ private class TypeOracleBuildContext { private final MethodArgNamesLookup allMethodArgs; // map of internal names to class visitors. private final Map<String, CollectClassData> classMap = new HashMap<String, CollectClassData>(); // map of JRealType instances to lookup class visitors. private final HashMap<JRealClassType, CollectClassData> classMapType = new HashMap<JRealClassType, CollectClassData>(); private final Resolver resolver = new TypeOracleMediatorResolver(this); private TypeOracleBuildContext(MethodArgNamesLookup allMethodArgs) { this.allMethodArgs = allMethodArgs; } }; private class TypeOracleMediatorResolver implements Resolver { private final TypeOracleBuildContext context; public TypeOracleMediatorResolver(TypeOracleBuildContext context) { this.context = context; } public void addImplementedInterface(JRealClassType type, JClassType intf) { TypeOracleMediator.this.addImplementedInterface(type, intf); } public void addThrows(JAbstractMethod method, JClassType exception) { TypeOracleMediator.this.addThrows(method, exception); } public Map<String, JRealClassType> getBinaryMapper() { return TypeOracleMediator.this.binaryMapper; } public TypeOracle getTypeOracle() { return TypeOracleMediator.this.typeOracle; } public JMethod newMethod(JClassType type, String name, Map<Class<? extends Annotation>, Annotation> declaredAnnotations, JTypeParameter[] typeParams) { return TypeOracleMediator.this.newMethod(type, name, declaredAnnotations, typeParams); } public void newParameter(JAbstractMethod method, JType argType, String argName, Map<Class<? extends Annotation>, Annotation> declaredAnnotations, boolean argNamesAreReal) { TypeOracleMediator.this.newParameter(method, argType, argName, declaredAnnotations, argNamesAreReal); } public JRealClassType newRealClassType(JPackage pkg, String enclosingTypeName, boolean isLocalType, String className, boolean isIntf) { return TypeOracleMediator.this.newRealClassType(pkg, enclosingTypeName, className, isIntf); } public boolean resolveAnnotation(TreeLogger logger, CollectAnnotationData annotVisitor, Map<Class<? extends Annotation>, Annotation> declaredAnnotations) { return TypeOracleMediator.this.resolveAnnotation(logger, annotVisitor, declaredAnnotations); } public boolean resolveAnnotations(TreeLogger logger, List<CollectAnnotationData> annotations, Map<Class<? extends Annotation>, Annotation> declaredAnnotations) { return TypeOracleMediator.this.resolveAnnotations(logger, annotations, declaredAnnotations); } public boolean resolveClass(TreeLogger logger, JRealClassType type) { return TypeOracleMediator.this.resolveClass(logger, type, context); } public void setReturnType(JAbstractMethod method, JType returnType) { TypeOracleMediator.this.setReturnType(method, returnType); } public void setSuperClass(JRealClassType type, JClassType superType) { TypeOracleMediator.this.setSuperClass(type, superType); } } /** * Pairs of bits to convert from ASM Opcodes.* to Shared.* bitfields. */ private static final int[] ASM_TO_SHARED_MODIFIERS = new int[]{ Opcodes.ACC_PUBLIC, Shared.MOD_PUBLIC, // Opcodes.ACC_PRIVATE, Shared.MOD_PRIVATE, // Opcodes.ACC_PROTECTED, Shared.MOD_PROTECTED, // Opcodes.ACC_STATIC, Shared.MOD_STATIC, // Opcodes.ACC_FINAL, Shared.MOD_FINAL, // Opcodes.ACC_ABSTRACT, Shared.MOD_ABSTRACT, // Opcodes.ACC_VOLATILE, Shared.MOD_VOLATILE, // Opcodes.ACC_TRANSIENT, Shared.MOD_TRANSIENT, // }; private static final JTypeParameter[] NO_TYPE_PARAMETERS = new JTypeParameter[0]; /** * Turn on to trace class processing. */ private static final boolean TRACE_CLASSES = false; /** * Suppress some warnings related to missing valiation.jar on classpath. */ private static boolean warnedMissingValidationJar = false; private static JTypeParameter[] collectTypeParams(String signature) { if (signature != null) { List<JTypeParameter> params = new ArrayList<JTypeParameter>(); SignatureReader reader = new SignatureReader(signature); reader.accept(new CollectTypeParams(params)); return params.toArray(new JTypeParameter[params.size()]); } return NO_TYPE_PARAMETERS; } private static JTypeParameter[] getTypeParametersForClass( CollectClassData classData) { JTypeParameter[] typeParams = null; if (classData.getSignature() != null) { // TODO(jat): do we need to consider generic types w/ method type // params for local classes? typeParams = collectTypeParams(classData.getSignature()); } return typeParams; } private static Class<?> getWrapperClass(Class<?> primitiveClass) { assert primitiveClass.isPrimitive(); if (primitiveClass.equals(Integer.TYPE)) { return Integer.class; } else if (primitiveClass.equals(Boolean.TYPE)) { return Boolean.class; } else if (primitiveClass.equals(Byte.TYPE)) { return Byte.class; } else if (primitiveClass.equals(Character.TYPE)) { return Character.class; } else if (primitiveClass.equals(Short.TYPE)) { return Short.class; } else if (primitiveClass.equals(Long.TYPE)) { return Long.class; } else if (primitiveClass.equals(Float.TYPE)) { return Float.class; } else if (primitiveClass.equals(Double.TYPE)) { return Double.class; } else { throw new IllegalArgumentException(primitiveClass.toString() + " not a primitive class"); } } /** * @return <code>true</code> if this name is the special package-info type * name. */ private static boolean isPackageInfoTypeName(String qname) { return "package-info".equals(qname); } /** * Returns true if this class is a non-static class inside a generic class. * * TODO(jat): do we need to consider the entire hierarchy? * * @param classData * @param enclosingClassData * @return true if this class is a non-static class inside a generic class */ private static boolean nonStaticInsideGeneric(CollectClassData classData, CollectClassData enclosingClassData) { if (enclosingClassData == null || (classData.getAccess() & Opcodes.ACC_STATIC) != 0) { return false; } return getTypeParametersForClass(enclosingClassData) != null; } /** * Substitute the raw type if the supplied type is generic. * * @param type * @return original type or its raw type if it is generic */ private static JType possiblySubstituteRawType(JType type) { if (type != null) { JGenericType genericType = (JGenericType) type.isGenericType(); if (genericType != null) { type = genericType.getRawType(); } } return type; } // map of internal names to classes final Map<String, JRealClassType> binaryMapper = new HashMap<String, JRealClassType>(); private final Set<JRealClassType> resolved = new HashSet<JRealClassType>(); /** * Adds new units to an existing TypeOracle. * * @param logger logger to use * @param typeDataList collection of data need to build types */ public void addNewTypes(TreeLogger logger, Collection<TypeData> typeDataList) { addNewTypes(logger, typeDataList, new MethodArgNamesLookup()); } /** * Adds new units to an existing TypeOracle. * * @param logger logger to use * @param typeDataList collection of data need to build types * @param argsLookup Allows the caller to pass the method argument names which * are not normally available in bytecode. */ public void addNewTypes(TreeLogger logger, Collection<TypeData> typeDataList, MethodArgNamesLookup argsLookup) { Event typeOracleMediatorEvent = SpeedTracerLogger.start(CompilerEventType.TYPE_ORACLE_MEDIATOR); // First collect all class data. Event visitClassFileEvent = SpeedTracerLogger.start( CompilerEventType.TYPE_ORACLE_MEDIATOR, "phase", "Visit Class Files"); TypeOracleBuildContext context = new TypeOracleBuildContext(argsLookup); for (TypeData typeData : typeDataList) { CollectClassData cv = typeData.getCollectClassData(); // skip any classes that can't be referenced by name outside of // their local scope, such as anonymous classes and method-local classes if (!cv.hasNoExternalName()) { context.classMap.put(typeData.internalName, cv); } } visitClassFileEvent.end(); Event identityEvent = SpeedTracerLogger.start( CompilerEventType.TYPE_ORACLE_MEDIATOR, "phase", "Establish Identity"); // Perform a shallow pass to establish identity for new and old types. Set<JRealClassType> unresolvedTypes = new LinkedHashSet<JRealClassType>(); for (TypeData typeData : typeDataList) { CollectClassData cv = context.classMap.get(typeData.internalName); if (cv == null) { // ignore classes that were skipped earlier continue; } JRealClassType type = createType(typeData, unresolvedTypes, context); if (type != null) { binaryMapper.put(typeData.internalName, type); context.classMapType.put(type, cv); } } identityEvent.end(); Event resolveEnclosingEvent = SpeedTracerLogger.start( CompilerEventType.TYPE_ORACLE_MEDIATOR, "phase", "Resolve Enclosing Classes"); // Hook up enclosing types TreeLogger branch = logger.branch(TreeLogger.SPAM, "Resolving enclosing classes"); for (Iterator<JRealClassType> it = unresolvedTypes.iterator(); it.hasNext();) { JRealClassType type = it.next(); if (!resolveEnclosingClass(branch, type, context)) { // already logged why it failed, don't try and use it further it.remove(); } } resolveEnclosingEvent.end(); Event resolveUnresolvedEvent = SpeedTracerLogger.start( CompilerEventType.TYPE_ORACLE_MEDIATOR, "phase", "Resolve Unresolved Types"); // Resolve unresolved types. for (JRealClassType type : unresolvedTypes) { branch = logger.branch(TreeLogger.SPAM, "Resolving " + type.getQualifiedSourceName()); if (!resolveClass(branch, type, context)) { // already logged why it failed. // TODO: should we do anything else here? } } resolveUnresolvedEvent.end(); Event finishEvent = SpeedTracerLogger.start( CompilerEventType.TYPE_ORACLE_MEDIATOR, "phase", "Finish"); super.finish(); finishEvent.end(); // no longer needed context = null; typeOracleMediatorEvent.end(); } /** * @return a map from binary class names to JRealClassType. */ public Map<String, JRealClassType> getBinaryMapper() { return binaryMapper; } /** * Intended for unit testing only. * * @return a mocked up version of this mediator's resolver. */ public Resolver getMockResolver() { return new TypeOracleMediatorResolver(new TypeOracleBuildContext(new MethodArgNamesLookup())); } /** * @return the TypeOracle managed by the mediator. */ public TypeOracle getTypeOracle() { return typeOracle; } private Annotation createAnnotation(TreeLogger logger, Class<? extends Annotation> annotationClass, AnnotationData annotData) { // Make a copy before we mutate the collection. Map<String, Object> values = new HashMap<String, Object>(annotData.getValues()); for (Map.Entry<String, Object> entry : values.entrySet()) { Method method = null; Throwable caught = null; try { method = annotationClass.getMethod(entry.getKey()); } catch (SecurityException e) { caught = e; } catch (NoSuchMethodException e) { caught = e; } if (caught != null) { logger.log(TreeLogger.WARN, "Exception resolving " + annotationClass.getCanonicalName() + "." + entry.getKey(), caught); return null; } entry.setValue(resolveAnnotationValue(logger, method.getReturnType(), entry.getValue())); } return AnnotationProxyFactory.create(annotationClass, values); } private JRealClassType createType(TypeData typeData, CollectClassData collectClassData, CollectClassData enclosingClassData) { int access = collectClassData.getAccess(); String qualifiedSourceName = typeData.sourceName; String className = Shared.getShortName(qualifiedSourceName); JRealClassType resultType = null; String jpkgName = typeData.packageName; JPackage pkg = typeOracle.getOrCreatePackage(jpkgName); boolean isIntf = (access & Opcodes.ACC_INTERFACE) != 0; assert !collectClassData.hasNoExternalName(); String enclosingTypeName = null; if (enclosingClassData != null) { enclosingTypeName = InternalName.toSourceName(InternalName.getClassName(enclosingClassData.getName())); } if ((access & Opcodes.ACC_ANNOTATION) != 0) { resultType = newAnnotationType(pkg, enclosingTypeName, className); } else if ((access & Opcodes.ACC_ENUM) != 0) { resultType = newEnumType(pkg, enclosingTypeName, className); } else { JTypeParameter[] typeParams = getTypeParametersForClass(collectClassData); if ((typeParams != null && typeParams.length > 0) || nonStaticInsideGeneric(collectClassData, enclosingClassData)) { resultType = new JGenericType(typeOracle, pkg, enclosingTypeName, className, isIntf, typeParams); } else { resultType = newRealClassType(pkg, enclosingTypeName, className, isIntf); } } /* * Add modifiers since these are needed for * TypeOracle.getParameterizedType's error checking code. */ resultType.addModifierBits(mapBits(ASM_TO_SHARED_MODIFIERS, access)); if (isIntf) { // Always add implicit modifiers on interfaces. resultType.addModifierBits(Shared.MOD_STATIC | Shared.MOD_ABSTRACT); } /* * Add lastModified time from compilation unit */ resultType.addLastModifiedTime(typeData.lastModifiedTime); return resultType; } private JRealClassType createType(TypeData typeData, Set<JRealClassType> unresolvedTypes, TypeOracleBuildContext context) { CollectClassData collectClassData = context.classMap.get(typeData.internalName); String outerClassName = collectClassData.getOuterClass(); CollectClassData enclosingClassData = null; if (outerClassName != null) { enclosingClassData = context.classMap.get(outerClassName); if (enclosingClassData == null) { // if our enclosing class was skipped, skip this one too return null; } } JRealClassType realClassType = createType(typeData, collectClassData, enclosingClassData); unresolvedTypes.add(realClassType); return realClassType; } private Class<? extends Annotation> getAnnotationClass(TreeLogger logger, AnnotationData annotData) { Type type = Type.getType(annotData.getDesc()); String typeName = type.getClassName(); try { Class<?> clazz = Class.forName(typeName, false, Thread.currentThread().getContextClassLoader()); if (!Annotation.class.isAssignableFrom(clazz)) { logger.log(TreeLogger.ERROR, "Type " + typeName + " is not an annotation"); return null; } return clazz.asSubclass(Annotation.class); } catch (ClassNotFoundException e) { TreeLogger.Type level = TreeLogger.WARN; if (shouldSuppressUnresolvableAnnotation(logger, typeName)) { level = TreeLogger.DEBUG; } logger.log(level, "Ignoring unresolvable annotation type " + typeName); return null; } } @SuppressWarnings("unused") private Class<?> getClassLiteralForPrimitive(Type type) { switch (type.getSort()) { case Type.BOOLEAN: return Boolean.TYPE; case Type.BYTE: return Byte.TYPE; case Type.CHAR: return Character.TYPE; case Type.SHORT: return Short.TYPE; case Type.INT: return Integer.TYPE; case Type.LONG: return Long.TYPE; case Type.FLOAT: return Float.TYPE; case Type.DOUBLE: return Double.TYPE; case Type.VOID: return Void.TYPE; default: assert false : "Unexpected primitive type " + type; return null; } } /** * Map a bitset onto a different bitset. * * @param mapping int array containing a sequence of from/to pairs, each from * entry should have exactly one bit set * @param input bitset to map * @return mapped bitset */ private int mapBits(int[] mapping, int input) { int output = 0; for (int i = 0; i < mapping.length; i += 2) { if ((input & mapping[i]) != 0) { output |= mapping[i + 1]; } } return output; } private boolean resolveAnnotation(TreeLogger logger, CollectAnnotationData annotVisitor, Map<Class<? extends Annotation>, Annotation> declaredAnnotations) { AnnotationData annotData = annotVisitor.getAnnotation(); Class<? extends Annotation> annotationClass = getAnnotationClass(logger, annotData); if (annotationClass == null) { return false; } Annotation annotInstance = createAnnotation(logger, annotationClass, annotData); if (annotInstance == null) { return false; } declaredAnnotations.put(annotationClass, annotInstance); return true; } private boolean resolveAnnotations(TreeLogger logger, List<CollectAnnotationData> annotations, Map<Class<? extends Annotation>, Annotation> declaredAnnotations) { boolean succeeded = true; if (annotations != null) { for (CollectAnnotationData annotation : annotations) { succeeded &= resolveAnnotation(logger, annotation, declaredAnnotations); } } return succeeded; } @SuppressWarnings("unchecked") private Object resolveAnnotationValue(TreeLogger logger, Class<?> expectedType, Object value) { if (expectedType.isArray()) { Class<?> componentType = expectedType.getComponentType(); if (!value.getClass().isArray()) { logger.log(TreeLogger.WARN, "Annotation error: expected array of " + componentType.getCanonicalName() + ", got " + value.getClass().getCanonicalName()); return null; } if (componentType.isPrimitive()) { // primitive arrays are already resolved return value; } // resolve each element in the array int n = Array.getLength(value); Object newArray = Array.newInstance(componentType, n); for (int i = 0; i < n; ++i) { Object valueElement = Array.get(value, i); Object resolvedValue = resolveAnnotationValue(logger, componentType, valueElement); if (resolvedValue == null || !componentType.isAssignableFrom(resolvedValue.getClass())) { logger.log(TreeLogger.ERROR, "Annotation error: expected " + componentType + ", got " + resolvedValue); } else { Array.set(newArray, i, resolvedValue); } } return newArray; } else if (expectedType.isEnum()) { if (!(value instanceof AnnotationEnum)) { logger.log(TreeLogger.ERROR, "Annotation error: expected an enum value," + " but got " + value); return null; } AnnotationEnum annotEnum = (AnnotationEnum) value; Class<? extends Enum> enumType = expectedType.asSubclass(Enum.class); try { return Enum.valueOf(enumType, annotEnum.getValue()); } catch (IllegalArgumentException e) { logger.log(TreeLogger.WARN, "Unable to resolve annotation value '" + annotEnum.getValue() + "' within enum type '" + enumType.getName() + "'"); return null; } } else if (Annotation.class.isAssignableFrom(expectedType)) { if (!(value instanceof AnnotationData)) { logger.log(TreeLogger.WARN, "Annotation error: expected annotation type " + expectedType.getCanonicalName() + ", got " + value.getClass().getCanonicalName()); return null; } AnnotationData annotData = (AnnotationData) value; Class<? extends Annotation> annotationClass = getAnnotationClass(logger, annotData); if (!expectedType.isAssignableFrom(annotationClass)) { logger.log(TreeLogger.WARN, "Annotation error: expected " + expectedType.getCanonicalName() + ", got " + annotationClass.getCanonicalName()); return null; } return createAnnotation(logger, annotationClass, annotData); } else if (expectedType.isPrimitive()) { Class<?> wrapper = getWrapperClass(expectedType); return wrapper.cast(value); } else { if (expectedType.isAssignableFrom(value.getClass())) { return value; } if (Class.class.equals(expectedType)) { if (!(value instanceof Type)) { logger.log(TreeLogger.WARN, "Annotation error: expected a class " + "literal, but received " + value); return null; } Type valueType = (Type) value; // See if we can use a binary only class here try { return Class.forName(valueType.getClassName(), false, Thread.currentThread().getContextClassLoader()); } catch (ClassNotFoundException e) { logger.log(TreeLogger.ERROR, "Annotation error: cannot resolve " + valueType.getClassName(), e); return null; } } // TODO(jat) asserts about other acceptable types return value; } } private JType resolveArray(Type type) { assert type.getSort() == Type.ARRAY; JType resolvedType = resolveType(type.getElementType()); int dims = type.getDimensions(); for (int i = 0; i < dims; ++i) { resolvedType = typeOracle.getArrayType(resolvedType); } return resolvedType; } private boolean resolveClass(TreeLogger logger, JRealClassType type, TypeOracleBuildContext context) { assert type != null; // Avoid cycles and useless computation. if (resolved.contains(type)) { return true; } resolved.add(type); // Make sure our enclosing type is resolved first. if (type.getEnclosingType() != null && !resolveClass(logger, type.getEnclosingType(), context)) { return false; } // Build a search list for type parameters to find their definition, // resolving enclosing classes as we go up. TypeParameterLookup typeParamLookup = new TypeParameterLookup(); typeParamLookup.pushEnclosingScopes(type); CollectClassData classData = context.classMapType.get(type); assert classData != null; int access = classData.getAccess(); assert (!classData.getClassType().hasNoExternalName()); logger = logger.branch(TreeLogger.SPAM, "Found type '" + type.getQualifiedSourceName() + "'", null); // Handle package-info classes. if (isPackageInfoTypeName(type.getSimpleSourceName())) { return resolvePackage(logger, type, classData.getAnnotations()); } // Resolve annotations Map<Class<? extends Annotation>, Annotation> declaredAnnotations = new HashMap<Class<? extends Annotation>, Annotation>(); resolveAnnotations(logger, classData.getAnnotations(), declaredAnnotations); addAnnotations(type, declaredAnnotations); String signature = classData.getSignature(); /* * Note: Byte code from the OpenJDK compiler doesn't contain a type signature for non-static * inner classes of parameterized types that do not contain new parameters (but JDT compiled * byte code does). That can cause some differences in the way generic types are represented * in the type oracle. * * These differences also show up when using java.lang.reflect to look at types. */ if (signature != null) { // If we have a signature, use it for superclass and interfaces SignatureReader reader = new SignatureReader(signature); ResolveClassSignature classResolver = new ResolveClassSignature(context.resolver, binaryMapper, logger, type, typeParamLookup); reader.accept(classResolver); classResolver.finish(); } else { // Set the super type for non-interfaces if ((access & Opcodes.ACC_INTERFACE) == 0) { String superName = classData.getSuperName(); if (superName != null) { JClassType superType = binaryMapper.get(superName); if (superType == null || !resolveClass(logger, superType, context)) { logger.log(TreeLogger.WARN, "Unable to resolve supertype " + superName); return false; } setSuperClass(type, (JClassType) possiblySubstituteRawType(superType)); } } // Set interfaces for (String intfName : classData.getInterfaces()) { JClassType intf = binaryMapper.get(intfName); if (intf == null || !resolveClass(logger, intf, context)) { logger.log(TreeLogger.WARN, "Unable to resolve interface " + intfName); return false; } addImplementedInterface(type, (JClassType) possiblySubstituteRawType(intf)); } } if (((access & Opcodes.ACC_INTERFACE) == 0) && type.getSuperclass() == null) { // Only Object or interfaces should not have a superclass assert "java/lang/Object".equals(classData.getName()); } // Process methods for (CollectMethodData method : classData.getMethods()) { if (!resolveMethod(logger, type, method, typeParamLookup, context)) { logger.log(TreeLogger.WARN, "Unable to resolve method " + method); return false; } } // Process fields // Track the next enum ordinal across resolveField calls. int[] nextEnumOrdinal = new int[]{0}; for (CollectFieldData field : classData.getFields()) { if (!resolveField(logger, type, field, typeParamLookup, nextEnumOrdinal, context)) { logger.log(TreeLogger.WARN, "Unable to resolve field " + field); return false; } } return true; } private boolean resolveClass(TreeLogger logger, JType type, TypeOracleBuildContext context) { if (!(type instanceof JClassType)) { // non-classes are already resolved return true; } if (type instanceof JRealClassType) { return resolveClass(logger, (JRealClassType) type, context); } if (type instanceof JArrayType) { return resolveClass(logger, ((JArrayType) type).getComponentType(), context); } if (type instanceof JParameterizedType) { return resolveClass(logger, ((JParameterizedType) type).getBaseType(), context); } if (type instanceof JRawType) { return resolveClass(logger, ((JRawType) type).getBaseType(), context); } if (type instanceof JTypeParameter) { JTypeParameter typeParam = (JTypeParameter) type; if (!resolveClass(logger, typeParam.getDeclaringClass(), context)) { return false; } for (JClassType bound : typeParam.getBounds()) { if (!resolveClass(logger, bound, context)) { return false; } } return true; } if (type instanceof JWildcardType) { JWildcardType wildcard = (JWildcardType) type; for (JClassType bound : wildcard.getUpperBounds()) { if (!resolveClass(logger, bound, context)) { return false; } } for (JClassType bound : wildcard.getLowerBounds()) { if (!resolveClass(logger, bound, context)) { return false; } } return true; } return false; } private boolean resolveEnclosingClass(TreeLogger logger, JRealClassType type, TypeOracleBuildContext context) { assert type != null; if (type.getEnclosingType() != null) { return true; } // Find our enclosing class and set it CollectClassData classData = context.classMapType.get(type); assert classData != null; String outerClass = classData.getOuterClass(); JRealClassType enclosingType = null; if (outerClass != null) { enclosingType = binaryMapper.get(outerClass); // Ensure enclosing classes are resolved if (enclosingType != null) { if (!resolveEnclosingClass(logger, enclosingType, context)) { return false; } if (enclosingType.isGenericType() != null && (classData.getAccess() & (Opcodes.ACC_STATIC | Opcodes.ACC_INTERFACE)) != 0) { // If the inner class doesn't have access to it's enclosing type's // type variables, the enclosign type must be the raw type instead // of the generic type. JGenericType genericType = enclosingType.isGenericType(); setEnclosingType(type, genericType.getRawType()); } else { setEnclosingType(type, enclosingType); } } } return true; } private boolean resolveField(TreeLogger logger, JRealClassType type, CollectFieldData field, TypeParameterLookup typeParamLookup, int[] nextEnumOrdinal, TypeOracleBuildContext context) { Map<Class<? extends Annotation>, Annotation> declaredAnnotations = new HashMap<Class<? extends Annotation>, Annotation>(); resolveAnnotations(logger, field.getAnnotations(), declaredAnnotations); String name = field.getName(); JField jfield; if ((field.getAccess() & Opcodes.ACC_ENUM) != 0) { assert (type.isEnum() != null); jfield = newEnumConstant(type, name, declaredAnnotations, nextEnumOrdinal[0]++); } else { JField newField = newField(type, name, declaredAnnotations); jfield = newField; } // Get modifiers. // addModifierBits(jfield, mapBits(ASM_TO_SHARED_MODIFIERS, field.getAccess())); String signature = field.getSignature(); JType fieldType; if (signature != null) { SignatureReader reader = new SignatureReader(signature); JType[] fieldTypeRef = new JType[1]; reader.acceptType(new ResolveTypeSignature(context.resolver, binaryMapper, logger, fieldTypeRef, typeParamLookup, null)); fieldType = fieldTypeRef[0]; } else { fieldType = resolveType(Type.getType(field.getDesc())); } if (fieldType == null) { return false; } setFieldType(jfield, fieldType); return true; } private boolean resolveMethod(TreeLogger logger, JRealClassType type, CollectMethodData methodData, TypeParameterLookup typeParamLookup, TypeOracleBuildContext context) { Map<Class<? extends Annotation>, Annotation> declaredAnnotations = new HashMap<Class<? extends Annotation>, Annotation>(); resolveAnnotations(logger, methodData.getAnnotations(), declaredAnnotations); String name = methodData.getName(); if ("<clinit>".equals(name) || (methodData.getAccess() & Opcodes.ACC_SYNTHETIC) != 0) { // Ignore the following and leave them out of TypeOracle: // - static initializers // - synthetic methods return true; } if (type.isEnum() != null && "<init>".equals(name)) { // Leave enum constructors out of TypeOracle return true; } JAbstractMethod method; // Declare the type parameters. We will pass them into the constructors for // JConstructor/JMethod/JAnnotatedMethod. Then, we'll do a second pass to // resolve the bounds on each JTypeParameter object. JTypeParameter[] typeParams = collectTypeParams(methodData.getSignature()); typeParamLookup.pushScope(typeParams); boolean hasReturnType = true; if ("<init>".equals(name)) { name = type.getSimpleSourceName(); method = newConstructor(type, name, declaredAnnotations, typeParams); hasReturnType = false; } else { if (type.isAnnotation() != null) { // TODO(jat): actually resolve the default annotation value. method = newAnnotationMethod(type, name, declaredAnnotations, typeParams, null); } else { method = newMethod(type, name, declaredAnnotations, typeParams); } } addModifierBits(method, mapBits(ASM_TO_SHARED_MODIFIERS, methodData.getAccess())); if (type.isInterface() != null) { // Always add implicit modifiers on interface methods. addModifierBits(method, Shared.MOD_PUBLIC | Shared.MOD_ABSTRACT); } if ((methodData.getAccess() & Opcodes.ACC_VARARGS) != 0) { setVarArgs(method); } String signature = methodData.getSignature(); if (signature != null) { // If we have a signature, use it for superclass and interfaces SignatureReader reader = new SignatureReader(signature); ResolveMethodSignature methodResolver = new ResolveMethodSignature( context.resolver, logger, method, typeParamLookup, hasReturnType, methodData, methodData.getArgTypes(), methodData.getArgNames(), methodData.hasActualArgNames(), context.allMethodArgs); // TraceSignatureVisitor trace = new TraceSignatureVisitor( // methodData.getAccess()); // reader.accept(trace); // System.err.println("Method " + name + ": " + trace.getDeclaration()); reader.accept(methodResolver); if (!methodResolver.finish()) { return false; } } else { if (hasReturnType) { Type returnType = Type.getReturnType(methodData.getDesc()); JType returnJType = resolveType(returnType); if (returnJType == null) { return false; } setReturnType(method, returnJType); } if (!resolveParameters(logger, method, methodData, context)) { return false; } } // The signature might not actually include the exceptions if they don't // include a type variable, so resolveThrows is always used (it does // nothing if there are already exceptions defined) if (!resolveThrows(method, methodData)) { return false; } typeParamLookup.popScope(); return true; } private JRealClassType resolveObject(Type type) { assert type.getSort() == Type.OBJECT; String className = type.getInternalName(); assert Name.isInternalName(className); JRealClassType classType = binaryMapper.get(className); return classType; } private boolean resolvePackage(TreeLogger logger, JRealClassType type, List<CollectAnnotationData> annotations) { Map<Class<? extends Annotation>, Annotation> declaredAnnotations = new HashMap<Class<? extends Annotation>, Annotation>(); resolveAnnotations(logger, annotations, declaredAnnotations); addAnnotations(type.getPackage(), declaredAnnotations); return true; } private boolean resolveParameters(TreeLogger logger, JAbstractMethod method, CollectMethodData methodData, TypeOracleBuildContext context) { Type[] argTypes = methodData.getArgTypes(); boolean argNamesAreReal = methodData.hasActualArgNames(); String[] argNames = methodData.getArgNames(); if (!argNamesAreReal) { String[] lookupNames = context.allMethodArgs.lookup(method, methodData); if (lookupNames != null) { argNames = lookupNames; argNamesAreReal = true; } } List<CollectAnnotationData>[] paramAnnot = methodData.getArgAnnotations(); for (int i = 0; i < argTypes.length; ++i) { JType argType = resolveType(argTypes[i]); if (argType == null) { return false; } // Try to resolve annotations, ignore any that fail. Map<Class<? extends Annotation>, Annotation> declaredAnnotations = new HashMap<Class<? extends Annotation>, Annotation>(); resolveAnnotations(logger, paramAnnot[i], declaredAnnotations); newParameter(method, argType, argNames[i], declaredAnnotations, argNamesAreReal); } return true; } private boolean resolveThrows(JAbstractMethod method, CollectMethodData methodData) { if (method.getThrows().length == 0) { for (String excName : methodData.getExceptions()) { JType exc = resolveType(Type.getObjectType(excName)); if (exc == null) { return false; } addThrows(method, (JClassType) exc); } } return true; } /** * Returns a primitive, an array, or a JRealClassType. */ private JType resolveType(Type type) { // Check for primitives. switch (type.getSort()) { case Type.BOOLEAN: return JPrimitiveType.BOOLEAN; case Type.BYTE: return JPrimitiveType.BYTE; case Type.CHAR: return JPrimitiveType.CHAR; case Type.SHORT: return JPrimitiveType.SHORT; case Type.INT: return JPrimitiveType.INT; case Type.LONG: return JPrimitiveType.LONG; case Type.FLOAT: return JPrimitiveType.FLOAT; case Type.DOUBLE: return JPrimitiveType.DOUBLE; case Type.VOID: return JPrimitiveType.VOID; case Type.ARRAY: return resolveArray(type); case Type.OBJECT: JRealClassType resolvedType = resolveObject(type); return possiblySubstituteRawType(resolvedType); default: assert false : "Unexpected type " + type; return null; } } /** * Suppress multiple validation related messages and replace with a hint. * * @param typeName fully qualified type name to check for filtering */ // TODO(zundel): Can be removed when javax.validation is included in the JRE private boolean shouldSuppressUnresolvableAnnotation(TreeLogger logger, String typeName) { if (typeName.startsWith("javax.validation.") || typeName.startsWith("com.google.gwt.validation.")) { if (!warnedMissingValidationJar) { warnedMissingValidationJar = true; logger.log(TreeLogger.WARN, "Detected warnings related to '" + typeName + "'. " + " Is validation-<version>.jar on the classpath?"); logger.log(TreeLogger.INFO, "Specify -logLevel DEBUG to see all errors."); // Show the first error that matches return false; } // Suppress subsequent errors that match return true; } return false; } }