/* * 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 org.jetbrains.android.inspections.lint; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.annotations.VisibleForTesting; 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.Severity; import com.google.common.collect.Lists; import com.intellij.openapi.application.AccessToken; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Computable; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import lombok.ast.*; import org.jetbrains.annotations.Contract; import java.io.File; import java.lang.reflect.Modifier; import java.util.Collections; import java.util.List; public class LombokPsiParser extends JavaParser { private final LintClient myClient; private AccessToken myLock; public LombokPsiParser(LintClient client) { myClient = client; } @Override public void prepareJavaParse(@NonNull List<JavaContext> contexts) { } @Nullable @Override public Node parseJava(@NonNull final JavaContext context) { assert myLock == null; myLock = ApplicationManager.getApplication().acquireReadActionLock(); Node compilationUnit = parse(context); if (compilationUnit == null) { myLock.finish(); myLock = null; } return compilationUnit; } @Override public void dispose(@NonNull JavaContext context, @NonNull Node compilationUnit) { if (context.getCompilationUnit() != null) { myLock.finish(); myLock = null; context.setCompilationUnit(null); } } @Nullable private Node parse(@NonNull JavaContext context) { // Should only be called from read thread assert ApplicationManager.getApplication().isReadAccessAllowed(); final PsiFile psiFile = IntellijLintUtils.getPsiFile(context); if (!(psiFile instanceof PsiJavaFile)) { return null; } PsiJavaFile javaFile = (PsiJavaFile)psiFile; try { return LombokPsiConverter.convert(javaFile); } catch (Throwable t) { myClient.log(t, "Failed converting PSI parse tree to Lombok for file %1$s", context.file.getPath()); return null; } } @NonNull @Override public Location getLocation(@NonNull JavaContext context, @NonNull Node node) { Position position = node.getPosition(); if (position == null) { myClient.log(Severity.WARNING, null, "No position data found for node %1$s", node); return Location.create(context.file); } return Location.create(context.file, null, position.getStart(), position.getEnd()); } @NonNull @Override public Location.Handle createLocationHandle(@NonNull JavaContext context, @NonNull Node node) { return new LocationHandle(context.file, node); } @Nullable private static PsiElement getPsiElement(@NonNull Node node) { Object nativeNode = node.getNativeNode(); if (nativeNode == null) { return null; } return (PsiElement)nativeNode; } @Nullable @Override public ResolvedNode resolve(@NonNull JavaContext context, @NonNull Node node) { final PsiElement element = getPsiElement(node); if (element == null) { return null; } Application application = ApplicationManager.getApplication(); if (application.isReadAccessAllowed()) { return resolve(element); } return application.runReadAction(new Computable<ResolvedNode>() { @Nullable @Override public ResolvedNode compute() { return resolve(element); } }); } @Nullable @Override public TypeDescriptor getType(@NonNull JavaContext context, @NonNull Node node) { final PsiElement element = getPsiElement(node); if (element == null) { return null; } Application application = ApplicationManager.getApplication(); if (application.isReadAccessAllowed()) { return getTypeDescriptor(element); } return application.runReadAction(new Computable<TypeDescriptor>() { @Nullable @Override public TypeDescriptor compute() { return getTypeDescriptor(element); } }); } @VisibleForTesting @Nullable static ResolvedNode resolve(@NonNull PsiElement element) { if (element instanceof PsiCall) { PsiMethod resolved = ((PsiCall)element).resolveMethod(); if (resolved != null) { return new ResolvedPsiMethod(resolved); } return null; } PsiReference reference = element.getReference(); if (reference != null) { PsiElement resolved = reference.resolve(); if (resolved != null) { element = resolved; } } if (element instanceof PsiField) { return new ResolvedPsiField((PsiField)element); } else if (element instanceof PsiMethod) { return new ResolvedPsiMethod((PsiMethod)element); } else if (element instanceof PsiVariable) { return new ResolvedPsiVariable((PsiVariable)element); } else if (element instanceof PsiClass) { return new ResolvedPsiClass((PsiClass)element); } else if (element instanceof PsiJavaCodeReferenceElement) { PsiJavaCodeReferenceElement r = (PsiJavaCodeReferenceElement)element; String qualifiedName = r.getQualifiedName(); if (qualifiedName != null) { return new ResolvedPsiClassName(element.getManager(), qualifiedName); } } return null; } @Nullable private static TypeDescriptor getTypeDescriptor(@NonNull PsiElement element) { PsiType type = null; if (element instanceof PsiExpression) { type = ((PsiExpression)element).getType(); } else if (element instanceof PsiVariable) { type = ((PsiVariable)element).getType(); } else if (element instanceof PsiMethod) { type = ((PsiMethod)element).getReturnType(); } return getTypeDescriptor(type); } @Contract("!null -> !null") @Nullable private static TypeDescriptor getTypeDescriptor(@Nullable PsiType type) { return type != null ? new DefaultTypeDescriptor(type.getCanonicalText()) : null; } private static int computeModifiers(@Nullable PsiModifierListOwner owner) { // TODO: Find out if there is a PSI utility method somewhere to handle this int modifiers = 0; if (owner != null) { if (owner.hasModifierProperty(PsiModifier.ABSTRACT)) { modifiers |= Modifier.ABSTRACT; } if (owner.hasModifierProperty(PsiModifier.PUBLIC)) { modifiers |= Modifier.PUBLIC; } if (owner.hasModifierProperty(PsiModifier.STATIC)) { modifiers |= Modifier.STATIC; } if (owner.hasModifierProperty(PsiModifier.PRIVATE)) { modifiers |= Modifier.PRIVATE; } if (owner.hasModifierProperty(PsiModifier.PROTECTED)) { modifiers |= Modifier.PROTECTED; } if (owner.hasModifierProperty(PsiModifier.FINAL)) { modifiers |= Modifier.FINAL; } // Other constants are not used by lint. } return modifiers; } /* Handle for creating positions cheaply and returning full fledged locations later */ private class LocationHandle implements Location.Handle { private final File myFile; private final Node myNode; private Object mClientData; public LocationHandle(File file, Node node) { myFile = file; myNode = node; } @NonNull @Override public Location resolve() { Position pos = myNode.getPosition(); if (pos == null) { myClient.log(Severity.WARNING, null, "No position data found for node %1$s", myNode); return Location.create(myFile); } return Location.create(myFile, null /*contents*/, pos.getStart(), pos.getEnd()); } @Override public void setClientData(@Nullable Object clientData) { mClientData = clientData; } @Override @Nullable public Object getClientData() { return mClientData; } } private static class ResolvedPsiMethod extends ResolvedMethod { private PsiMethod myMethod; private ResolvedPsiMethod(@NonNull PsiMethod method) { myMethod = method; } @NonNull @Override public String getName() { return myMethod.getName(); } @Override public boolean matches(@NonNull String name) { return name.equals(myMethod.getName()); } @NonNull @Override public ResolvedClass getContainingClass() { PsiClass containingClass = myMethod.getContainingClass(); return new ResolvedPsiClass(containingClass); } @Override public int getArgumentCount() { return myMethod.getParameterList().getParametersCount(); } @NonNull @Override public TypeDescriptor getArgumentType(int index) { PsiParameter parameter = myMethod.getParameterList().getParameters()[index]; PsiType type = parameter.getType(); return getTypeDescriptor(type); } @Nullable @Override public TypeDescriptor getReturnType() { if (myMethod.isConstructor()) { return null; } else { return getTypeDescriptor(myMethod.getReturnType()); } } @Override public String getSignature() { return myMethod.toString(); } @Override public int getModifiers() { // TODO: Find out if there is a PSI utility method somewhere to handle this int modifiers = 0; if (myMethod.hasModifierProperty(PsiModifier.ABSTRACT)) { modifiers |= Modifier.ABSTRACT; } if (myMethod.hasModifierProperty(PsiModifier.PUBLIC)) { modifiers |= Modifier.PUBLIC; } if (myMethod.hasModifierProperty(PsiModifier.STATIC)) { modifiers |= Modifier.STATIC; } if (myMethod.hasModifierProperty(PsiModifier.PRIVATE)) { modifiers |= Modifier.PRIVATE; } if (myMethod.hasModifierProperty(PsiModifier.PROTECTED)) { modifiers |= Modifier.PROTECTED; } if (myMethod.hasModifierProperty(PsiModifier.FINAL)) { modifiers |= Modifier.FINAL; } // Other constants are not used by lint. return modifiers; } } private static class ResolvedPsiVariable extends ResolvedVariable { private PsiVariable myVariable; private ResolvedPsiVariable(PsiVariable variable) { myVariable = variable; } @NonNull @Override public String getName() { return myVariable.getName(); } @Override public boolean matches(@NonNull String name) { return name.equals(myVariable.getName()); } @NonNull @Override public TypeDescriptor getType() { return getTypeDescriptor(myVariable.getType()); } @Override public int getModifiers() { return computeModifiers(myVariable); } @Override public String getSignature() { return myVariable.toString(); } } private static class ResolvedPsiField extends ResolvedField { private PsiField myField; private ResolvedPsiField(PsiField field) { myField = field; } @NonNull @Override public String getName() { return myField.getName(); } @Override public boolean matches(@NonNull String name) { return name.equals(myField.getName()); } @NonNull @Override public TypeDescriptor getType() { return getTypeDescriptor(myField.getType()); } @NonNull @Override public ResolvedClass getContainingClass() { return new ResolvedPsiClass(myField.getContainingClass()); } @Nullable @Override public Object getValue() { return myField.computeConstantValue(); } @Override public int getModifiers() { return computeModifiers(myField); } @Override public String getSignature() { return myField.toString(); } } private static class ResolvedPsiClassName extends ResolvedPsiClass { private final String myName; private final PsiManager myManager; private boolean myInitialized; private ResolvedPsiClassName(PsiManager manager, String name) { super(null); myManager = manager; myName = name; } @NonNull @Override public String getName() { return myName; } @Override public boolean matches(@NonNull String name) { return name.equals(myName); } private void ensureInitialized() { if (myInitialized) { return; } myInitialized = true; Project project = myManager.getProject(); myClass = JavaPsiFacade.getInstance(project).findClass(myName, GlobalSearchScope.allScope(project)); } @Nullable @Override public ResolvedClass getSuperClass() { ensureInitialized(); if (myClass != null) { return super.getSuperClass(); } return null; } @Nullable @Override public ResolvedClass getContainingClass() { ensureInitialized(); if (myClass != null) { return super.getContainingClass(); } return null; } @Override public boolean isSubclassOf(@NonNull String name, boolean strict) { if (!strict && name.equals(myName)) { return true; } ensureInitialized(); if (myClass != null) { return super.isSubclassOf(name, strict); } return false; } @NonNull @Override public Iterable<ResolvedMethod> getConstructors() { ensureInitialized(); if (myClass != null) { return super.getConstructors(); } return Collections.emptyList(); } @NonNull @Override public Iterable<ResolvedMethod> getMethods(@NonNull String name, boolean includeInherited) { ensureInitialized(); if (myClass != null) { return super.getMethods(name, includeInherited); } return Collections.emptyList(); } @Nullable @Override public ResolvedField getField(@NonNull String name, boolean includeInherited) { ensureInitialized(); if (myClass != null) { return super.getField(name, includeInherited); } return null; } @Override public int getModifiers() { ensureInitialized(); return computeModifiers(myClass); } @Override public String getSignature() { return myName; } } private static class ResolvedPsiClass extends ResolvedClass { @Nullable protected PsiClass myClass; private ResolvedPsiClass(@Nullable PsiClass cls) { myClass = cls; } @NonNull @Override public String getName() { if (myClass != null) { String qualifiedName = myClass.getQualifiedName(); if (qualifiedName != null) { return qualifiedName; } else { return myClass.getName(); } } return ""; } @Override public boolean matches(@NonNull String name) { return name.equals(getName()); } @Nullable @Override public ResolvedClass getSuperClass() { if (myClass != null) { PsiClass superClass = myClass.getSuperClass(); if (superClass != null) { return new ResolvedPsiClass(superClass); } } return null; } @Nullable @Override public ResolvedClass getContainingClass() { if (myClass != null) { PsiClass containingClass = myClass.getContainingClass(); if (containingClass != null) { return new ResolvedPsiClass(containingClass); } } return null; } @Override public boolean isSubclassOf(@NonNull String name, boolean strict) { if (myClass != null) { PsiClass cls = myClass; if (strict) { cls = cls.getSuperClass(); } while (cls != null) { if (name.equals(cls.getQualifiedName())) { return true; } cls = cls.getSuperClass(); } } return false; } @NonNull @Override public Iterable<ResolvedMethod> getConstructors() { if (myClass != null) { PsiMethod[] methods = myClass.getConstructors(); if (methods.length > 0) { List<ResolvedMethod> result = Lists.newArrayListWithExpectedSize(methods.length); for (PsiMethod method : methods) { result.add(new ResolvedPsiMethod(method)); } return result; } } return Collections.emptyList(); } @NonNull @Override public Iterable<ResolvedMethod> getMethods(@NonNull String name, boolean includeInherited) { if (myClass != null) { PsiMethod[] methods = myClass.findMethodsByName(name, includeInherited); if (methods.length > 0) { List<ResolvedMethod> result = Lists.newArrayListWithExpectedSize(methods.length); for (PsiMethod method : methods) { if (!method.isConstructor()) { result.add(new ResolvedPsiMethod(method)); } } return result; } } return Collections.emptyList(); } @Nullable @Override public ResolvedField getField(@NonNull String name, boolean includeInherited) { if (myClass != null) { PsiField field = myClass.findFieldByName(name, includeInherited); if (field != null) { return new ResolvedPsiField(field); } } return null; } @Override public int getModifiers() { return computeModifiers(myClass); } @Override public String getSignature() { return getName(); } } }