blob: b4496f1e6d321d8ea94289b2889a6c969efe8a5a [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.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.TypeOracleUpdater;
import com.google.gwt.dev.util.Name;
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.annotations.VisibleForTesting;
import com.google.gwt.thirdparty.guava.common.base.Function;
import com.google.gwt.thirdparty.guava.common.collect.Collections2;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap;
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.Queues;
import com.google.gwt.thirdparty.guava.common.collect.Sets;
import com.google.gwt.thirdparty.guava.common.util.concurrent.ThreadFactoryBuilder;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.signature.SignatureReader;
import org.objectweb.asm.util.TraceClassVisitor;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Builds or rebuilds a {@link com.google.gwt.core.ext.typeinfo.TypeOracle} from a set of
* compilation units.
*/
public class CompilationUnitTypeOracleUpdater extends TypeOracleUpdater {
/**
* A container to hold all the information we need to add one type to the TypeOracle.
*/
static class TypeData {
private final byte[] byteCode;
private CollectClassData classData;
private final String internalName;
/**
* A timestamp as returned from {@link System#currentTimeMillis()}
*/
private final long lastModifiedTime;
private final String packageName;
private final String sourceName;
protected TypeData(String packageName, String sourceName, String internalName, byte[] byteCode,
long lastModifiedTime) {
this.packageName = packageName;
this.sourceName = sourceName;
this.internalName = internalName;
this.byteCode = byteCode;
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 classVisitor = classData;
if (TRACE_CLASSES) {
classVisitor = new TraceClassVisitor(classVisitor, new PrintWriter(System.out));
}
reader.accept(classVisitor, 0);
}
return classData;
}
}
private class CompilationUnitTypeOracleResolver implements Resolver {
private final TypeOracleBuildContext context;
public CompilationUnitTypeOracleResolver(TypeOracleBuildContext context) {
this.context = context;
}
@Override
public void addImplementedInterface(JRealClassType type, JClassType intf) {
CompilationUnitTypeOracleUpdater.this.addImplementedInterface(type, intf);
}
@Override
public void addThrows(JAbstractMethod method, JClassType exception) {
CompilationUnitTypeOracleUpdater.this.addThrows(method, exception);
}
@Override
public JRealClassType findByInternalName(String internalName) {
return CompilationUnitTypeOracleUpdater.this.findByInternalName(internalName);
}
@Override
public TypeOracle getTypeOracle() {
return CompilationUnitTypeOracleUpdater.this.typeOracle;
}
@Override
public JMethod newMethod(JClassType type, String name,
Map<Class<? extends Annotation>, Annotation> declaredAnnotations,
JTypeParameter[] typeParams) {
return CompilationUnitTypeOracleUpdater.this.newMethod(
type, name, declaredAnnotations, typeParams);
}
@Override
public void newParameter(JAbstractMethod method, JType argType, String argName,
Map<Class<? extends Annotation>, Annotation> declaredAnnotations, boolean argNamesAreReal) {
CompilationUnitTypeOracleUpdater.this.newParameter(
method, argType, argName, declaredAnnotations, argNamesAreReal);
}
@Override
public JRealClassType newRealClassType(JPackage pkg, String enclosingTypeName,
boolean isLocalType, String simpleSourceName, boolean isInterface) {
return CompilationUnitTypeOracleUpdater.this.newRealClassType(
pkg, enclosingTypeName, simpleSourceName, isInterface);
}
@Override
public boolean resolveAnnotations(TreeLogger logger, List<CollectAnnotationData> annotations,
Map<Class<? extends Annotation>, Annotation> declaredAnnotations) {
return CompilationUnitTypeOracleUpdater.this.resolveAnnotations(
logger, annotations, declaredAnnotations);
}
@Override
public boolean resolveClass(TreeLogger logger, JRealClassType type) {
return CompilationUnitTypeOracleUpdater.this.resolveClass(logger, type, context);
}
@Override
public void setReturnType(JAbstractMethod method, JType returnType) {
CompilationUnitTypeOracleUpdater.this.setReturnType(method, returnType);
}
@Override
public void setSuperClass(JRealClassType type, JClassType superType) {
CompilationUnitTypeOracleUpdater.this.setSuperClass(type, superType);
}
}
/**
* This context keeps common data so we don't have to pass it around between methods for one pass
* of {@link CompilationUnitTypeOracleUpdater#addNewTypesDontIndex(TreeLogger, Collection,
* MethodArgNamesLookup)} .
*/
protected class TypeOracleBuildContext {
protected final MethodArgNamesLookup allMethodArgs;
private final Map<String, CollectClassData> classDataByInternalName = Maps.newHashMap();
private final Map<JRealClassType, CollectClassData> classDataByType = Maps.newHashMap();
private final Resolver resolver = new CompilationUnitTypeOracleResolver(this);
protected TypeOracleBuildContext(MethodArgNamesLookup allMethodArgs) {
this.allMethodArgs = allMethodArgs;
}
}
/**
* 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 = Lists.newArrayList();
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");
}
}
/**
* Returns whether this name is the special package-info type name.
*/
private static boolean isPackageInfoTypeName(String simpleSourceName) {
return "package-info".equals(simpleSourceName);
}
/**
* Returns true if this class is a non-static class inside a generic class.
*/
// TODO(jat): do we need to consider the entire hierarchy?
private static boolean nonStaticInsideGeneric(
CollectClassData classData, CollectClassData enclosingClassData) {
if (enclosingClassData == null || (classData.getAccess() & Opcodes.ACC_STATIC) != 0) {
return false;
}
return getTypeParametersForClass(enclosingClassData) != null;
}
/**
* Returns the 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;
}
private final Set<String> resolvedTypeSourceNames = Sets.newHashSet();
private final Map<String, JRealClassType> typesByInternalName = Maps.newHashMap();
/**
* An executor service to parallelize some of the update process.
*/
private static ExecutorService executor =
new ThreadPoolExecutor(0, Runtime.getRuntime().availableProcessors(), 60L, TimeUnit.SECONDS,
Queues.<Runnable>newLinkedBlockingQueue(),
// Make sure this executor lets the whole process terminate correctly even if there
// are still live threads.
new ThreadFactoryBuilder().setDaemon(true).build());
public CompilationUnitTypeOracleUpdater(TypeOracle typeOracle) {
super(typeOracle);
}
/**
* Adds new units to an existing TypeOracle but does not yet index their type hierarchy.<br />
*
* It is ok for this function to recursive since no repeated or invalid type indexing will result.
*
* @param logger logger to use
* @param typeDataList collection of data need to build types. (Doesn't retain references to
* TypeData instances.)
* @param argsLookup Allows the caller to pass the method argument names which are not normally
* available in bytecode.
*/
void addNewTypesDontIndex(
TreeLogger logger, Collection<TypeData> typeDataList, MethodArgNamesLookup argsLookup) {
Event typeOracleUpdaterEvent = SpeedTracerLogger.start(CompilerEventType.TYPE_ORACLE_UPDATER);
// First collect all class data.
Event visitClassFileEvent = SpeedTracerLogger.start(
CompilerEventType.TYPE_ORACLE_UPDATER, "phase", "Visit Class Files");
TypeOracleBuildContext context = getContext(argsLookup);
for (TypeData typeData : typeDataList) {
CollectClassData classData = 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 (classData.hasNoExternalName()) {
continue;
}
// skip classes that have been previously added
if (typesByInternalName.containsKey(classData.getInternalName())) {
continue;
}
context.classDataByInternalName.put(typeData.internalName, classData);
}
visitClassFileEvent.end();
Event identityEvent = SpeedTracerLogger.start(
CompilerEventType.TYPE_ORACLE_UPDATER, "phase", "Establish Identity");
// Perform a shallow pass to establish identity for new and old types.
Set<JRealClassType> unresolvedTypes = Sets.newLinkedHashSet();
for (TypeData typeData : typeDataList) {
CollectClassData classData = context.classDataByInternalName.get(typeData.internalName);
if (classData == null) {
// ignore classes that were skipped earlier
continue;
}
if (typesByInternalName.containsKey(classData.getInternalName())) {
// skip classes that have been previously added
continue;
}
JRealClassType type = createType(typeData, unresolvedTypes, context);
if (type != null) {
assert Name.isInternalName(typeData.internalName);
typesByInternalName.put(typeData.internalName, type);
context.classDataByType.put(type, classData);
}
}
identityEvent.end();
Event resolveEnclosingEvent = SpeedTracerLogger.start(
CompilerEventType.TYPE_ORACLE_UPDATER, "phase", "Resolve Enclosing Classes");
// Hook up enclosing types
TreeLogger branch = logger.branch(TreeLogger.SPAM, "Resolving enclosing classes");
for (Iterator<JRealClassType> unresolvedTypesIterator = unresolvedTypes.iterator();
unresolvedTypesIterator.hasNext();) {
JRealClassType unresolvedType = unresolvedTypesIterator.next();
if (!resolveEnclosingClass(branch, unresolvedType, context)) {
// already logged why it failed, don't try and use it further
unresolvedTypesIterator.remove();
}
}
resolveEnclosingEvent.end();
Event resolveUnresolvedEvent = SpeedTracerLogger.start(
CompilerEventType.TYPE_ORACLE_UPDATER, "phase", "Resolve Unresolved Types");
// Resolve unresolved types.
for (JRealClassType unresolvedType : unresolvedTypes) {
branch =
logger.branch(TreeLogger.SPAM, "Resolving " + unresolvedType.getQualifiedSourceName());
if (!resolveClass(branch, unresolvedType, context)) {
// already logged why it failed.
// TODO: should we do anything else here?
}
}
resolveUnresolvedEvent.end();
// no longer needed
context = null;
typeOracleUpdaterEvent.end();
}
private static void prefechTypeData(Collection<TypeData> typeDataList) {
// Parse bytecode in parallel by calling {@code TypeData.getCollectClassData()} in parallel.
try {
executor.<Void>invokeAll(Collections2.transform(typeDataList,
new Function<TypeData, Callable<Void>>() {
@Override
public Callable<Void> apply(final TypeData typeData) {
return new Callable<Void>() {
@Override
public Void call() {
typeData.getCollectClassData();
return null;
}
};
}
}));
} catch (InterruptedException e) {
// InterruptedException can be safely ignored here as the threads interrupted are only
// precomputing data in parallel that will otherwise be computed later sequentially if
// the threads are aborted.
// Anyway restore the thread interrupted state just in case.
Thread.currentThread().interrupt();
}
}
/**
* Adds new units to an existing TypeOracle and indexes their type hierarchy.
*/
public void addNewUnits(TreeLogger logger, Collection<CompilationUnit> compilationUnits) {
addNewTypesDontIndex(logger, compilationUnits);
indexTypes();
}
@VisibleForTesting
void indexTypes() {
Event finishEvent =
SpeedTracerLogger.start(CompilerEventType.TYPE_ORACLE_UPDATER, "phase", "Finish");
super.finish();
finishEvent.end();
}
protected void addNewTypesDontIndex(TreeLogger logger,
Collection<CompilationUnit> compilationUnits) {
Collection<TypeData> typeDataList = Lists.newArrayList();
// Create method args data for types to add
MethodArgNamesLookup argsLookup = new MethodArgNamesLookup();
for (CompilationUnit compilationUnit : compilationUnits) {
argsLookup.mergeFrom(compilationUnit.getMethodArgs());
}
// Create list including byte code for each type to add
// TODO(rluble): this process can be done in parallel. Probably best to merge this for
// loop with the one in prefetchTypeData.
for (CompilationUnit compilationUnit : compilationUnits) {
for (CompiledClass compiledClass : compilationUnit.getCompiledClasses()) {
TypeData typeData = new TypeData(compiledClass.getPackageName(),
compiledClass.getSourceName(), compiledClass.getInternalName(),
compiledClass.getBytes(), compiledClass.getUnit().getLastModified());
typeDataList.add(typeData);
}
}
prefechTypeData(typeDataList);
// Add the new types to the type oracle build in progress.
addNewTypesDontIndex(logger, typeDataList, argsLookup);
}
@VisibleForTesting
public Resolver getMockResolver() {
return new CompilationUnitTypeOracleResolver(
new TypeOracleBuildContext(new MethodArgNamesLookup()));
}
public TypeOracle getTypeOracle() {
return typeOracle;
}
@VisibleForTesting
public Map<String, JRealClassType> getTypesByInternalName() {
return typesByInternalName;
}
/**
* Returns the type corresponding to the given internal name.<br />
*
* Implementations are free to service requests eagerly or lazily.
*/
protected JRealClassType findByInternalName(String internalName) {
assert Name.isInternalName(internalName);
return typesByInternalName.get(internalName);
}
private Annotation createAnnotation(TreeLogger logger,
Class<? extends Annotation> annotationClass, AnnotationData annotationData) {
// Make a copy before we mutate the collection.
Map<String, Object> values = Maps.newHashMap(annotationData.getValues());
logger =
logger.branch(TreeLogger.TRACE, "Resolving annotation for " + annotationClass.getName());
for (Map.Entry<String, Object> entry : values.entrySet()) {
Method method = null;
Throwable caught = null;
try {
method = annotationClass.getMethod(entry.getKey());
entry.setValue(resolveAnnotationValue(logger, method.getReturnType(), entry.getValue()));
} catch (SecurityException e) {
caught = e;
} catch (NoSuchMethodException e) {
caught = e;
}
if (caught != null) {
logger.log(TreeLogger.WARN,
"Exception resolving " + annotationClass.getCanonicalName() + "." + entry.getKey() +
" : " + caught.getMessage());
return null;
}
}
return AnnotationProxyFactory.create(annotationClass, values);
}
/**
* Doesn't retain a reference to the TypeData.
*/
private JRealClassType createType(
TypeData typeData, CollectClassData collectClassData, CollectClassData enclosingClassData) {
int access = collectClassData.getAccess();
String simpleName = Shared.getShortName(typeData.sourceName);
JRealClassType type = null;
String packageName = typeData.packageName;
JPackage pkg = typeOracle.getOrCreatePackage(packageName);
boolean isInterface = (access & Opcodes.ACC_INTERFACE) != 0;
assert !collectClassData.hasNoExternalName();
String enclosingSimpleName = null;
if (enclosingClassData != null) {
enclosingSimpleName = enclosingClassData.getNestedSourceName();
}
if ((access & Opcodes.ACC_ANNOTATION) != 0) {
type = newAnnotationType(pkg, enclosingSimpleName, simpleName);
} else if ((access & Opcodes.ACC_ENUM) != 0) {
type = newEnumType(pkg, enclosingSimpleName, simpleName);
} else {
JTypeParameter[] typeParams = getTypeParametersForClass(collectClassData);
if ((typeParams != null && typeParams.length > 0)
|| nonStaticInsideGeneric(collectClassData, enclosingClassData)) {
type = new JGenericType(
typeOracle, pkg, enclosingSimpleName, simpleName, isInterface, typeParams);
} else {
type = newRealClassType(pkg, enclosingSimpleName, simpleName, isInterface);
}
}
type.addModifierBits(mapBits(ASM_TO_SHARED_MODIFIERS, access));
if (isInterface) {
// Always add implicit modifiers on interfaces.
type.addModifierBits(Shared.MOD_STATIC | Shared.MOD_ABSTRACT);
}
type.addLastModifiedTime(typeData.lastModifiedTime);
return type;
}
/**
* Doesn't retain a reference to the TypeData.
*/
private JRealClassType createType(
TypeData typeData, Set<JRealClassType> unresolvedTypes, TypeOracleBuildContext context) {
CollectClassData classData = context.classDataByInternalName.get(typeData.internalName);
String enclosingClassInternalName = classData.getEnclosingInternalName();
CollectClassData enclosingClassData = null;
if (enclosingClassInternalName != null) {
enclosingClassData = context.classDataByInternalName.get(enclosingClassInternalName);
if (enclosingClassData == null) {
// if our enclosing class was skipped, skip this one too
return null;
}
}
JRealClassType realClassType = createType(typeData, classData, enclosingClassData);
unresolvedTypes.add(realClassType);
return realClassType;
}
private Class<? extends Annotation> getAnnotationClass(
TreeLogger logger, AnnotationData annotationData) {
Type type = Type.getType(annotationData.getDesc());
String binaryName = type.getClassName();
try {
Class<?> clazz =
Class.forName(binaryName, false, Thread.currentThread().getContextClassLoader());
if (!Annotation.class.isAssignableFrom(clazz)) {
logger.log(TreeLogger.ERROR, "Type " + binaryName + " is not an annotation");
return null;
}
return clazz.asSubclass(Annotation.class);
} catch (ClassNotFoundException e) {
TreeLogger.Type level = TreeLogger.WARN;
if (shouldSuppressUnresolvableAnnotation(logger, binaryName)) {
level = TreeLogger.DEBUG;
}
logger.log(level, "Ignoring unresolvable annotation type " + binaryName);
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;
}
}
/**
* Returns a new build context to use for the duration of one addNewTypesDontIndex() invocation.
*/
protected TypeOracleBuildContext getContext(MethodArgNamesLookup argsLookup) {
return new TypeOracleBuildContext(argsLookup);
}
/**
* 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 annotationVisitor,
Map<Class<? extends Annotation>, Annotation> declaredAnnotations) {
AnnotationData annotationData = annotationVisitor.getAnnotation();
Class<? extends Annotation> annotationClass = getAnnotationClass(logger, annotationData);
if (annotationClass == null) {
return false;
}
Annotation annotation = createAnnotation(logger, annotationClass, annotationData);
if (annotation == null) {
return false;
}
declaredAnnotations.put(annotationClass, annotation);
return true;
}
private boolean resolveAnnotations(TreeLogger logger,
List<CollectAnnotationData> annotationVisitors,
Map<Class<? extends Annotation>, Annotation> declaredAnnotations) {
boolean succeeded = true;
if (annotationVisitors != null) {
for (CollectAnnotationData annotationVisitor : annotationVisitors) {
succeeded &= resolveAnnotation(logger, annotationVisitor, 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 resolvedTypes
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 forName(valueType.getClassName());
} catch (ClassNotFoundException e) {
logger.log(
TreeLogger.WARN, "Annotation error: cannot resolve " + valueType.getClassName());
return null;
}
}
// TODO(jat) asserts about other acceptable types
return value;
}
}
private static final Map<String, Class<?>> BUILT_IN_PRIMITIVE_MAP;
static {
ImmutableMap.Builder<String, Class<?>> builder = ImmutableMap.<String, Class<?>>builder()
.put("Z", boolean.class)
.put("B", byte.class)
.put("C", char.class)
.put("S", short.class)
.put("I", int.class)
.put("F", float.class)
.put("D", double.class)
.put("J", long.class)
.put("V", void.class);
for (Class c : new Class[] { void.class, boolean.class, byte.class, char.class,
short.class, int.class, float.class, double.class, long.class }) {
builder.put(c.getName(), c);
}
BUILT_IN_PRIMITIVE_MAP = builder.build();
}
public static Class forName(String name) throws ClassNotFoundException {
Class c = BUILT_IN_PRIMITIVE_MAP.get(name);
if (c == null) {
c = Class.forName(name, false, Thread.currentThread().getContextClassLoader());
}
return c;
}
private JType resolveArray(Type type) {
assert type.getSort() == Type.ARRAY;
JType resolvedType = resolveType(type.getElementType());
int dimensions = type.getDimensions();
for (int i = 0; i < dimensions; ++i) {
resolvedType = typeOracle.getArrayType(resolvedType);
}
return resolvedType;
}
// TODO(stalcup): refactor this recursive resolveFoo() process. At the moment there is a recursive
// tree of resolveFoo() calls. Most of them return a boolean, but some do not. Some of the
// booleans are read and acted upon, some are not. Some functions do their own logging and some
// return a false to indicate that the caller should log. Sometimes the boolean return value is an
// indication of whether logging should occur and other times it's an indication of whether
// exploration should continue. It's a mess. Some ideas that would probably make this more sane:
// be consistent about SPAM logging throughout and WARN logging only at the tip, and process types
// in a queue instead of with recursion.
private boolean resolveClass(
TreeLogger logger, JRealClassType unresolvedType, TypeOracleBuildContext context) {
assert unresolvedType != null;
// Avoid cycles and useless computation.
if (resolvedTypeSourceNames.contains(unresolvedType.getQualifiedSourceName())) {
return true;
}
resolvedTypeSourceNames.add(unresolvedType.getQualifiedSourceName());
// Make sure our enclosing type is resolved first.
if (unresolvedType.getEnclosingType() != null
&& !resolveClass(logger, unresolvedType.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(unresolvedType);
CollectClassData classData = context.classDataByType.get(unresolvedType);
assert classData != null;
int access = classData.getAccess();
assert (!classData.getClassType().hasNoExternalName());
logger = logger.branch(
TreeLogger.SPAM, "Found type '" + unresolvedType.getQualifiedSourceName() + "'", null);
// Handle package-info classes.
if (isPackageInfoTypeName(unresolvedType.getSimpleSourceName())) {
return resolvePackage(logger, unresolvedType, classData.getAnnotations());
}
// Resolve annotations
Map<Class<? extends Annotation>, Annotation> declaredAnnotations = Maps.newHashMap();
resolveAnnotations(logger, classData.getAnnotations(), declaredAnnotations);
addAnnotations(unresolvedType, 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.
*/
boolean isInterface = (access & Opcodes.ACC_INTERFACE) != 0;
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, logger, unresolvedType, typeParamLookup);
reader.accept(classResolver);
classResolver.finish();
if (unresolvedType.getSuperclass() != null
&& !resolveClass(logger, unresolvedType.getSuperclass(), context)) {
logger.log(TreeLogger.WARN,
"Unable to resolve supertype " + unresolvedType.getSuperclass().getName());
return false;
}
} else {
// Set the super type for non-interfaces
if (!isInterface) {
String superInternalName = classData.getSuperInternalName();
assert Name.isInternalName(superInternalName);
if (superInternalName != null) {
JClassType superType = findByInternalName(superInternalName);
if (superType == null || !resolveClass(logger, superType, context)) {
logger.log(TreeLogger.WARN, "Unable to resolve supertype " + superInternalName);
return false;
}
setSuperClass(unresolvedType, (JClassType) possiblySubstituteRawType(superType));
}
}
// Set interfaces
for (String interfaceInternalName : classData.getInterfaceInternalNames()) {
JClassType interfaceType = findByInternalName(interfaceInternalName);
if (interfaceType == null || !resolveClass(logger, interfaceType, context)) {
logger.log(TreeLogger.WARN, "Unable to resolve interface " + interfaceInternalName);
return false;
}
addImplementedInterface(
unresolvedType, (JClassType) possiblySubstituteRawType(interfaceType));
}
}
if (!isInterface && unresolvedType.getSuperclass() == null) {
// Only Object or interfaces should not have a superclass
assert "java/lang/Object".equals(classData.getInternalName());
}
// Process methods
for (CollectMethodData method : classData.getMethods()) {
TreeLogger branch = logger.branch(TreeLogger.SPAM, "Resolving method " + method.getName());
// TODO(rluble): Allow the users to ask for Java 8 features. For now these are filtered out.
if (isInterface && isJava8InterfaceMethod(method)) {
logger.log(TreeLogger.Type.SPAM, "Ignoring Java 8 interface method " + method.getName());
continue;
}
if (!resolveMethod(branch, unresolvedType, method, typeParamLookup, context)) {
// Already logged.
return false;
}
}
// Process fields
// Track the next enum ordinal across resolveField calls.
int[] nextEnumOrdinal = new int[] {0};
for (CollectFieldData field : classData.getFields()) {
TreeLogger branch = logger.branch(TreeLogger.SPAM, "Resolving field " + field.getName());
if (!resolveField(branch, unresolvedType, field, typeParamLookup, nextEnumOrdinal, context)) {
// Already logged.
return false;
}
}
return true;
}
private boolean isJava8InterfaceMethod(CollectMethodData method) {
// (Normal) interface methods are abstract. Java 8 introduced the ability to declare default
// methods and static methods both of which are exposed as non abstract methods.
return (method.getAccess() & Opcodes.ACC_ABSTRACT) == 0;
}
private boolean resolveClass(
TreeLogger logger, JType unresolvedType, TypeOracleBuildContext context) {
if (!(unresolvedType instanceof JClassType)) {
// non-classes are already resolvedTypes
return true;
}
if (unresolvedType instanceof JRealClassType) {
return resolveClass(logger, (JRealClassType) unresolvedType, context);
}
if (unresolvedType instanceof JArrayType) {
return resolveClass(logger, ((JArrayType) unresolvedType).getComponentType(), context);
}
if (unresolvedType instanceof JParameterizedType) {
return resolveClass(logger, ((JParameterizedType) unresolvedType).getBaseType(), context);
}
if (unresolvedType instanceof JRawType) {
return resolveClass(logger, ((JRawType) unresolvedType).getBaseType(), context);
}
if (unresolvedType instanceof JTypeParameter) {
JTypeParameter typeParam = (JTypeParameter) unresolvedType;
if (!resolveClass(logger, typeParam.getDeclaringClass(), context)) {
return false;
}
for (JClassType bound : typeParam.getBounds()) {
if (!resolveClass(logger, bound, context)) {
return false;
}
}
return true;
}
if (unresolvedType instanceof JWildcardType) {
JWildcardType wildcard = (JWildcardType) unresolvedType;
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 unresolvedType, TypeOracleBuildContext context) {
assert unresolvedType != null;
if (unresolvedType.getEnclosingType() != null) {
return true;
}
// Find our enclosing class and set it
CollectClassData classData = context.classDataByType.get(unresolvedType);
assert classData != null;
String enclosingClassInternalName = classData.getEnclosingInternalName();
JRealClassType enclosingType = null;
if (enclosingClassInternalName != null) {
enclosingType = findByInternalName(enclosingClassInternalName);
// 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 enclosing type must be the raw type instead
// of the generic type.
JGenericType genericType = enclosingType.isGenericType();
setEnclosingType(unresolvedType, genericType.getRawType());
} else {
setEnclosingType(unresolvedType, enclosingType);
}
}
}
return true;
}
private boolean resolveField(TreeLogger logger, JRealClassType unresolvedType,
CollectFieldData field, TypeParameterLookup typeParamLookup, int[] nextEnumOrdinal,
TypeOracleBuildContext context) {
Map<Class<? extends Annotation>, Annotation> declaredAnnotations = Maps.newHashMap();
resolveAnnotations(logger, field.getAnnotations(), declaredAnnotations);
String name = field.getName();
JField jfield;
if ((field.getAccess() & Opcodes.ACC_ENUM) != 0) {
assert (unresolvedType.isEnum() != null);
jfield = newEnumConstant(unresolvedType, name, declaredAnnotations, nextEnumOrdinal[0]++);
} else {
JField newField = newField(unresolvedType, name, declaredAnnotations);
jfield = newField;
}
// Get modifiers.
addModifierBits(jfield, mapBits(ASM_TO_SHARED_MODIFIERS, field.getAccess()));
String signature = field.getSignature();
JType fieldJType;
if (signature != null) {
SignatureReader reader = new SignatureReader(signature);
JType[] fieldTypeRef = new JType[1];
reader.acceptType(new ResolveTypeSignature(
context.resolver, logger, fieldTypeRef, typeParamLookup, null));
fieldJType = fieldTypeRef[0];
if (fieldJType == null) {
logger.log(TreeLogger.ERROR, "Unable to resolve type in field signature " + signature);
return false;
}
} else {
Type fieldType = Type.getType(field.getDesc());
fieldJType = resolveType(fieldType);
if (fieldJType == null) {
logger.log(TreeLogger.ERROR, "Unable to resolve type " + fieldType.getInternalName()
+ " of field " + field.getName());
return false;
}
}
setFieldType(jfield, fieldJType);
return true;
}
private boolean resolveMethod(TreeLogger logger, JRealClassType unresolvedType,
CollectMethodData methodData, TypeParameterLookup typeParamLookup,
TypeOracleBuildContext context) {
Map<Class<? extends Annotation>, Annotation> declaredAnnotations = Maps.newHashMap();
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 (unresolvedType.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 = unresolvedType.getSimpleSourceName();
method = newConstructor(unresolvedType, name, declaredAnnotations, typeParams);
hasReturnType = false;
} else {
if (unresolvedType.isAnnotation() != null) {
// TODO(jat): actually resolve the default annotation value.
method = newAnnotationMethod(unresolvedType, name, declaredAnnotations, typeParams, null);
} else {
method = newMethod(unresolvedType, name, declaredAnnotations, typeParams);
}
}
addModifierBits(method, mapBits(ASM_TO_SHARED_MODIFIERS, methodData.getAccess()));
if (unresolvedType.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);
reader.accept(methodResolver);
if (!methodResolver.finish()) {
logger.log(TreeLogger.ERROR, "Failed to resolve.");
return false;
}
} else {
if (hasReturnType) {
Type returnType = Type.getReturnType(methodData.getDesc());
JType returnJType = resolveType(returnType);
if (returnJType == null) {
logger.log(TreeLogger.ERROR,
"Unable to resolve return type " + returnType.getInternalName());
return false;
}
setReturnType(method, returnJType);
}
if (!resolveParameters(logger, method, methodData, context)) {
// Already logged.
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(logger, method, methodData)) {
// Already logged.
return false;
}
typeParamLookup.popScope();
return true;
}
private JRealClassType resolveObject(Type type) {
assert type.getSort() == Type.OBJECT;
String internalName = type.getInternalName();
assert Name.isInternalName(internalName);
JRealClassType classType = findByInternalName(internalName);
return classType;
}
private boolean resolvePackage(TreeLogger logger, JRealClassType unresolvedType,
List<CollectAnnotationData> annotationVisitors) {
Map<Class<? extends Annotation>, Annotation> declaredAnnotations = Maps.newHashMap();
resolveAnnotations(logger, annotationVisitors, declaredAnnotations);
addAnnotations(unresolvedType.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) {
Type argType = argTypes[i];
JType argJType = resolveType(argType);
if (argJType == null) {
logger.log(TreeLogger.ERROR, "Unable to resolve type " + argType.getInternalName()
+ " of argument " + methodData.getArgNames()[i]);
return false;
}
// Try to resolve annotations, ignore any that fail.
Map<Class<? extends Annotation>, Annotation> declaredAnnotations = Maps.newHashMap();
resolveAnnotations(logger, paramAnnot[i], declaredAnnotations);
newParameter(method, argJType, argNames[i], declaredAnnotations, argNamesAreReal);
}
return true;
}
private boolean resolveThrows(TreeLogger logger, JAbstractMethod method,
CollectMethodData methodData) {
if (method.getThrows().length == 0) {
for (String exceptionName : methodData.getExceptions()) {
Type exceptionType = Type.getObjectType(exceptionName);
JType exceptionJType = resolveType(exceptionType);
if (exceptionJType == null) {
logger.log(TreeLogger.ERROR,
"Unable to resolve type " + exceptionType.getInternalName() + " of thrown exception");
return false;
}
addThrows(method, (JClassType) exceptionJType);
}
}
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.
*/
// TODO(zundel): Can be removed when javax.validation is included in the JRE
private boolean shouldSuppressUnresolvableAnnotation(TreeLogger logger, String sourceName) {
if (sourceName.startsWith("javax.validation.")
|| sourceName.startsWith("com.google.gwt.validation.")) {
if (!warnedMissingValidationJar) {
warnedMissingValidationJar = true;
logger.log(TreeLogger.WARN, "Detected warnings related to '" + sourceName + "'. "
+ " 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;
}
}