| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * 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.intellij.codeInsight; |
| |
| import com.intellij.openapi.project.Project; |
| import com.intellij.psi.*; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.PsiUtil; |
| import com.intellij.util.ArrayUtil; |
| import gnu.trove.THashSet; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.Proxy; |
| import java.util.*; |
| |
| /** |
| * @author max |
| */ |
| public class AnnotationUtil { |
| /** |
| * The full qualified name of the standard Nullable annotation. |
| */ |
| public static final String NULLABLE = "org.jetbrains.annotations.Nullable"; |
| |
| /** |
| * The full qualified name of the standard NotNull annotation. |
| */ |
| public static final String NOT_NULL = "org.jetbrains.annotations.NotNull"; |
| |
| @NonNls public static final String NOT_NULL_SIMPLE_NAME = "NotNull"; |
| |
| @NonNls public static final String NULLABLE_SIMPLE_NAME = "Nullable"; |
| |
| /** |
| * The full qualified name of the standard NonNls annotation. |
| * |
| * @since 5.0.1 |
| */ |
| public static final String NON_NLS = "org.jetbrains.annotations.NonNls"; |
| public static final String NLS = "org.jetbrains.annotations.Nls"; |
| public static final String PROPERTY_KEY = "org.jetbrains.annotations.PropertyKey"; |
| @NonNls public static final String PROPERTY_KEY_RESOURCE_BUNDLE_PARAMETER = "resourceBundle"; |
| |
| @NonNls public static final String NON_NLS_SIMPLE_NAME = "NonNls"; |
| @NonNls public static final String PROPERTY_KEY_SIMPLE_NAME = "PropertyKey"; |
| |
| public static final String TEST_ONLY = "org.jetbrains.annotations.TestOnly"; |
| @NonNls public static final String TEST_ONLY_SIMPLE_NAME = "TestOnly"; |
| |
| public static final String LANGUAGE = "org.intellij.lang.annotations.Language"; |
| |
| public static final Set<String> ALL_ANNOTATIONS; |
| |
| @NonNls private static final String[] SIMPLE_NAMES = |
| {NOT_NULL_SIMPLE_NAME, NULLABLE_SIMPLE_NAME, NON_NLS_SIMPLE_NAME, PROPERTY_KEY_SIMPLE_NAME, TEST_ONLY_SIMPLE_NAME, |
| "Language", "Identifier", "Pattern", "PrintFormat", "RegExp", "Subst"}; |
| |
| static { |
| ALL_ANNOTATIONS = new HashSet<String>(2); |
| ALL_ANNOTATIONS.add(NULLABLE); |
| ALL_ANNOTATIONS.add(NOT_NULL); |
| } |
| |
| @Nullable |
| public static PsiAnnotation findAnnotation(@Nullable PsiModifierListOwner listOwner, @NotNull String... annotationNames) { |
| return findAnnotation(listOwner, false, annotationNames); |
| } |
| |
| @Nullable |
| public static PsiAnnotation findAnnotation(@Nullable PsiModifierListOwner listOwner, final boolean skipExternal, @NotNull String... annotationNames) { |
| if (annotationNames.length == 0) return null; |
| Set<String> set = annotationNames.length == 1 ? Collections.singleton(annotationNames[0]) : new HashSet<String>(Arrays.asList(annotationNames)); |
| return findAnnotation(listOwner, set, skipExternal); |
| } |
| |
| @Nullable |
| public static PsiAnnotation findAnnotation(@Nullable PsiModifierListOwner listOwner, @NotNull Set<String> annotationNames) { |
| return findAnnotation(listOwner, (Collection<String>)annotationNames); |
| } |
| |
| @Nullable |
| public static PsiAnnotation findAnnotation(@Nullable PsiModifierListOwner listOwner, @NotNull Collection<String> annotationNames) { |
| return findAnnotation(listOwner, annotationNames, false); |
| } |
| |
| @Nullable |
| public static PsiAnnotation findAnnotation(@Nullable PsiModifierListOwner listOwner, @NotNull Collection<String> annotationNames, |
| final boolean skipExternal) { |
| if (listOwner == null) return null; |
| final PsiModifierList list = listOwner.getModifierList(); |
| if (list == null) return null; |
| final PsiAnnotation[] allAnnotations = list.getAnnotations(); |
| for (PsiAnnotation annotation : allAnnotations) { |
| String qualifiedName = annotation.getQualifiedName(); |
| if (annotationNames.contains(qualifiedName)) { |
| return annotation; |
| } |
| } |
| if (!skipExternal) { |
| final Project project = listOwner.getProject(); |
| final ExternalAnnotationsManager annotationsManager = ExternalAnnotationsManager.getInstance(project); |
| for (String annotationName : annotationNames) { |
| final PsiAnnotation annotation = annotationsManager.findExternalAnnotation(listOwner, annotationName); |
| if (annotation != null) { |
| return annotation; |
| } |
| } |
| final InferredAnnotationsManager inferredAnnotationsManager = InferredAnnotationsManager.getInstance(project); |
| for (String annotationName : annotationNames) { |
| final PsiAnnotation annotation = inferredAnnotationsManager.findInferredAnnotation(listOwner, annotationName); |
| if (annotation != null) { |
| return annotation; |
| } |
| } |
| } |
| return null; |
| } |
| |
| @NotNull |
| public static PsiAnnotation[] findAnnotations(@Nullable PsiModifierListOwner modifierListOwner, @NotNull Collection<String> annotationNames) { |
| if (modifierListOwner == null) return PsiAnnotation.EMPTY_ARRAY; |
| final PsiModifierList modifierList = modifierListOwner.getModifierList(); |
| if (modifierList == null) return PsiAnnotation.EMPTY_ARRAY; |
| final PsiAnnotation[] annotations = modifierList.getAnnotations(); |
| ArrayList<PsiAnnotation> result = null; |
| for (final PsiAnnotation psiAnnotation : annotations) { |
| if (annotationNames.contains(psiAnnotation.getQualifiedName())) { |
| if (result == null) result = new ArrayList<PsiAnnotation>(); |
| result.add(psiAnnotation); |
| } |
| } |
| return result == null ? PsiAnnotation.EMPTY_ARRAY : result.toArray(new PsiAnnotation[result.size()]); |
| } |
| |
| @Nullable |
| public static PsiAnnotation findAnnotationInHierarchy(PsiModifierListOwner listOwner, @NotNull Set<String> annotationNames) { |
| PsiAnnotation directAnnotation = findAnnotation(listOwner, annotationNames); |
| if (directAnnotation != null) return directAnnotation; |
| if (listOwner instanceof PsiMethod) { |
| PsiMethod method = (PsiMethod)listOwner; |
| PsiClass aClass = method.getContainingClass(); |
| if (aClass == null) return null; |
| HierarchicalMethodSignature methodSignature = method.getHierarchicalMethodSignature(); |
| return findAnnotationInHierarchy(methodSignature, annotationNames, method, null, |
| JavaPsiFacade.getInstance(method.getProject()).getResolveHelper()); |
| } |
| if (listOwner instanceof PsiClass) { |
| return findAnnotationInHierarchy((PsiClass)listOwner, annotationNames, null); |
| } |
| if (listOwner instanceof PsiParameter) { |
| PsiParameter parameter = (PsiParameter)listOwner; |
| return doFindAnnotationInHierarchy(parameter, annotationNames, null); |
| } |
| return null; |
| } |
| |
| @Nullable |
| private static PsiAnnotation doFindAnnotationInHierarchy(@NotNull PsiParameter parameter, |
| @NotNull Set<String> annotationNames, |
| @Nullable Set<PsiModifierListOwner> visited) { |
| PsiAnnotation annotation = findAnnotation(parameter, annotationNames); |
| if (annotation != null) return annotation; |
| PsiElement scope = parameter.getDeclarationScope(); |
| if (!(scope instanceof PsiMethod)) { |
| return null; |
| } |
| PsiMethod method = (PsiMethod)scope; |
| PsiClass aClass = method.getContainingClass(); |
| PsiElement parent = parameter.getParent(); |
| if (aClass == null || !(parent instanceof PsiParameterList)) { |
| return null; |
| } |
| int index = ((PsiParameterList)parent).getParameterIndex(parameter); |
| HierarchicalMethodSignature methodSignature = method.getHierarchicalMethodSignature(); |
| |
| final List<HierarchicalMethodSignature> superSignatures = methodSignature.getSuperSignatures(); |
| PsiResolveHelper resolveHelper = PsiResolveHelper.SERVICE.getInstance(aClass.getProject()); |
| for (final HierarchicalMethodSignature superSignature : superSignatures) { |
| final PsiMethod superMethod = superSignature.getMethod(); |
| if (visited == null) visited = new THashSet<PsiModifierListOwner>(); |
| if (!visited.add(superMethod)) continue; |
| if (!resolveHelper.isAccessible(superMethod, parameter, null)) continue; |
| PsiParameter[] superParameters = superMethod.getParameterList().getParameters(); |
| if (index < superParameters.length) { |
| PsiAnnotation insuper = doFindAnnotationInHierarchy(superParameters[index], annotationNames, visited); |
| if (insuper != null) return insuper; |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| private static PsiAnnotation findAnnotationInHierarchy(@NotNull final PsiClass psiClass, @NotNull Set<String> annotationNames, @Nullable Set<PsiClass> processed) { |
| final PsiClass[] superClasses = psiClass.getSupers(); |
| for (final PsiClass superClass : superClasses) { |
| if (processed == null) processed = new THashSet<PsiClass>(); |
| if (!processed.add(superClass)) return null; |
| final PsiAnnotation annotation = findAnnotation(superClass, annotationNames); |
| if (annotation != null) return annotation; |
| final PsiAnnotation annotationInHierarchy = findAnnotationInHierarchy(superClass, annotationNames, processed); |
| if (annotationInHierarchy != null) return annotationInHierarchy; |
| } |
| return null; |
| } |
| |
| @Nullable |
| private static PsiAnnotation findAnnotationInHierarchy(@NotNull HierarchicalMethodSignature signature, |
| @NotNull Set<String> annotationNames, |
| @NotNull PsiElement place, |
| @Nullable Set<PsiMethod> processed, |
| @NotNull PsiResolveHelper resolveHelper) { |
| final List<HierarchicalMethodSignature> superSignatures = signature.getSuperSignatures(); |
| for (final HierarchicalMethodSignature superSignature : superSignatures) { |
| final PsiMethod superMethod = superSignature.getMethod(); |
| if (processed == null) processed = new THashSet<PsiMethod>(); |
| if (!processed.add(superMethod)) continue; |
| if (!resolveHelper.isAccessible(superMethod, place, null)) continue; |
| PsiAnnotation direct = findAnnotation(superMethod, annotationNames); |
| if (direct != null) return direct; |
| PsiAnnotation superResult = findAnnotationInHierarchy(superSignature, annotationNames, place, processed, resolveHelper); |
| if (superResult != null) return superResult; |
| } |
| |
| return null; |
| } |
| |
| public static boolean isAnnotated(@NotNull PsiModifierListOwner listOwner, @NotNull Collection<String> annotations) { |
| return isAnnotated(listOwner, annotations, false); |
| } |
| |
| public static boolean isAnnotated(@NotNull PsiModifierListOwner listOwner, |
| @NotNull Collection<String> annotations, |
| final boolean checkHierarchy) { |
| return isAnnotated(listOwner, annotations, checkHierarchy, true); |
| } |
| |
| public static boolean isAnnotated(@NotNull PsiModifierListOwner listOwner, |
| @NotNull Collection<String> annotations, |
| final boolean checkHierarchy, |
| boolean skipExternal) { |
| for (String annotation : annotations) { |
| if (isAnnotated(listOwner, annotation, checkHierarchy, skipExternal)) return true; |
| } |
| return false; |
| } |
| |
| public static boolean isAnnotated(@NotNull PsiModifierListOwner listOwner, @NonNls @NotNull String annotationFQN, boolean checkHierarchy) { |
| return isAnnotated(listOwner, annotationFQN, checkHierarchy, true, null); |
| } |
| |
| public static boolean isAnnotated(@NotNull PsiModifierListOwner listOwner, @NonNls @NotNull String annotationFQN, boolean checkHierarchy, |
| boolean skipExternal) { |
| return isAnnotated(listOwner, annotationFQN, checkHierarchy, skipExternal, null); |
| } |
| |
| private static boolean isAnnotated(@NotNull PsiModifierListOwner listOwner, |
| @NonNls @NotNull String annotationFQN, |
| boolean checkHierarchy, final boolean skipExternal, @Nullable Set<PsiMember> processed) { |
| if (!listOwner.isValid()) return false; |
| final PsiModifierList modifierList = listOwner.getModifierList(); |
| if (modifierList == null) return false; |
| PsiAnnotation annotation = modifierList.findAnnotation(annotationFQN); |
| if (annotation != null) return true; |
| if (!skipExternal) { |
| final Project project = listOwner.getProject(); |
| if (ExternalAnnotationsManager.getInstance(project).findExternalAnnotation(listOwner, annotationFQN) != null || |
| InferredAnnotationsManager.getInstance(project).findInferredAnnotation(listOwner, annotationFQN) != null) { |
| return true; |
| } |
| } |
| if (checkHierarchy) { |
| if (listOwner instanceof PsiMethod) { |
| PsiMethod method = (PsiMethod)listOwner; |
| if (processed == null) processed = new THashSet<PsiMember>(); |
| if (!processed.add(method)) return false; |
| final PsiMethod[] superMethods = method.findSuperMethods(); |
| for (PsiMethod superMethod : superMethods) { |
| if (isAnnotated(superMethod, annotationFQN, checkHierarchy, skipExternal, processed)) return true; |
| } |
| } else if (listOwner instanceof PsiClass) { |
| final PsiClass clazz = (PsiClass)listOwner; |
| if (processed == null) processed = new THashSet<PsiMember>(); |
| if (!processed.add(clazz)) return false; |
| final PsiClass[] superClasses = clazz.getSupers(); |
| for (PsiClass superClass : superClasses) { |
| if (isAnnotated(superClass, annotationFQN, checkHierarchy, skipExternal, processed)) return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| public static boolean isAnnotatingApplicable(@NotNull PsiElement elt) { |
| return isAnnotatingApplicable(elt, NullableNotNullManager.getInstance(elt.getProject()).getDefaultNullable()); |
| } |
| |
| public static boolean isAnnotatingApplicable(@NotNull PsiElement elt, @NotNull String annotationFQN) { |
| final Project project = elt.getProject(); |
| return PsiUtil.isLanguageLevel5OrHigher(elt) && |
| JavaPsiFacade.getInstance(project).findClass(annotationFQN, elt.getResolveScope()) != null; |
| } |
| |
| public static boolean isJetbrainsAnnotation(@NonNls @NotNull String simpleName) { |
| return ArrayUtil.find(SIMPLE_NAMES, simpleName) != -1; |
| } |
| |
| /** |
| * Works similar to #isAnnotated(PsiModifierListOwner, Collection<String>) but supports FQN patters |
| * like "javax.ws.rs.*". Supports ending "*" only. |
| * |
| * @param owner modifier list |
| * @param annotations annotations qualified names or patterns. Patterns can have '*' at the end |
| * @return <code>true</code> if annotated of at least one annotation from the annotations list |
| */ |
| public static boolean checkAnnotatedUsingPatterns(@Nullable PsiModifierListOwner owner, @NotNull Collection<String> annotations) { |
| final PsiModifierList modList; |
| if (owner == null || (modList = owner.getModifierList()) == null) return false; |
| |
| List<String> fqns = null; |
| for (String fqn : annotations) { |
| boolean isPattern = fqn.endsWith("*"); |
| if (!isPattern && isAnnotated(owner, fqn, false)) { |
| return true; |
| } else if (isPattern) { |
| if (fqns == null) { |
| fqns = new ArrayList<String>(); |
| final PsiAnnotation[] annos = modList.getAnnotations(); |
| for (PsiAnnotation anno : annos) { |
| final String qName = anno.getQualifiedName(); |
| if (qName != null) { |
| fqns.add(qName); |
| } |
| } |
| if (fqns.isEmpty()) return false; |
| } |
| fqn = fqn.substring(0, fqn.length() - 2); |
| for (String annoFQN : fqns) { |
| if (annoFQN.startsWith(fqn)) { |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| @Nullable |
| public static PsiMethod getAnnotationMethod(@NotNull PsiNameValuePair pair) { |
| final PsiAnnotation annotation = PsiTreeUtil.getParentOfType(pair.getParent(), PsiAnnotation.class); |
| assert annotation != null; |
| |
| final String fqn = annotation.getQualifiedName(); |
| if (fqn == null) return null; |
| |
| final PsiClass psiClass = JavaPsiFacade.getInstance(pair.getProject()).findClass(fqn, pair.getResolveScope()); |
| if (psiClass != null && psiClass.isAnnotationType()) { |
| final String name = pair.getName(); |
| return ArrayUtil.getFirstElement(psiClass.findMethodsByName(name != null ? name : "value", false)); |
| } |
| return null; |
| } |
| |
| @NotNull |
| public static PsiAnnotation[] getAllAnnotations(@NotNull PsiModifierListOwner owner, |
| boolean inHierarchy, |
| @Nullable Set<PsiModifierListOwner> visited) { |
| return getAllAnnotations(owner, inHierarchy, visited, true); |
| } |
| |
| @NotNull |
| public static PsiAnnotation[] getAllAnnotations(@NotNull PsiModifierListOwner owner, |
| boolean inHierarchy, |
| @Nullable Set<PsiModifierListOwner> visited, boolean withInferred) { |
| final PsiModifierList list = owner.getModifierList(); |
| PsiAnnotation[] annotations = PsiAnnotation.EMPTY_ARRAY; |
| if (list != null) { |
| annotations = list.getAnnotations(); |
| } |
| |
| final Project project = owner.getProject(); |
| final PsiAnnotation[] externalAnnotations = ExternalAnnotationsManager.getInstance(project).findExternalAnnotations(owner); |
| if (externalAnnotations != null) { |
| annotations = ArrayUtil.mergeArrays(annotations, externalAnnotations, PsiAnnotation.ARRAY_FACTORY); |
| } |
| if (withInferred) { |
| final PsiAnnotation[] inferredAnnotations = InferredAnnotationsManager.getInstance(project).findInferredAnnotations(owner); |
| annotations = ArrayUtil.mergeArrays(annotations, inferredAnnotations, PsiAnnotation.ARRAY_FACTORY); |
| } |
| |
| if (inHierarchy) { |
| if (owner instanceof PsiClass) { |
| for (PsiClass superClass : ((PsiClass)owner).getSupers()) { |
| if (visited == null) visited = new THashSet<PsiModifierListOwner>(); |
| if (visited.add(superClass)) annotations = ArrayUtil.mergeArrays(annotations, getAllAnnotations(superClass, inHierarchy, visited)); |
| } |
| } |
| else if (owner instanceof PsiMethod) { |
| PsiMethod method = (PsiMethod)owner; |
| PsiClass aClass = method.getContainingClass(); |
| if (aClass != null) { |
| HierarchicalMethodSignature methodSignature = method.getHierarchicalMethodSignature(); |
| |
| final List<HierarchicalMethodSignature> superSignatures = methodSignature.getSuperSignatures(); |
| PsiResolveHelper resolveHelper = PsiResolveHelper.SERVICE.getInstance(aClass.getProject()); |
| for (final HierarchicalMethodSignature superSignature : superSignatures) { |
| final PsiMethod superMethod = superSignature.getMethod(); |
| if (visited == null) visited = new THashSet<PsiModifierListOwner>(); |
| if (!visited.add(superMethod)) continue; |
| if (!resolveHelper.isAccessible(superMethod, owner, null)) continue; |
| annotations = ArrayUtil.mergeArrays(annotations, getAllAnnotations(superMethod, inHierarchy, visited)); |
| } |
| } |
| } |
| else if (owner instanceof PsiParameter) { |
| PsiParameter parameter = (PsiParameter)owner; |
| PsiElement scope = parameter.getDeclarationScope(); |
| if (scope instanceof PsiMethod) { |
| PsiMethod method = (PsiMethod)scope; |
| PsiClass aClass = method.getContainingClass(); |
| PsiElement parent = parameter.getParent(); |
| if (aClass != null && parent instanceof PsiParameterList) { |
| int index = ((PsiParameterList)parent).getParameterIndex(parameter); |
| HierarchicalMethodSignature methodSignature = method.getHierarchicalMethodSignature(); |
| |
| final List<HierarchicalMethodSignature> superSignatures = methodSignature.getSuperSignatures(); |
| PsiResolveHelper resolveHelper = PsiResolveHelper.SERVICE.getInstance(aClass.getProject()); |
| for (final HierarchicalMethodSignature superSignature : superSignatures) { |
| final PsiMethod superMethod = superSignature.getMethod(); |
| if (visited == null) visited = new THashSet<PsiModifierListOwner>(); |
| if (!visited.add(superMethod)) continue; |
| if (!resolveHelper.isAccessible(superMethod, owner, null)) continue; |
| PsiParameter[] superParameters = superMethod.getParameterList().getParameters(); |
| if (index < superParameters.length) { |
| annotations = ArrayUtil.mergeArrays(annotations, getAllAnnotations(superParameters[index], inHierarchy, visited)); |
| } |
| } |
| } |
| } |
| } |
| } |
| return annotations; |
| } |
| |
| public static boolean isInsideAnnotation(PsiElement element) { |
| return PsiTreeUtil.getParentOfType(element, PsiNameValuePair.class, PsiArrayInitializerMemberValue.class) != null; |
| } |
| |
| public static boolean isInferredAnnotation(@NotNull PsiAnnotation annotation) { |
| return InferredAnnotationsManager.getInstance(annotation.getProject()).isInferredAnnotation(annotation); |
| } |
| |
| @Nullable |
| public static String getStringAttributeValue(@NotNull PsiAnnotation anno, @Nullable final String attributeName) { |
| PsiAnnotationMemberValue attrValue = anno.findAttributeValue(attributeName); |
| Object constValue = JavaPsiFacade.getInstance(anno.getProject()).getConstantEvaluationHelper().computeConstantExpression(attrValue); |
| return constValue instanceof String ? (String)constValue : null; |
| } |
| @Nullable |
| public static Boolean getBooleanAttributeValue(@NotNull PsiAnnotation anno, @Nullable final String attributeName) { |
| PsiAnnotationMemberValue attrValue = anno.findAttributeValue(attributeName); |
| Object constValue = JavaPsiFacade.getInstance(anno.getProject()).getConstantEvaluationHelper().computeConstantExpression(attrValue); |
| return constValue instanceof Boolean ? (Boolean)constValue : null; |
| } |
| |
| @Nullable |
| public static <T extends Annotation> T findAnnotationInHierarchy(@NotNull PsiModifierListOwner listOwner, @NotNull Class<T> annotationClass) { |
| PsiAnnotation annotation = findAnnotationInHierarchy(listOwner, Collections.singleton(annotationClass.getName())); |
| if (annotation == null) return null; |
| return (T)Proxy.newProxyInstance( |
| annotationClass.getClassLoader(), new Class<?>[]{annotationClass}, new AnnotationInvocationHandler(annotationClass, annotation)); |
| } |
| } |