| /* |
| * 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.UTF_8; |
| |
| import com.android.SdkConstants; |
| import com.android.annotations.NonNull; |
| import com.android.annotations.Nullable; |
| 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.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.AllocationExpression; |
| import org.eclipse.jdt.internal.compiler.ast.Annotation; |
| 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.FloatLiteral; |
| import org.eclipse.jdt.internal.compiler.ast.IntLiteral; |
| import org.eclipse.jdt.internal.compiler.ast.Literal; |
| import org.eclipse.jdt.internal.compiler.ast.LongLiteral; |
| import org.eclipse.jdt.internal.compiler.ast.MagicLiteral; |
| 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.Binding; |
| 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.ProblemBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.ProblemFieldBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.ProblemMethodBinding; |
| 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.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.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 Parser mParser; |
| |
| 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.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 { |
| 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 void 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 (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(); |
| } |
| } |
| } |
| |
| @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) { |
| 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) { |
| 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()) { |
| 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); |
| } |
| } |
| } |
| |
| @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 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) { |
| return resolve(((Annotation) nativeNode).resolvedType); |
| } else if (nativeNode instanceof AbstractMethodDeclaration) { |
| return resolve(((AbstractMethodDeclaration) nativeNode).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 static 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; |
| } |
| |
| @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 |
| private static TypeDescriptor getTypeDescriptor(@Nullable TypeBinding resolvedType) { |
| if (resolvedType == null) { |
| return null; |
| } |
| return new EcjTypeDescriptor(resolvedType.readableName()); |
| } |
| |
| private static TypeDescriptor getTypeDescriptor(String fqn) { |
| return new DefaultTypeDescriptor(fqn); |
| } |
| |
| /* 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 static class EcjTypeDescriptor extends TypeDescriptor { |
| private String mName; |
| private char[] mChars; |
| |
| private EcjTypeDescriptor(char[] chars) { |
| mChars = chars; |
| } |
| |
| @NonNull |
| @Override |
| public String getName() { |
| if (mName == null) { |
| mName = new String(mChars); |
| } |
| return mName; |
| } |
| |
| @NonNull |
| @Override |
| public String getSignature() { |
| return getName(); |
| } |
| |
| @Override |
| public boolean matchesName(@NonNull String name) { |
| return sameChars(name, mChars); |
| } |
| |
| @Override |
| public boolean matchesSignature(@NonNull String signature) { |
| return matchesName(signature); |
| } |
| |
| @Override |
| public String toString() { |
| return getSignature(); |
| } |
| } |
| |
| private static 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; |
| } |
| |
| @Nullable |
| @Override |
| public TypeDescriptor getReturnType() { |
| return isConstructor() ? null : getTypeDescriptor(mBinding.returnType); |
| } |
| |
| @Override |
| public boolean isConstructor() { |
| return mBinding.isConstructor(); |
| } |
| |
| @Override |
| public int getModifiers() { |
| return mBinding.getAccessFlags(); |
| } |
| |
| @Override |
| public String getSignature() { |
| return mBinding.toString(); |
| } |
| } |
| |
| private static class EcjResolvedClass extends ResolvedClass { |
| private TypeBinding mBinding; |
| |
| private EcjResolvedClass(TypeBinding 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()); |
| } |
| |
| @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) { |
| if (mBinding instanceof ReferenceBinding) { |
| ReferenceBinding cls = (ReferenceBinding) mBinding; |
| MethodBinding[] methods = cls.getMethods(name.toCharArray()); |
| 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 |
| @Nullable |
| public ResolvedField getField(@NonNull String name) { |
| if (mBinding instanceof ReferenceBinding) { |
| ReferenceBinding cls = (ReferenceBinding) mBinding; |
| FieldBinding[] fields = cls.fields(); |
| if (fields != null) { |
| for (FieldBinding field : fields) { |
| if (sameChars(name, field.name)) { |
| return new EcjResolvedField(field); |
| } |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| @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(); |
| } |
| } |
| |
| private static 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() { |
| Constant constant = mBinding.constant(); |
| if (constant != null) { |
| if (constant instanceof StringConstant) { |
| return constant.stringValue(); |
| } else if (constant instanceof IntConstant) { |
| return constant.intValue(); |
| } else if (constant instanceof BooleanConstant) { |
| return constant.booleanValue(); |
| } else if (constant instanceof LongConstant) { |
| return constant.longValue(); |
| } else if (constant instanceof DoubleConstant) { |
| return constant.doubleValue(); |
| } else if (constant instanceof CharConstant) { |
| return constant.charValue(); |
| } else if (constant instanceof FloatConstant) { |
| return constant.floatValue(); |
| } else if (constant instanceof ShortConstant) { |
| return constant.shortValue(); |
| } else if (constant instanceof ByteConstant) { |
| return constant.byteValue(); |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public int getModifiers() { |
| return mBinding.getAccessFlags(); |
| } |
| |
| @Override |
| public String getSignature() { |
| return mBinding.toString(); |
| } |
| } |
| |
| private static 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; |
| } |
| |
| @Override |
| public String getSignature() { |
| return mBinding.toString(); |
| } |
| } |
| |
| 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; |
| } |
| } |