| /* |
| * 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; |
| } |
| } |