blob: 593c76a85d6394b6bcb1374d4d0046508c566600 [file] [log] [blame]
/*
* 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;
}
}