blob: 090e3f11e3eb76b66ac4f2244606e076e89a047f [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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.android.tools.lint;
import static com.android.SdkConstants.INT_DEF_ANNOTATION;
import static com.android.SdkConstants.STRING_DEF_ANNOTATION;
import static com.android.SdkConstants.UTF_8;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
import com.android.sdklib.IAndroidTarget;
import com.android.tools.lint.client.api.JavaParser;
import com.android.tools.lint.client.api.LintClient;
import com.android.tools.lint.detector.api.JavaContext;
import com.android.tools.lint.detector.api.Location;
import com.android.tools.lint.detector.api.Project;
import com.android.tools.lint.detector.api.Scope;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.eclipse.jdt.core.compiler.CategorizedProblem;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.Compiler;
import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy;
import org.eclipse.jdt.internal.compiler.IProblemFactory;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration;
import org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer;
import org.eclipse.jdt.internal.compiler.ast.CharLiteral;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.DoubleLiteral;
import org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.FalseLiteral;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.FloatLiteral;
import org.eclipse.jdt.internal.compiler.ast.IntLiteral;
import org.eclipse.jdt.internal.compiler.ast.Literal;
import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
import org.eclipse.jdt.internal.compiler.ast.LongLiteral;
import org.eclipse.jdt.internal.compiler.ast.MagicLiteral;
import org.eclipse.jdt.internal.compiler.ast.MemberValuePair;
import org.eclipse.jdt.internal.compiler.ast.MessageSend;
import org.eclipse.jdt.internal.compiler.ast.NameReference;
import org.eclipse.jdt.internal.compiler.ast.NullLiteral;
import org.eclipse.jdt.internal.compiler.ast.NumberLiteral;
import org.eclipse.jdt.internal.compiler.ast.StringLiteral;
import org.eclipse.jdt.internal.compiler.ast.TrueLiteral;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.batch.CompilationUnit;
import org.eclipse.jdt.internal.compiler.batch.FileSystem;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
import org.eclipse.jdt.internal.compiler.impl.BooleanConstant;
import org.eclipse.jdt.internal.compiler.impl.ByteConstant;
import org.eclipse.jdt.internal.compiler.impl.CharConstant;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.impl.DoubleConstant;
import org.eclipse.jdt.internal.compiler.impl.FloatConstant;
import org.eclipse.jdt.internal.compiler.impl.IntConstant;
import org.eclipse.jdt.internal.compiler.impl.LongConstant;
import org.eclipse.jdt.internal.compiler.impl.ShortConstant;
import org.eclipse.jdt.internal.compiler.impl.StringConstant;
import org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding;
import org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.eclipse.jdt.internal.compiler.lookup.ElementValuePair;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.NestedTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.PackageBinding;
import org.eclipse.jdt.internal.compiler.lookup.ProblemBinding;
import org.eclipse.jdt.internal.compiler.lookup.ProblemFieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.ProblemMethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.ProblemReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.jdt.internal.compiler.parser.Parser;
import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
import java.io.File;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import lombok.ast.Node;
import lombok.ast.VariableDeclaration;
import lombok.ast.VariableDefinition;
import lombok.ast.VariableDefinitionEntry;
import lombok.ast.ecj.EcjTreeConverter;
/**
* Java parser which uses ECJ for parsing and type attribution
*/
public class EcjParser extends JavaParser {
private static final boolean DEBUG_DUMP_PARSE_ERRORS = false;
private final LintClient mClient;
private final Project mProject;
private Map<File, ICompilationUnit> mSourceUnits;
private Map<ICompilationUnit, CompilationUnitDeclaration> mCompiled;
private Map<String, TypeDeclaration> mTypeUnits;
private Parser mParser;
private INameEnvironment mEnvironment;
public EcjParser(@NonNull LintCliClient client, @Nullable Project project) {
mClient = client;
mProject = project;
mParser = getParser();
}
/**
* Create the default compiler options
*/
public static CompilerOptions createCompilerOptions() {
CompilerOptions options = new CompilerOptions();
// Always using JDK 7 rather than basing it on project metadata since we
// don't do compilation error validation in lint (we leave that to the IDE's
// error parser or the command line build's compilation step); we want an
// AST that is as tolerant as possible.
long languageLevel = ClassFileConstants.JDK1_7;
options.complianceLevel = languageLevel;
options.sourceLevel = languageLevel;
options.targetJDK = languageLevel;
options.originalComplianceLevel = languageLevel;
options.originalSourceLevel = languageLevel;
options.inlineJsrBytecode = true; // >1.5
options.parseLiteralExpressionsAsConstants = true;
options.analyseResourceLeaks = false;
options.docCommentSupport = false;
options.defaultEncoding = UTF_8;
options.suppressOptionalErrors = true;
options.generateClassFiles = false;
options.isAnnotationBasedNullAnalysisEnabled = false;
options.reportUnusedDeclaredThrownExceptionExemptExceptionAndThrowable = false;
options.reportUnusedDeclaredThrownExceptionIncludeDocCommentReference = false;
options.reportUnusedDeclaredThrownExceptionWhenOverriding = false;
options.reportUnusedParameterIncludeDocCommentReference = false;
options.reportUnusedParameterWhenImplementingAbstract = false;
options.reportUnusedParameterWhenOverridingConcrete = false;
options.suppressWarnings = true;
options.processAnnotations = true;
options.storeAnnotations = true;
options.verbose = false;
return options;
}
public static long getLanguageLevel(int major, int minor) {
assert major == 1;
switch (minor) {
case 5: return ClassFileConstants.JDK1_5;
case 6: return ClassFileConstants.JDK1_6;
case 7:
default:
return ClassFileConstants.JDK1_7;
}
}
private Parser getParser() {
if (mParser == null) {
CompilerOptions options = createCompilerOptions();
ProblemReporter problemReporter = new ProblemReporter(
DefaultErrorHandlingPolicies.exitOnFirstError(),
options,
new DefaultProblemFactory());
mParser = new Parser(problemReporter,
options.parseLiteralExpressionsAsConstants);
mParser.javadocParser.checkDocComment = false;
}
return mParser;
}
@Override
public void prepareJavaParse(@NonNull final List<JavaContext> contexts) {
if (mProject == null || contexts.isEmpty()) {
return;
}
List<ICompilationUnit> sources = Lists.newArrayListWithExpectedSize(contexts.size());
mSourceUnits = Maps.newHashMapWithExpectedSize(sources.size());
for (JavaContext context : contexts) {
String contents = context.getContents();
if (contents == null) {
continue;
}
File file = context.file;
CompilationUnit unit = new CompilationUnit(contents.toCharArray(), file.getPath(),
UTF_8);
sources.add(unit);
mSourceUnits.put(file, unit);
}
List<String> classPath = computeClassPath(contexts);
mCompiled = Maps.newHashMapWithExpectedSize(mSourceUnits.size());
try {
mEnvironment = parse(createCompilerOptions(), sources, classPath, mCompiled, mClient);
} catch (Throwable t) {
mClient.log(t, "ECJ compiler crashed");
}
if (DEBUG_DUMP_PARSE_ERRORS) {
for (CompilationUnitDeclaration unit : mCompiled.values()) {
// so maybe I don't need my map!!
CategorizedProblem[] problems = unit.compilationResult()
.getAllProblems();
if (problems != null) {
for (IProblem problem : problems) {
if (problem == null || !problem.isError()) {
continue;
}
System.out.println(
new String(problem.getOriginatingFileName()) + ":"
+ (problem.isError() ? "Error" : "Warning") + ": "
+ problem.getSourceLineNumber() + ": " + problem.getMessage());
}
}
}
}
}
/** Parse the given source units and class path and store it into the given output map */
public static INameEnvironment parse(
CompilerOptions options,
@NonNull List<ICompilationUnit> sourceUnits,
@NonNull List<String> classPath,
@NonNull Map<ICompilationUnit, CompilationUnitDeclaration> outputMap,
@Nullable LintClient client) {
INameEnvironment environment = new FileSystem(
classPath.toArray(new String[classPath.size()]), new String[0],
options.defaultEncoding);
IErrorHandlingPolicy policy = DefaultErrorHandlingPolicies.proceedWithAllProblems();
IProblemFactory problemFactory = new DefaultProblemFactory(Locale.getDefault());
ICompilerRequestor requestor = new ICompilerRequestor() {
@Override
public void acceptResult(CompilationResult result) {
// Not used; we need the corresponding CompilationUnitDeclaration for the source
// units (the AST parsed from source) which we don't get access to here, so we
// instead subclass AST to get our hands on them.
}
};
NonGeneratingCompiler compiler = new NonGeneratingCompiler(environment, policy, options,
requestor, problemFactory, outputMap);
try {
compiler.compile(sourceUnits.toArray(new ICompilationUnit[sourceUnits.size()]));
} catch (OutOfMemoryError e) {
environment.cleanup();
// Since we're running out of memory, if it's all still held we could potentially
// fail attempting to log the failure. Actively get rid of the large ECJ data
// structure references first so minimize the chance of that
//noinspection UnusedAssignment
compiler = null;
//noinspection UnusedAssignment
environment = null;
//noinspection UnusedAssignment
requestor = null;
//noinspection UnusedAssignment
problemFactory = null;
//noinspection UnusedAssignment
policy = null;
String msg = "Ran out of memory analyzing .java sources with ECJ: Some lint checks "
+ "may not be accurate (missing type information from the compiler)";
if (client != null) {
// Don't log exception too; this isn't a compiler error per se where we
// need to pin point the exact unlucky code that asked for memory when it
// had already run out
client.log(null, msg);
} else {
System.out.println(msg);
}
} catch (Throwable t) {
if (client != null) {
CompilationUnitDeclaration currentUnit = compiler.getCurrentUnit();
if (currentUnit == null || currentUnit.getFileName() == null) {
client.log(t, "ECJ compiler crashed");
} else {
client.log(t, "ECJ compiler crashed processing %1$s",
new String(currentUnit.getFileName()));
}
} else {
t.printStackTrace();
}
environment.cleanup();
environment = null;
}
return environment;
}
@NonNull
private List<String> computeClassPath(@NonNull List<JavaContext> contexts) {
assert mProject != null;
List<String> classPath = Lists.newArrayList();
IAndroidTarget compileTarget = mProject.getBuildTarget();
if (compileTarget != null) {
String androidJar = compileTarget.getPath(IAndroidTarget.ANDROID_JAR);
if (androidJar != null && new File(androidJar).exists()) {
classPath.add(androidJar);
}
}
Set<File> libraries = Sets.newHashSet();
Set<String> names = Sets.newHashSet();
for (File library : mProject.getJavaLibraries()) {
libraries.add(library);
names.add(getLibraryName(library));
}
for (Project project : mProject.getAllLibraries()) {
for (File library : project.getJavaLibraries()) {
String name = getLibraryName(library);
// Avoid pulling in android-support-v4.jar from libraries etc
// since we're pointing to the local copies rather than the real
// maven/gradle source copies
if (!names.contains(name)) {
libraries.add(library);
names.add(name);
}
}
}
for (File file : libraries) {
if (file.exists()) {
classPath.add(file.getPath());
}
}
// In incremental mode we may need to point to other sources in the project
// for type resolution
EnumSet<Scope> scope = contexts.get(0).getScope();
if (!scope.contains(Scope.ALL_JAVA_FILES)) {
// May need other compiled classes too
for (File dir : mProject.getJavaClassFolders()) {
if (dir.exists()) {
classPath.add(dir.getPath());
}
}
}
return classPath;
}
@NonNull
private static String getLibraryName(@NonNull File library) {
String name = library.getName();
if (name.equals(SdkConstants.FN_CLASSES_JAR)) {
// For AAR artifacts they'll all clash with "classes.jar"; include more unique
// context
String path = library.getPath();
int index = path.indexOf("exploded-aar");
if (index != -1) {
return path.substring(index);
} else {
index = path.indexOf("exploded-bundles");
if (index != -1) {
return path.substring(index);
}
}
File parent = library.getParentFile();
if (parent != null) {
return parent.getName() + File.separatorChar + name;
}
}
return name;
}
@Override
public Node parseJava(@NonNull JavaContext context) {
String code = context.getContents();
if (code == null) {
return null;
}
CompilationUnitDeclaration unit = getParsedUnit(context, code);
try {
EcjTreeConverter converter = new EcjTreeConverter();
converter.visit(code, unit);
List<? extends Node> nodes = converter.getAll();
if (nodes != null) {
// There could be more than one node when there are errors; pick out the
// compilation unit node
for (Node node : nodes) {
if (node instanceof lombok.ast.CompilationUnit) {
return node;
}
}
}
return null;
} catch (Throwable t) {
mClient.log(t, "Failed converting ECJ parse tree to Lombok for file %1$s",
context.file.getPath());
return null;
}
}
@Nullable
private CompilationUnitDeclaration getParsedUnit(
@NonNull JavaContext context,
@NonNull String code) {
ICompilationUnit sourceUnit = null;
if (mSourceUnits != null && mCompiled != null) {
sourceUnit = mSourceUnits.get(context.file);
if (sourceUnit != null) {
CompilationUnitDeclaration unit = mCompiled.get(sourceUnit);
if (unit != null) {
return unit;
}
}
}
if (sourceUnit == null) {
sourceUnit = new CompilationUnit(code.toCharArray(), context.file.getName(), UTF_8);
}
try {
CompilationResult compilationResult = new CompilationResult(sourceUnit, 0, 0, 0);
return getParser().parse(sourceUnit, compilationResult);
} catch (AbortCompilation e) {
// No need to report Java parsing errors while running in Eclipse.
// Eclipse itself will already provide problem markers for these files,
// so all this achieves is creating "multiple annotations on this line"
// tooltips instead.
return null;
}
}
@NonNull
@Override
public Location getLocation(@NonNull JavaContext context, @NonNull Node node) {
lombok.ast.Position position = node.getPosition();
return Location.create(context.file, context.getContents(),
position.getStart(), position.getEnd());
}
@NonNull
@Override
public
Location.Handle createLocationHandle(@NonNull JavaContext context, @NonNull Node node) {
return new LocationHandle(context.file, node);
}
@Override
public void dispose(@NonNull JavaContext context,
@NonNull Node compilationUnit) {
if (mSourceUnits != null && mCompiled != null) {
ICompilationUnit sourceUnit = mSourceUnits.get(context.file);
if (sourceUnit != null) {
mSourceUnits.remove(context.file);
mCompiled.remove(sourceUnit);
}
}
}
@Override
public void dispose() {
if (mEnvironment != null) {
mEnvironment.cleanup();
mEnvironment = null;
}
}
@Nullable
private static Object getNativeNode(@NonNull Node node) {
Object nativeNode = node.getNativeNode();
if (nativeNode != null) {
return nativeNode;
}
Node parent = node.getParent();
// The ECJ native nodes are sometimes spotty; for example, for a
// MethodInvocation node we can have a null native node, but its
// parent expression statement will point to the real MessageSend node
if (parent != null) {
nativeNode = parent.getNativeNode();
if (nativeNode != null) {
return nativeNode;
}
}
if (node instanceof VariableDefinitionEntry) {
node = node.getParent().getParent();
}
if (node instanceof VariableDeclaration) {
VariableDeclaration declaration = (VariableDeclaration) node;
VariableDefinition definition = declaration.astDefinition();
if (definition != null) {
lombok.ast.TypeReference typeReference = definition.astTypeReference();
if (typeReference != null) {
return typeReference.getNativeNode();
}
}
}
return null;
}
@Override
@Nullable
public ResolvedNode resolve(@NonNull JavaContext context, @NonNull Node node) {
Object nativeNode = getNativeNode(node);
if (nativeNode == null) {
return null;
}
if (nativeNode instanceof NameReference) {
return resolve(((NameReference) nativeNode).binding);
} else if (nativeNode instanceof TypeReference) {
return resolve(((TypeReference) nativeNode).resolvedType);
} else if (nativeNode instanceof MessageSend) {
return resolve(((MessageSend) nativeNode).binding);
} else if (nativeNode instanceof AllocationExpression) {
return resolve(((AllocationExpression) nativeNode).binding);
} else if (nativeNode instanceof TypeDeclaration) {
return resolve(((TypeDeclaration) nativeNode).binding);
} else if (nativeNode instanceof ExplicitConstructorCall) {
return resolve(((ExplicitConstructorCall) nativeNode).binding);
} else if (nativeNode instanceof Annotation) {
AnnotationBinding compilerAnnotation =
((Annotation) nativeNode).getCompilerAnnotation();
if (compilerAnnotation != null) {
return new EcjResolvedAnnotation(compilerAnnotation);
}
return resolve(((Annotation) nativeNode).resolvedType);
} else if (nativeNode instanceof AbstractMethodDeclaration) {
return resolve(((AbstractMethodDeclaration) nativeNode).binding);
} else if (nativeNode instanceof AbstractVariableDeclaration) {
if (nativeNode instanceof LocalDeclaration) {
return resolve(((LocalDeclaration) nativeNode).binding);
} else if (nativeNode instanceof FieldDeclaration) {
FieldDeclaration fieldDeclaration = (FieldDeclaration) nativeNode;
if (fieldDeclaration.initialization instanceof AllocationExpression) {
AllocationExpression allocation =
(AllocationExpression)fieldDeclaration.initialization;
if (allocation.binding != null) {
// Field constructor call: this is an enum constant.
return new EcjResolvedMethod(allocation.binding);
}
}
return resolve(fieldDeclaration.binding);
}
}
// TODO: Handle org.eclipse.jdt.internal.compiler.ast.SuperReference. It
// doesn't contain an actual method binding; the parent node call should contain
// it, but is missing a native node reference; investigate the ECJ bridge's super
// handling.
return null;
}
private ResolvedNode resolve(@Nullable Binding binding) {
if (binding == null || binding instanceof ProblemBinding) {
return null;
}
if (binding instanceof TypeBinding) {
TypeBinding tb = (TypeBinding) binding;
return new EcjResolvedClass(tb);
} else if (binding instanceof MethodBinding) {
MethodBinding mb = (MethodBinding) binding;
if (mb instanceof ProblemMethodBinding) {
return null;
}
//noinspection VariableNotUsedInsideIf
if (mb.declaringClass != null) {
return new EcjResolvedMethod(mb);
}
} else if (binding instanceof LocalVariableBinding) {
LocalVariableBinding lvb = (LocalVariableBinding) binding;
//noinspection VariableNotUsedInsideIf
if (lvb.type != null) {
return new EcjResolvedVariable(lvb);
}
} else if (binding instanceof FieldBinding) {
FieldBinding fb = (FieldBinding) binding;
if (fb instanceof ProblemFieldBinding) {
return null;
}
if (fb.type != null && fb.declaringClass != null) {
return new EcjResolvedField(fb);
}
}
return null;
}
private TypeDeclaration findTypeDeclaration(@NonNull String signature) {
if (mTypeUnits == null) {
mTypeUnits = Maps.newHashMapWithExpectedSize(mCompiled.size());
for (CompilationUnitDeclaration unit : mCompiled.values()) {
if (unit.types != null) {
for (TypeDeclaration typeDeclaration : unit.types) {
addTypeDeclaration(typeDeclaration);
}
}
}
}
return mTypeUnits.get(signature);
}
private void addTypeDeclaration(TypeDeclaration typeDeclaration) {
String type = new String(typeDeclaration.binding.readableName());
mTypeUnits.put(type, typeDeclaration);
// Recurse on member types
if (typeDeclaration.memberTypes != null) {
for (TypeDeclaration member : typeDeclaration.memberTypes) {
addTypeDeclaration(member);
}
}
}
@Override
@Nullable
public TypeDescriptor getType(@NonNull JavaContext context, @NonNull Node node) {
Object nativeNode = getNativeNode(node);
if (nativeNode == null) {
return null;
}
if (nativeNode instanceof MessageSend) {
nativeNode = ((MessageSend)nativeNode).binding;
} else if (nativeNode instanceof AllocationExpression) {
nativeNode = ((AllocationExpression)nativeNode).resolvedType;
} else if (nativeNode instanceof NameReference) {
nativeNode = ((NameReference)nativeNode).resolvedType;
} else if (nativeNode instanceof Expression) {
if (nativeNode instanceof Literal) {
if (nativeNode instanceof StringLiteral) {
return getTypeDescriptor(TYPE_STRING);
} else if (nativeNode instanceof NumberLiteral) {
if (nativeNode instanceof IntLiteral) {
return getTypeDescriptor(TYPE_INT);
} else if (nativeNode instanceof LongLiteral) {
return getTypeDescriptor(TYPE_LONG);
} else if (nativeNode instanceof CharLiteral) {
return getTypeDescriptor(TYPE_CHAR);
} else if (nativeNode instanceof FloatLiteral) {
return getTypeDescriptor(TYPE_FLOAT);
} else if (nativeNode instanceof DoubleLiteral) {
return getTypeDescriptor(TYPE_DOUBLE);
}
} else if (nativeNode instanceof MagicLiteral) {
if (nativeNode instanceof TrueLiteral || nativeNode instanceof FalseLiteral) {
return getTypeDescriptor(TYPE_BOOLEAN);
} else if (nativeNode instanceof NullLiteral) {
return getTypeDescriptor(TYPE_NULL);
}
}
}
nativeNode = ((Expression)nativeNode).resolvedType;
} else if (nativeNode instanceof TypeDeclaration) {
nativeNode = ((TypeDeclaration) nativeNode).binding;
} else if (nativeNode instanceof AbstractMethodDeclaration) {
nativeNode = ((AbstractMethodDeclaration) nativeNode).binding;
}
if (nativeNode instanceof Binding) {
Binding binding = (Binding) nativeNode;
if (binding instanceof TypeBinding) {
TypeBinding tb = (TypeBinding) binding;
return getTypeDescriptor(tb);
} else if (binding instanceof LocalVariableBinding) {
LocalVariableBinding lvb = (LocalVariableBinding) binding;
if (lvb.type != null) {
return getTypeDescriptor(lvb.type);
}
} else if (binding instanceof FieldBinding) {
FieldBinding fb = (FieldBinding) binding;
if (fb.type != null) {
return getTypeDescriptor(fb.type);
}
} else if (binding instanceof MethodBinding) {
return getTypeDescriptor(((MethodBinding) binding).returnType);
} else if (binding instanceof ProblemBinding) {
// Unresolved type. We just don't know.
return null;
}
}
return null;
}
@Nullable
@Override
public ResolvedClass findClass(@NonNull JavaContext context,
@NonNull String fullyQualifiedName) {
Node compilationUnit = context.getCompilationUnit();
if (compilationUnit == null) {
return null;
}
Object nativeObj = getNativeNode(compilationUnit);
if (!(nativeObj instanceof CompilationUnitDeclaration)) {
return null;
}
CompilationUnitDeclaration ecjUnit = (CompilationUnitDeclaration) nativeObj;
// Convert "foo.bar.Baz" into char[][] 'foo','bar','Baz' as required for
// ECJ name lookup
List<char[]> arrays = Lists.newArrayList();
for (String segment : Splitter.on('.').split(fullyQualifiedName)) {
arrays.add(segment.toCharArray());
}
char[][] compoundName = new char[arrays.size()][];
for (int i = 0, n = arrays.size(); i < n; i++) {
compoundName[i] = arrays.get(i);
}
Binding typeOrPackage = ecjUnit.scope.getTypeOrPackage(compoundName);
if (typeOrPackage instanceof TypeBinding && !(typeOrPackage instanceof ProblemReferenceBinding)) {
return new EcjResolvedClass((TypeBinding)typeOrPackage);
}
return null;
}
@Nullable
private TypeDescriptor getTypeDescriptor(@Nullable TypeBinding resolvedType) {
if (resolvedType == null) {
return null;
}
return new EcjTypeDescriptor(resolvedType);
}
private static TypeDescriptor getTypeDescriptor(String fqn) {
return new DefaultTypeDescriptor(fqn);
}
/** Computes the super method, if any, given a method binding */
private static MethodBinding findSuperMethodBinding(@NonNull MethodBinding binding) {
try {
ReferenceBinding superclass = binding.declaringClass.superclass();
while (superclass != null) {
MethodBinding[] methods = superclass.getMethods(binding.selector,
binding.parameters.length);
for (MethodBinding method : methods) {
if (method.areParameterErasuresEqual(binding)) {
return method;
}
}
superclass = superclass.superclass();
}
} catch (Exception ignore) {
// Work around ECJ bugs; see https://code.google.com/p/android/issues/detail?id=172268
}
return null;
}
@NonNull
private static Collection<ResolvedAnnotation> merge(
@Nullable Collection<ResolvedAnnotation> first,
@Nullable Collection<ResolvedAnnotation> second) {
if (first == null || first.isEmpty()) {
if (second == null) {
return Collections.emptyList();
} else {
return second;
}
} else if (second == null || second.isEmpty()) {
return first;
} else {
int size = first.size() + second.size();
List<ResolvedAnnotation> merged = Lists.newArrayListWithExpectedSize(size);
merged.addAll(first);
merged.addAll(second);
return merged;
}
}
/* Handle for creating positions cheaply and returning full fledged locations later */
private static class LocationHandle implements Location.Handle {
private File mFile;
private Node mNode;
private Object mClientData;
public LocationHandle(File file, Node node) {
mFile = file;
mNode = node;
}
@NonNull
@Override
public Location resolve() {
lombok.ast.Position pos = mNode.getPosition();
return Location.create(mFile, null /*contents*/, pos.getStart(), pos.getEnd());
}
@Override
public void setClientData(@Nullable Object clientData) {
mClientData = clientData;
}
@Override
@Nullable
public Object getClientData() {
return mClientData;
}
}
// Custom version of the compiler which skips code generation and records source units
private static class NonGeneratingCompiler extends Compiler {
private Map<ICompilationUnit, CompilationUnitDeclaration> mUnits;
private CompilationUnitDeclaration mCurrentUnit;
public NonGeneratingCompiler(INameEnvironment environment, IErrorHandlingPolicy policy,
CompilerOptions options, ICompilerRequestor requestor,
IProblemFactory problemFactory,
Map<ICompilationUnit, CompilationUnitDeclaration> units) {
super(environment, policy, options, requestor, problemFactory, null, null);
mUnits = units;
}
@Nullable
CompilationUnitDeclaration getCurrentUnit() {
// Can't use lookupEnvironment.unitBeingCompleted directly; it gets nulled out
// as part of the exception catch handling in the compiler before this method
// is called from lint -- therefore we stash a copy in our own mCurrentUnit field
return mCurrentUnit;
}
@Override
protected synchronized void addCompilationUnit(ICompilationUnit sourceUnit,
CompilationUnitDeclaration parsedUnit) {
super.addCompilationUnit(sourceUnit, parsedUnit);
mUnits.put(sourceUnit, parsedUnit);
}
@Override
public void process(CompilationUnitDeclaration unit, int unitNumber) {
mCurrentUnit = lookupEnvironment.unitBeingCompleted = unit;
parser.getMethodBodies(unit);
if (unit.scope != null) {
unit.scope.faultInTypes();
unit.scope.verifyMethods(lookupEnvironment.methodVerifier());
}
unit.resolve();
unit.analyseCode();
// This is where we differ from super: DON'T call generateCode().
// Sadly we can't just set ignoreMethodBodies=true to have the same effect,
// since that would also skip the analyseCode call, which we DO, want:
// unit.generateCode();
if (options.produceReferenceInfo && unit.scope != null) {
unit.scope.storeDependencyInfo();
}
unit.finalizeProblems();
unit.compilationResult.totalUnitsKnown = totalUnits;
lookupEnvironment.unitBeingCompleted = null;
}
}
private class EcjTypeDescriptor extends TypeDescriptor {
private final TypeBinding mBinding;
private EcjTypeDescriptor(@NonNull TypeBinding binding) {
mBinding = binding;
}
@NonNull
@Override
public String getName() {
return new String(mBinding.readableName());
}
@Override
public boolean matchesName(@NonNull String name) {
return sameChars(name, mBinding.readableName());
}
@Override
public boolean matchesSignature(@NonNull String signature) {
return sameChars(signature, mBinding.readableName());
}
@NonNull
@Override
public String getSignature() {
return getName();
}
@Override
@Nullable
public ResolvedClass getTypeClass() {
if (!mBinding.isPrimitiveType()) {
return new EcjResolvedClass(mBinding);
}
return null;
}
@SuppressWarnings("RedundantIfStatement")
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
EcjTypeDescriptor that = (EcjTypeDescriptor) o;
if (!mBinding.equals(that.mBinding)) {
return false;
}
return true;
}
@Override
public int hashCode() {
return mBinding.hashCode();
}
}
private class EcjResolvedMethod extends ResolvedMethod {
private MethodBinding mBinding;
private EcjResolvedMethod(MethodBinding binding) {
mBinding = binding;
assert mBinding.declaringClass != null;
}
@NonNull
@Override
public String getName() {
char[] c = isConstructor() ? mBinding.declaringClass.readableName() : mBinding.selector;
return new String(c);
}
@Override
public boolean matches(@NonNull String name) {
char[] c = isConstructor() ? mBinding.declaringClass.readableName() : mBinding.selector;
return sameChars(name, c);
}
@NonNull
@Override
public ResolvedClass getContainingClass() {
return new EcjResolvedClass(mBinding.declaringClass);
}
@Override
public int getArgumentCount() {
return mBinding.parameters != null ? mBinding.parameters.length : 0;
}
@NonNull
@Override
public TypeDescriptor getArgumentType(int index) {
TypeBinding parameterType = mBinding.parameters[index];
TypeDescriptor typeDescriptor = getTypeDescriptor(parameterType);
assert typeDescriptor != null; // because parameter is not null
return typeDescriptor;
}
@Override
public boolean argumentMatchesType(int index, @NonNull String signature) {
return sameChars(signature, mBinding.parameters[index].readableName());
}
@Nullable
@Override
public TypeDescriptor getReturnType() {
return isConstructor() ? null : getTypeDescriptor(mBinding.returnType);
}
@Override
public boolean isConstructor() {
return mBinding.isConstructor();
}
@Override
@Nullable
public ResolvedMethod getSuperMethod() {
MethodBinding superBinding = findSuperMethodBinding(mBinding);
if (superBinding != null) {
return new EcjResolvedMethod(superBinding);
}
return null;
}
@NonNull
@Override
public Iterable<ResolvedAnnotation> getAnnotations() {
List<ResolvedAnnotation> all = Lists.newArrayListWithExpectedSize(4);
ExternalAnnotationRepository manager = ExternalAnnotationRepository.get(mClient);
MethodBinding binding = this.mBinding;
while (binding != null) {
AnnotationBinding[] annotations = binding.getAnnotations();
int count = annotations.length;
if (count > 0) {
for (AnnotationBinding annotation : annotations) {
if (annotation != null) {
all.add(new EcjResolvedAnnotation(annotation));
}
}
}
// Look for external annotations
Collection<ResolvedAnnotation> external = manager.getAnnotations(
new EcjResolvedMethod(binding));
if (external != null) {
all.addAll(external);
}
binding = findSuperMethodBinding(binding);
}
return all;
}
@NonNull
@Override
public Iterable<ResolvedAnnotation> getParameterAnnotations(int index) {
List<ResolvedAnnotation> all = Lists.newArrayListWithExpectedSize(4);
ExternalAnnotationRepository manager = ExternalAnnotationRepository.get(mClient);
MethodBinding binding = this.mBinding;
while (binding != null) {
AnnotationBinding[][] parameterAnnotations = binding.getParameterAnnotations();
if (parameterAnnotations != null &&
index >= 0 && index < parameterAnnotations.length) {
AnnotationBinding[] annotations = parameterAnnotations[index];
int count = annotations.length;
if (count > 0) {
for (AnnotationBinding annotation : annotations) {
if (annotation != null) {
all.add(new EcjResolvedAnnotation(annotation));
}
}
}
}
// Look for external annotations
Collection<ResolvedAnnotation> external = manager.getAnnotations(
new EcjResolvedMethod(binding), index);
if (external != null) {
all.addAll(external);
}
binding = findSuperMethodBinding(binding);
}
return all;
}
@Override
public int getModifiers() {
return mBinding.getAccessFlags();
}
@Override
public String getSignature() {
return mBinding.toString();
}
@Override
public boolean isInPackage(@NonNull String pkgName, boolean includeSubPackages) {
PackageBinding pkg = mBinding.declaringClass.getPackage();
if (pkg != null) {
return includeSubPackages ?
startsWithCompound(pkgName, pkg.compoundName) :
equalsCompound(pkgName, pkg.compoundName);
}
return false;
}
@SuppressWarnings("RedundantIfStatement")
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
EcjResolvedMethod that = (EcjResolvedMethod) o;
if (mBinding != null ? !mBinding.equals(that.mBinding) : that.mBinding != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
return mBinding != null ? mBinding.hashCode() : 0;
}
}
private class EcjResolvedClass extends ResolvedClass {
protected final TypeBinding mBinding;
private EcjResolvedClass(TypeBinding binding) {
mBinding = binding;
}
@NonNull
@Override
public String getName() {
String name = new String(mBinding.readableName());
if (name.indexOf('.') == -1 && mBinding.enclosingType() != null) {
return new String(mBinding.enclosingType().readableName()) + '.' +
name;
}
return name;
}
@NonNull
@Override
public String getSimpleName() {
return new String(mBinding.shortReadableName());
}
@Override
public boolean matches(@NonNull String name) {
return sameChars(name, mBinding.readableName());
}
@Nullable
@Override
public ResolvedClass getSuperClass() {
if (mBinding instanceof ReferenceBinding) {
ReferenceBinding refBinding = (ReferenceBinding) mBinding;
ReferenceBinding superClass = refBinding.superclass();
if (superClass != null) {
return new EcjResolvedClass(superClass);
}
}
return null;
}
@Nullable
@Override
public ResolvedClass getContainingClass() {
if (mBinding instanceof NestedTypeBinding) {
NestedTypeBinding ntb = (NestedTypeBinding) mBinding;
if (ntb.enclosingType != null) {
return new EcjResolvedClass(ntb.enclosingType);
}
}
return null;
}
@Override
public boolean isSubclassOf(@NonNull String name, boolean strict) {
if (mBinding instanceof ReferenceBinding) {
ReferenceBinding cls = (ReferenceBinding) mBinding;
if (strict) {
cls = cls.superclass();
}
for (; cls != null; cls = cls.superclass()) {
if (sameChars(name, cls.readableName())) {
return true;
}
}
}
return false;
}
@Override
@NonNull
public Iterable<ResolvedMethod> getConstructors() {
if (mBinding instanceof ReferenceBinding) {
ReferenceBinding cls = (ReferenceBinding) mBinding;
MethodBinding[] methods = cls.getMethods(TypeConstants.INIT);
if (methods != null) {
int count = methods.length;
List<ResolvedMethod> result = Lists.newArrayListWithExpectedSize(count);
for (MethodBinding method : methods) {
if (method.isConstructor()) {
result.add(new EcjResolvedMethod(method));
}
}
return result;
}
}
return Collections.emptyList();
}
@Override
@NonNull
public Iterable<ResolvedMethod> getMethods(@NonNull String name,
boolean includeInherited) {
return findMethods(name, includeInherited);
}
@Override
@NonNull
public Iterable<ResolvedMethod> getMethods(boolean includeInherited) {
return findMethods(null, includeInherited);
}
@NonNull
private Iterable<ResolvedMethod> findMethods(@Nullable String name,
boolean includeInherited) {
if (mBinding instanceof ReferenceBinding) {
ReferenceBinding cls = (ReferenceBinding) mBinding;
if (includeInherited) {
List<ResolvedMethod> result = null;
while (cls != null) {
MethodBinding[] methods =
name != null ? cls.getMethods(name.toCharArray()) : cls.methods();
if (methods != null) {
int count = methods.length;
if (count > 0) {
if (result == null) {
result = Lists.newArrayListWithExpectedSize(count);
}
for (MethodBinding method : methods) {
if (!method.isConstructor()) {
// See if this method looks like it's masked
boolean masked = false;
for (ResolvedMethod m : result) {
MethodBinding mb = ((EcjResolvedMethod) m).mBinding;
if (mb.areParameterErasuresEqual(method)) {
masked = true;
break;
}
}
if (masked) {
continue;
}
result.add(new EcjResolvedMethod(method));
}
}
}
}
cls = cls.superclass();
}
return result != null ? result : Collections.<ResolvedMethod>emptyList();
} else {
MethodBinding[] methods =
name != null ? cls.getMethods(name.toCharArray()) : cls.methods();
if (methods != null) {
int count = methods.length;
List<ResolvedMethod> result = Lists.newArrayListWithExpectedSize(count);
for (MethodBinding method : methods) {
if (!method.isConstructor()) {
result.add(new EcjResolvedMethod(method));
}
}
return result;
}
}
}
return Collections.emptyList();
}
@NonNull
@Override
public Iterable<ResolvedAnnotation> getAnnotations() {
List<ResolvedAnnotation> all = Lists.newArrayListWithExpectedSize(2);
ExternalAnnotationRepository manager = ExternalAnnotationRepository.get(mClient);
if (mBinding instanceof ReferenceBinding) {
ReferenceBinding cls = (ReferenceBinding) mBinding;
while (cls != null) {
AnnotationBinding[] annotations = cls.getAnnotations();
int count = annotations.length;
if (count > 0) {
all = Lists.newArrayListWithExpectedSize(count);
for (AnnotationBinding annotation : annotations) {
if (annotation != null) {
all.add(new EcjResolvedAnnotation(annotation));
}
}
}
// Look for external annotations
Collection<ResolvedAnnotation> external = manager.getAnnotations(
new EcjResolvedClass(cls));
if (external != null) {
all.addAll(external);
}
cls = cls.superclass();
}
} else {
Collection<ResolvedAnnotation> external = manager.getAnnotations(this);
if (external != null) {
all.addAll(external);
}
}
return all;
}
@NonNull
@Override
public Iterable<ResolvedField> getFields(boolean includeInherited) {
if (mBinding instanceof ReferenceBinding) {
ReferenceBinding cls = (ReferenceBinding) mBinding;
if (includeInherited) {
List<ResolvedField> result = null;
while (cls != null) {
FieldBinding[] fields = cls.fields();
if (fields != null) {
int count = fields.length;
if (count > 0) {
if (result == null) {
result = Lists.newArrayListWithExpectedSize(count);
}
for (FieldBinding field : fields) {
// See if this field looks like it's masked
boolean masked = false;
for (ResolvedField f : result) {
FieldBinding mb = ((EcjResolvedField) f).mBinding;
if (Arrays.equals(mb.readableName(),
field.readableName())) {
masked = true;
break;
}
}
if (masked) {
continue;
}
result.add(new EcjResolvedField(field));
}
}
}
cls = cls.superclass();
}
return result != null ? result : Collections.<ResolvedField>emptyList();
} else {
FieldBinding[] fields = cls.fields();
if (fields != null) {
int count = fields.length;
List<ResolvedField> result = Lists.newArrayListWithExpectedSize(count);
for (FieldBinding field : fields) {
result.add(new EcjResolvedField(field));
}
return result;
}
}
}
return Collections.emptyList();
}
@Override
@Nullable
public ResolvedField getField(@NonNull String name, boolean includeInherited) {
if (mBinding instanceof ReferenceBinding) {
ReferenceBinding cls = (ReferenceBinding) mBinding;
while (cls != null) {
FieldBinding[] fields = cls.fields();
if (fields != null) {
for (FieldBinding field : fields) {
if (sameChars(name, field.name)) {
return new EcjResolvedField(field);
}
}
}
if (includeInherited) {
cls = cls.superclass();
} else {
break;
}
}
}
return null;
}
@Nullable
@Override
public ResolvedPackage getPackage() {
return new EcjResolvedPackage(mBinding.getPackage());
}
@Override
public int getModifiers() {
if (mBinding instanceof ReferenceBinding) {
ReferenceBinding cls = (ReferenceBinding) mBinding;
// These constants from ClassFileConstants luckily agree with the Modifier
// constants in the low bits we care about (public, abstract, static, etc)
return cls.getAccessFlags();
}
return 0;
}
@Override
public String getSignature() {
return getName();
}
@Override
public boolean isInPackage(@NonNull String pkgName, boolean includeSubPackages) {
PackageBinding pkg = mBinding.getPackage();
if (pkg != null) {
return includeSubPackages ?
startsWithCompound(pkgName, pkg.compoundName) :
equalsCompound(pkgName, pkg.compoundName);
}
return false;
}
@SuppressWarnings("RedundantIfStatement")
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
EcjResolvedClass that = (EcjResolvedClass) o;
if (mBinding != null ? !mBinding.equals(that.mBinding) : that.mBinding != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
return mBinding != null ? mBinding.hashCode() : 0;
}
}
// "package-info" as a char
private static final char[] PACKAGE_INFO_CHARS = new char[] {
'p', 'a', 'c', 'k', 'a', 'g', 'e', '-', 'i', 'n', 'f', 'o'
};
private class EcjResolvedPackage extends ResolvedPackage {
private final PackageBinding mBinding;
public EcjResolvedPackage(PackageBinding binding) {
mBinding = binding;
}
@NonNull
@Override
public String getName() {
return new String(mBinding.readableName());
}
@Override
public String getSignature() {
return getName();
}
@NonNull
@Override
public Iterable<ResolvedAnnotation> getAnnotations() {
List<ResolvedAnnotation> all = Lists.newArrayListWithExpectedSize(2);
AnnotationBinding[] annotations = mBinding.getAnnotations();
int count = annotations.length;
if (count == 0) {
Binding pkgInfo = mBinding.getTypeOrPackage(PACKAGE_INFO_CHARS);
if (pkgInfo != null) {
annotations = pkgInfo.getAnnotations();
}
count = annotations.length;
}
if (count > 0) {
for (AnnotationBinding annotation : annotations) {
if (annotation != null) {
all.add(new EcjResolvedAnnotation(annotation));
}
}
}
// Merge external annotations
ExternalAnnotationRepository manager = ExternalAnnotationRepository.get(mClient);
Collection<ResolvedAnnotation> external = manager.getAnnotations(this);
if (external != null) {
all.addAll(external);
}
return all;
}
@Override
public int getModifiers() {
return 0;
}
}
private class EcjResolvedField extends ResolvedField {
private FieldBinding mBinding;
private EcjResolvedField(FieldBinding binding) {
mBinding = binding;
}
@NonNull
@Override
public String getName() {
return new String(mBinding.readableName());
}
@Override
public boolean matches(@NonNull String name) {
return sameChars(name, mBinding.readableName());
}
@NonNull
@Override
public TypeDescriptor getType() {
TypeDescriptor typeDescriptor = getTypeDescriptor(mBinding.type);
assert typeDescriptor != null; // because mBinding.type is known not to be null
return typeDescriptor;
}
@NonNull
@Override
public ResolvedClass getContainingClass() {
return new EcjResolvedClass(mBinding.declaringClass);
}
@Nullable
@Override
public Object getValue() {
return getConstantValue(mBinding.constant());
}
@NonNull
@Override
public Iterable<ResolvedAnnotation> getAnnotations() {
List<ResolvedAnnotation> compiled = null;
AnnotationBinding[] annotations = mBinding.getAnnotations();
int count = annotations.length;
if (count > 0) {
compiled = Lists.newArrayListWithExpectedSize(count);
for (AnnotationBinding annotation : annotations) {
if (annotation != null) {
compiled.add(new EcjResolvedAnnotation(annotation));
}
}
}
// Look for external annotations
ExternalAnnotationRepository manager = ExternalAnnotationRepository.get(mClient);
Collection<ResolvedAnnotation> external = manager.getAnnotations(this);
return merge(compiled, external);
}
@Override
public int getModifiers() {
return mBinding.getAccessFlags();
}
@Override
public String getSignature() {
return mBinding.toString();
}
@Override
public boolean isInPackage(@NonNull String pkgName, boolean includeSubPackages) {
PackageBinding pkg = mBinding.declaringClass.getPackage();
if (pkg != null) {
return includeSubPackages ?
startsWithCompound(pkgName, pkg.compoundName) :
equalsCompound(pkgName, pkg.compoundName);
}
return false;
}
@SuppressWarnings("RedundantIfStatement")
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
EcjResolvedField that = (EcjResolvedField) o;
if (mBinding != null ? !mBinding.equals(that.mBinding) : that.mBinding != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
return mBinding != null ? mBinding.hashCode() : 0;
}
}
private class EcjResolvedVariable extends ResolvedVariable {
private LocalVariableBinding mBinding;
private EcjResolvedVariable(LocalVariableBinding binding) {
mBinding = binding;
}
@NonNull
@Override
public String getName() {
return new String(mBinding.readableName());
}
@Override
public boolean matches(@NonNull String name) {
return sameChars(name, mBinding.readableName());
}
@NonNull
@Override
public TypeDescriptor getType() {
TypeDescriptor typeDescriptor = getTypeDescriptor(mBinding.type);
assert typeDescriptor != null; // because mBinding.type is known not to be null
return typeDescriptor;
}
@Override
public int getModifiers() {
return mBinding.modifiers;
}
@NonNull
@Override
public Iterable<ResolvedAnnotation> getAnnotations() {
AnnotationBinding[] annotations = mBinding.getAnnotations();
int count = annotations.length;
if (count > 0) {
List<ResolvedAnnotation> result = Lists.newArrayListWithExpectedSize(count);
for (AnnotationBinding annotation : annotations) {
if (annotation != null) {
result.add(new EcjResolvedAnnotation(annotation));
}
}
return result;
}
// No external annotations for variables
return Collections.emptyList();
}
@Override
public String getSignature() {
return mBinding.toString();
}
@SuppressWarnings("RedundantIfStatement")
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
EcjResolvedVariable that = (EcjResolvedVariable) o;
if (mBinding != null ? !mBinding.equals(that.mBinding) : that.mBinding != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
return mBinding != null ? mBinding.hashCode() : 0;
}
}
private class EcjResolvedAnnotation extends ResolvedAnnotation {
private AnnotationBinding mBinding;
private EcjResolvedAnnotation(@NonNull final AnnotationBinding binding) {
mBinding = binding;
}
@NonNull
@Override
public String getName() {
return new String(mBinding.getAnnotationType().readableName());
}
@Override
public boolean matches(@NonNull String name) {
return sameChars(name, mBinding.getAnnotationType().readableName());
}
@NonNull
@Override
public TypeDescriptor getType() {
TypeDescriptor typeDescriptor = getTypeDescriptor(mBinding.getAnnotationType());
assert typeDescriptor != null; // because mBinding.type is known not to be null
return typeDescriptor;
}
@Override
public ResolvedClass getClassType() {
ReferenceBinding annotationType = mBinding.getAnnotationType();
return new EcjResolvedClass(annotationType) {
@NonNull
@Override
public Iterable<ResolvedAnnotation> getAnnotations() {
AnnotationBinding[] annotations = mBinding.getAnnotations();
int count = annotations.length;
if (count > 0) {
List<ResolvedAnnotation> result = Lists.newArrayListWithExpectedSize(count);
for (AnnotationBinding annotation : annotations) {
if (annotation != null) {
// Special case: If you look up the annotations *on* annotations,
// you're probably working with the typedef annotations, @IntDef
// and @StringDef. For these, we can't use the normal annotation
// handling, because the compiler only keeps the values of the
// constants, not the references to the constants which is what we
// care about for those annotations. So in this case, construct
// a special subclass of ResolvedAnnotation: EcjAstAnnotation, where
// we keep the AST node for the annotation definition such that
// we can look up the constant references themselves when queries
// via the annotation's getValue() lookup methods.
char[] readableName = annotation.getAnnotationType().readableName();
if (sameChars(INT_DEF_ANNOTATION, readableName)
|| sameChars(STRING_DEF_ANNOTATION, readableName)) {
TypeDeclaration typeDeclaration = findTypeDeclaration(getName());
if (typeDeclaration != null && typeDeclaration.annotations != null) {
Annotation astAnnotation = null;
for (Annotation a : typeDeclaration.annotations) {
if (a.resolvedType != null
&& (sameChars(INT_DEF_ANNOTATION, a.resolvedType.readableName()) ||
sameChars(STRING_DEF_ANNOTATION, a.resolvedType.readableName()))) {
astAnnotation = a;
break;
}
}
if (astAnnotation != null) {
result.add(new EcjAstAnnotation(annotation, astAnnotation));
continue;
}
}
}
result.add(new EcjResolvedAnnotation(annotation));
}
}
return result;
}
return Collections.emptyList();
}
};
}
@NonNull
@Override
public List<Value> getValues() {
ElementValuePair[] pairs = mBinding.getElementValuePairs();
if (pairs != null && pairs.length > 0) {
List<Value> values = Lists.newArrayListWithExpectedSize(pairs.length);
for (ElementValuePair pair : pairs) {
values.add(new Value(new String(pair.getName()), getPairValue(pair)));
}
}
return Collections.emptyList();
}
@Nullable
@Override
public Object getValue(@NonNull String name) {
ElementValuePair[] pairs = mBinding.getElementValuePairs();
if (pairs != null) {
for (ElementValuePair pair : pairs) {
if (sameChars(name, pair.getName())) {
return getPairValue(pair);
}
}
}
return null;
}
private Object getPairValue(ElementValuePair pair) {
return getConstantValue(pair.getValue());
}
@Override
public String getSignature() {
return new String(mBinding.getAnnotationType().readableName());
}
@Override
public int getModifiers() {
// Not applicable; move from ResolvedNode into ones that matter?
return 0;
}
@NonNull
@Override
public Iterable<ResolvedAnnotation> getAnnotations() {
List<ResolvedAnnotation> compiled = null;
AnnotationBinding[] annotations = mBinding.getAnnotationType().getAnnotations();
int count = annotations.length;
if (count > 0) {
compiled = Lists.newArrayListWithExpectedSize(count);
for (AnnotationBinding annotation : annotations) {
if (annotation != null) {
compiled.add(new EcjResolvedAnnotation(annotation));
}
}
}
// Look for external annotations
ExternalAnnotationRepository manager = ExternalAnnotationRepository.get(mClient);
Collection<ResolvedAnnotation> external = manager.getAnnotations(this);
return merge(compiled, external);
}
private class EcjAstAnnotation extends EcjResolvedAnnotation {
private final Annotation mAstAnnotation;
private List<Value> mValues;
public EcjAstAnnotation(
@NonNull AnnotationBinding binding, @NonNull Annotation astAnnotation) {
super(binding);
mAstAnnotation = astAnnotation;
}
@NonNull
@Override
public List<Value> getValues() {
if (mValues == null) {
MemberValuePair[] memberValuePairs = mAstAnnotation.memberValuePairs();
List<Value> result = Lists
.newArrayListWithExpectedSize(memberValuePairs.length);
for (MemberValuePair pair : memberValuePairs) {
// String n = new String(pair.name);
Expression expression = pair.value;
Object value = null;
if (expression instanceof ArrayInitializer) {
ArrayInitializer initializer = (ArrayInitializer) expression;
Expression[] expressions = initializer.expressions;
List<Object> values = Lists.newArrayList();
for (Expression e : expressions) {
if (e instanceof NameReference) {
ResolvedNode resolved = resolve(((NameReference) e).binding);
if (resolved != null) {
values.add(resolved);
}
} else if (e instanceof IntLiteral) {
values.add(((IntLiteral) e).value);
} else if (e instanceof StringLiteral) {
values.add(String.valueOf(((StringLiteral) e).source()));
} else {
values.add(e.toString());
}
}
value = values.toArray();
} else if (expression instanceof IntLiteral) {
IntLiteral intLiteral = (IntLiteral) expression;
value = intLiteral.value;
} else if (expression instanceof TrueLiteral) {
value = true;
} else if (expression instanceof FalseLiteral) {
value = false;
} else if (expression instanceof StringLiteral) {
value = String.valueOf(((StringLiteral) expression).source());
}
// Unfortunately, FloatLiteral, LongLiteral etc do not
// expose the value field as public. Luckily, we don't need that
// for our current annotations.
result.add(new Value(new String(pair.name), value));
}
mValues = result;
}
return mValues;
}
@Nullable
@Override
public Object getValue(@NonNull String name) {
for (Value value : getValues()) {
if (name.equals(value.name)) {
return value.value;
}
}
return null;
}
}
@SuppressWarnings("RedundantIfStatement")
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
EcjResolvedAnnotation that = (EcjResolvedAnnotation) o;
if (mBinding != null ? !mBinding.equals(that.mBinding) : that.mBinding != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
return mBinding != null ? mBinding.hashCode() : 0;
}
}
@Nullable
private Object getConstantValue(@Nullable Object value) {
if (value instanceof Constant) {
if (value == Constant.NotAConstant) {
return null;
}
if (value instanceof StringConstant) {
return ((StringConstant) value).stringValue();
} else if (value instanceof IntConstant) {
return ((IntConstant) value).intValue();
} else if (value instanceof BooleanConstant) {
return ((BooleanConstant) value).booleanValue();
} else if (value instanceof FloatConstant) {
return ((FloatConstant) value).floatValue();
} else if (value instanceof LongConstant) {
return ((LongConstant) value).longValue();
} else if (value instanceof DoubleConstant) {
return ((DoubleConstant) value).doubleValue();
} else if (value instanceof ShortConstant) {
return ((ShortConstant) value).shortValue();
} else if (value instanceof CharConstant) {
return ((CharConstant) value).charValue();
} else if (value instanceof ByteConstant) {
return ((ByteConstant) value).byteValue();
}
} else if (value instanceof Object[]) {
Object[] array = (Object[]) value;
if (array.length > 0) {
List<Object> list = Lists.newArrayListWithExpectedSize(array.length);
for (Object element : array) {
list.add(getConstantValue(element));
}
// Pick type of array. Annotations are limited to Strings, Classes
// and Annotations
if (!list.isEmpty()) {
Object first = list.get(0);
if (first instanceof String) {
//noinspection SuspiciousToArrayCall
return list.toArray(new String[list.size()]);
} else if (first instanceof java.lang.annotation.Annotation) {
//noinspection SuspiciousToArrayCall
return list.toArray(new Annotation[list.size()]);
} else if (first instanceof Class) {
//noinspection SuspiciousToArrayCall
return list.toArray(new Class[list.size()]);
}
}
return list.toArray();
}
} else if (value instanceof AnnotationBinding) {
return new EcjResolvedAnnotation((AnnotationBinding) value);
}
return value;
}
private static boolean sameChars(String str, char[] chars) {
int length = str.length();
if (chars.length != length) {
return false;
}
for (int i = 0; i < length; i++) {
if (chars[i] != str.charAt(i)) {
return false;
}
}
return true;
}
/**
* Does the given compound name match the given string?
* <p>
* TODO: Check if ECJ already has this as a utility somewhere
*/
@VisibleForTesting
static boolean startsWithCompound(@NonNull String name, @NonNull char[][] compoundName) {
int length = name.length();
if (length == 0) {
return false;
}
int index = 0;
for (int i = 0, n = compoundName.length; i < n; i++) {
char[] o = compoundName[i];
for (int j = 0, m = o.length; j < m; j++) {
if (index == length) {
return false; // Don't allow prefix in a compound name
}
if (name.charAt(index) != o[j]) {
return false;
}
index++;
}
if (i < n - 1) {
if (index == length) {
return true;
}
if (name.charAt(index) != '.') {
return false;
}
index++;
if (index == length) {
return true;
}
}
}
return index == length;
}
@VisibleForTesting
static boolean equalsCompound(@NonNull String name, @NonNull char[][] compoundName) {
int length = name.length();
if (length == 0) {
return false;
}
int index = 0;
for (int i = 0, n = compoundName.length; i < n; i++) {
char[] o = compoundName[i];
for (int j = 0, m = o.length; j < m; j++) {
if (index == length) {
return false; // Don't allow prefix in a compound name
}
if (name.charAt(index) != o[j]) {
return false;
}
index++;
}
if (i < n - 1) {
if (index == length) {
return false;
}
if (name.charAt(index) != '.') {
return false;
}
index++;
if (index == length) {
return false;
}
}
}
return index == length;
}
}