| /* |
| * Copyright (C) 2016 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.psi; |
| |
| import com.android.annotations.NonNull; |
| import com.android.annotations.Nullable; |
| import com.android.annotations.VisibleForTesting; |
| import com.android.tools.lint.ExternalAnnotationRepository; |
| import com.android.tools.lint.client.api.JavaEvaluator; |
| import com.google.common.collect.Lists; |
| import com.intellij.psi.PsiAnnotation; |
| import com.intellij.psi.PsiClass; |
| import com.intellij.psi.PsiClassType; |
| import com.intellij.psi.PsiMethod; |
| import com.intellij.psi.PsiModifierList; |
| import com.intellij.psi.PsiModifierListOwner; |
| import com.intellij.psi.PsiParameter; |
| import com.intellij.psi.PsiParameterList; |
| import com.intellij.psi.util.PsiTreeUtil; |
| |
| import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; |
| import org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.Binding; |
| import org.eclipse.jdt.internal.compiler.lookup.MethodBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; |
| |
| import java.util.Collection; |
| import java.util.List; |
| |
| public class EcjPsiJavaEvaluator extends JavaEvaluator { |
| private final EcjPsiManager mManager; |
| |
| public EcjPsiJavaEvaluator(@NonNull EcjPsiManager manager) { |
| mManager = manager; |
| } |
| |
| @Override |
| public boolean extendsClass( |
| @Nullable PsiClass cls, |
| @NonNull String className, |
| boolean strict) { |
| ReferenceBinding binding; |
| if (cls instanceof EcjPsiClass) { |
| TypeDeclaration declaration = (TypeDeclaration) ((EcjPsiClass) cls).mNativeNode; |
| binding = declaration.binding; |
| } else if (cls instanceof EcjPsiBinaryClass) { |
| binding = ((EcjPsiBinaryClass)cls).getTypeBinding(); |
| } else { |
| return false; |
| } |
| if (strict) { |
| binding = binding.superclass(); |
| } |
| |
| for (; binding != null; binding = binding.superclass()) { |
| if (equalsCompound(className, binding.compoundName)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| @Override |
| public boolean implementsInterface( |
| @NonNull PsiClass cls, |
| @NonNull String interfaceName, |
| boolean strict) { |
| ReferenceBinding binding; |
| if (cls instanceof EcjPsiClass) { |
| TypeDeclaration declaration = (TypeDeclaration) ((EcjPsiClass) cls).mNativeNode; |
| binding = declaration.binding; |
| } else if (cls instanceof EcjPsiBinaryClass) { |
| binding = ((EcjPsiBinaryClass)cls).getTypeBinding(); |
| } else { |
| return false; |
| } |
| if (strict) { |
| binding = binding.superclass(); |
| } |
| return isInheritor(binding, interfaceName); |
| } |
| |
| @Override |
| public boolean inheritsFrom( |
| @NonNull PsiClass cls, |
| @NonNull String className, |
| boolean strict) { |
| return /*extendsClass(cls, className, strict) || */implementsInterface(cls, className, strict); |
| } |
| |
| @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]; |
| //noinspection ForLoopReplaceableByForEach |
| 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] |
| // Allow using . as an inner class separator whereas the |
| // symbol table will always use $ |
| && !(o[j] == '$' && name.charAt(index) == '.')) { |
| 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; |
| } |
| |
| /** Checks whether the given class extends or implements a class with the given name */ |
| private static boolean isInheritor(@Nullable ReferenceBinding cls, @NonNull String name) { |
| for (; cls != null; cls = cls.superclass()) { |
| ReferenceBinding[] interfaces = cls.superInterfaces(); |
| for (ReferenceBinding binding : interfaces) { |
| if (isInheritor(binding, name)) { |
| return true; |
| } |
| } |
| |
| if (equalsCompound(name, cls.compoundName)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| @NonNull |
| @Override |
| public String getInternalName(@NonNull PsiClass psiClass) { |
| ReferenceBinding binding = null; |
| if (psiClass instanceof EcjPsiClass) { |
| //noinspection ConstantConditions |
| binding = ((TypeDeclaration) ((EcjPsiClass) psiClass).getNativeNode()).binding; |
| } else if (psiClass instanceof EcjPsiBinaryClass) { |
| Binding binaryBinding = ((EcjPsiBinaryClass) psiClass).getBinding(); |
| if (binaryBinding instanceof ReferenceBinding) { |
| binding = (ReferenceBinding) binaryBinding; |
| } |
| } |
| if (binding == null) { |
| return super.getInternalName(psiClass); |
| } |
| |
| return EcjPsiManager.getInternalName(binding.compoundName); |
| } |
| |
| @NonNull |
| @Override |
| public String getInternalName(@NonNull PsiClassType psiClassType) { |
| if (psiClassType instanceof EcjPsiClassType) { |
| EcjPsiManager.getTypeName(((EcjPsiClassType)psiClassType).getBinding()); |
| } |
| return super.getInternalName(psiClassType); |
| } |
| |
| @Override |
| @Nullable |
| public PsiClass findClass(@NonNull String fullyQualifiedName) { |
| return mManager.findClass(fullyQualifiedName); |
| } |
| |
| @Nullable |
| @Override |
| public PsiClassType getClassType(@Nullable PsiClass psiClass) { |
| if (psiClass != null) { |
| return mManager.getClassType(psiClass); |
| } |
| return null; |
| } |
| |
| @NonNull |
| @Override |
| public PsiAnnotation[] getAllAnnotations(@NonNull PsiModifierListOwner owner, |
| boolean inHierarchy) { |
| if (!inHierarchy) { |
| return getDirectAnnotations(owner); |
| } |
| |
| PsiModifierList modifierList = owner.getModifierList(); |
| if (modifierList == null) { |
| return getDirectAnnotations(owner); |
| } |
| |
| if (owner instanceof PsiMethod) { |
| MethodBinding method; |
| if (owner instanceof EcjPsiMethod) { |
| EcjPsiMethod psiMethod = (EcjPsiMethod) owner; |
| AbstractMethodDeclaration declaration = (AbstractMethodDeclaration) psiMethod.getNativeNode(); |
| assert declaration != null; |
| method = declaration.binding; |
| } else if (owner instanceof EcjPsiBinaryMethod) { |
| method = ((EcjPsiBinaryMethod) owner).getBinding(); |
| } else { |
| assert false : owner.getClass(); |
| return PsiAnnotation.EMPTY_ARRAY; |
| } |
| |
| List<PsiAnnotation> all = Lists.newArrayListWithExpectedSize(2); |
| ExternalAnnotationRepository manager = mManager.getAnnotationRepository(); |
| |
| while (method != null) { |
| AnnotationBinding[] annotations = method.getAnnotations(); |
| int count = annotations.length; |
| if (count > 0) { |
| all = Lists.newArrayListWithExpectedSize(count); |
| for (AnnotationBinding annotation : annotations) { |
| if (annotation != null) { |
| all.add(new EcjPsiBinaryAnnotation(mManager, modifierList, annotation)); |
| } |
| } |
| } |
| |
| // Look for external annotations |
| if (manager != null) { |
| Collection<PsiAnnotation> external = manager.getAnnotations(method); |
| if (external != null) { |
| all.addAll(external); |
| } |
| } |
| |
| method = EcjPsiManager.findSuperMethodBinding(method, false, false); |
| } |
| |
| return EcjPsiManager.ensureUnique(all); |
| } else if (owner instanceof PsiClass) { |
| ReferenceBinding cls; |
| if (owner instanceof EcjPsiClass) { |
| EcjPsiClass psiClass = (EcjPsiClass) owner; |
| TypeDeclaration declaration = (TypeDeclaration) psiClass.getNativeNode(); |
| assert declaration != null; |
| cls = declaration.binding; |
| } else if (owner instanceof EcjPsiBinaryClass) { |
| cls = ((EcjPsiBinaryClass) owner).getTypeBinding(); |
| } else { |
| assert false : owner.getClass(); |
| return PsiAnnotation.EMPTY_ARRAY; |
| } |
| |
| List<PsiAnnotation> all = Lists.newArrayListWithExpectedSize(2); |
| ExternalAnnotationRepository manager = mManager.getAnnotationRepository(); |
| |
| 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 EcjPsiBinaryAnnotation(mManager, modifierList, annotation)); |
| } |
| } |
| } |
| |
| // Look for external annotations |
| if (manager != null) { |
| Collection<PsiAnnotation> external = manager.getAnnotations(cls); |
| if (external != null) { |
| all.addAll(external); |
| } |
| } |
| |
| cls = cls.superclass(); |
| } |
| |
| return EcjPsiManager.ensureUnique(all); |
| } else if (owner instanceof PsiParameter) { |
| MethodBinding method; |
| int index; |
| |
| if (owner instanceof EcjPsiBinaryParameter) { |
| EcjPsiBinaryParameter parameter = (EcjPsiBinaryParameter) owner; |
| method = parameter.getOwnerMethod().getBinding(); |
| index = parameter.getIndex(); |
| } else if (owner instanceof EcjPsiParameter) { |
| EcjPsiParameter parameter = (EcjPsiParameter) owner; |
| if (parameter.getParent() instanceof PsiParameterList) { |
| EcjPsiMethod psiMethod = (EcjPsiMethod)PsiTreeUtil.getParentOfType( |
| parameter.getParent(), PsiMethod.class, true); |
| if (psiMethod == null) { |
| return getDirectAnnotations(owner); |
| } |
| index = ((PsiParameterList)parameter.getParent()).getParameterIndex(parameter); |
| AbstractMethodDeclaration declaration = (AbstractMethodDeclaration) psiMethod.getNativeNode(); |
| assert declaration != null; |
| method = declaration.binding; |
| } else { |
| // For each block, catch block |
| return getDirectAnnotations(owner); |
| } |
| } else { |
| // Unexpected method type |
| assert false : owner.getClass(); |
| return getDirectAnnotations(owner); |
| } |
| |
| List<PsiAnnotation> all = Lists.newArrayListWithExpectedSize(2); |
| ExternalAnnotationRepository manager = mManager.getAnnotationRepository(); |
| |
| while (method != null) { |
| AnnotationBinding[][] parameterAnnotations = method.getParameterAnnotations(); |
| if (parameterAnnotations != null && index < parameterAnnotations.length) { |
| AnnotationBinding[] annotations = parameterAnnotations[index]; |
| int count = annotations.length; |
| if (count > 0) { |
| all = Lists.newArrayListWithExpectedSize(count); |
| for (AnnotationBinding annotation : annotations) { |
| if (annotation != null) { |
| all.add(new EcjPsiBinaryAnnotation(mManager, modifierList, |
| annotation)); |
| } |
| } |
| } |
| } |
| |
| // Look for external annotations |
| if (manager != null) { |
| Collection<PsiAnnotation> external = manager.getParameterAnnotations(method, |
| index); |
| if (external != null) { |
| all.addAll(external); |
| } |
| } |
| |
| method = EcjPsiManager.findSuperMethodBinding(method, false, false); |
| } |
| |
| return EcjPsiManager.ensureUnique(all); |
| } else { |
| // PsiField, PsiLocalVariable etc: no inheritance |
| return getDirectAnnotations(owner); |
| } |
| } |
| |
| @NonNull |
| private static PsiAnnotation[] getDirectAnnotations(@NonNull PsiModifierListOwner owner) { |
| PsiModifierList modifierList = owner.getModifierList(); |
| if (modifierList != null) { |
| return modifierList.getAnnotations(); |
| } else { |
| return PsiAnnotation.EMPTY_ARRAY; |
| } |
| } |
| |
| @Nullable |
| @Override |
| public PsiAnnotation findAnnotationInHierarchy(@NonNull PsiModifierListOwner listOwner, |
| @NonNull String... annotationNames) { |
| throw new UnimplementedLintPsiApiException(); |
| } |
| |
| @Nullable |
| @Override |
| public PsiAnnotation findAnnotation(@Nullable PsiModifierListOwner listOwner, |
| @NonNull String... annotationNames) { |
| throw new UnimplementedLintPsiApiException(); |
| } |
| } |