check for annotations in hierarchy (IDEADEV-41871)
[fedora-idea.git] / java / openapi / src / com / intellij / codeInsight / AnnotationUtil.java
blob90a565f152432042a4b573b8ed1b7d51f4ff9a9a
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.codeInsight;
18 import com.intellij.psi.*;
19 import com.intellij.psi.util.PsiUtil;
20 import com.intellij.util.ArrayUtil;
21 import gnu.trove.THashSet;
22 import org.jetbrains.annotations.NonNls;
23 import org.jetbrains.annotations.NotNull;
24 import org.jetbrains.annotations.Nullable;
26 import java.util.*;
28 /**
29 * @author max
31 public class AnnotationUtil {
32 /**
33 * The full qualified name of the standard Nullable annotation.
35 public static final String NULLABLE = "org.jetbrains.annotations.Nullable";
37 /**
38 * The full qualified name of the standard NotNull annotation.
40 public static final String NOT_NULL = "org.jetbrains.annotations.NotNull";
42 @NonNls public static final String NOT_NULL_SIMPLE_NAME = "NotNull";
44 @NonNls public static final String NULLABLE_SIMPLE_NAME = "Nullable";
46 /**
47 * The full qualified name of the standard NonNls annotation.
49 * @since 5.0.1
51 public static final String NON_NLS = "org.jetbrains.annotations.NonNls";
52 public static final String NLS = "org.jetbrains.annotations.Nls";
53 public static final String PROPERTY_KEY = "org.jetbrains.annotations.PropertyKey";
54 @NonNls public static final String PROPERTY_KEY_RESOURCE_BUNDLE_PARAMETER = "resourceBundle";
56 @NonNls public static final String NON_NLS_SIMPLE_NAME = "NonNls";
57 @NonNls public static final String PROPERTY_KEY_SIMPLE_NAME = "PropertyKey";
59 public static final String TEST_ONLY = "org.jetbrains.annotations.TestOnly";
60 @NonNls public static final String TEST_ONLY_SIMPLE_NAME = "TestOnly";
62 public static final String LANGUAGE = "org.intellij.lang.annotations.Language";
64 public static final Set<String> ALL_ANNOTATIONS;
66 @NonNls private static final String[] SIMPLE_NAMES =
67 {NOT_NULL_SIMPLE_NAME, NULLABLE_SIMPLE_NAME, NON_NLS_SIMPLE_NAME, PROPERTY_KEY_SIMPLE_NAME, TEST_ONLY_SIMPLE_NAME,
68 "Language", "Identifier", "Pattern", "PrintFormat", "RegExp", "Subst"};
69 public static final String TARGET_ANNOTATION_FQ_NAME = "java.lang.annotation.Target";
71 static {
72 ALL_ANNOTATIONS = new HashSet<String>(2);
73 ALL_ANNOTATIONS.add(NULLABLE);
74 ALL_ANNOTATIONS.add(NOT_NULL);
77 public static boolean isNullable(@NotNull PsiModifierListOwner owner) {
78 return !isNotNull(owner) && isAnnotated(owner, NULLABLE, true);
81 public static boolean isNotNull(@NotNull PsiModifierListOwner owner) {
82 return isAnnotated(owner, NOT_NULL, true);
85 @Nullable
86 public static PsiAnnotation findAnnotation(PsiModifierListOwner listOwner, @NotNull String... annotationNames) {
87 return findAnnotation(listOwner, false, annotationNames);
90 @Nullable
91 public static PsiAnnotation findAnnotation(PsiModifierListOwner listOwner, final boolean skipExternal, @NotNull String... annotationNames) {
92 if (annotationNames.length == 0) return null;
93 Set<String> set = annotationNames.length == 1 ? Collections.singleton(annotationNames[0]) : new HashSet<String>(Arrays.asList(annotationNames));
94 return findAnnotation(listOwner, set, skipExternal);
97 @Nullable
98 public static PsiAnnotation findAnnotation(PsiModifierListOwner listOwner, @NotNull Set<String> annotationNames) {
99 return findAnnotation(listOwner, (Collection<String>)annotationNames);
102 @Nullable
103 public static PsiAnnotation findAnnotation(@Nullable PsiModifierListOwner listOwner, Collection<String> annotationNames) {
104 return findAnnotation(listOwner, annotationNames, false);
107 @Nullable
108 public static PsiAnnotation findAnnotation(@Nullable PsiModifierListOwner listOwner, Collection<String> annotationNames,
109 final boolean skipExternal) {
110 if (listOwner == null) return null;
111 final PsiModifierList list = listOwner.getModifierList();
112 if (list == null) return null;
113 final PsiAnnotation[] allAnnotations = list.getAnnotations();
114 for (PsiAnnotation annotation : allAnnotations) {
115 String qualifiedName = annotation.getQualifiedName();
116 if (annotationNames.contains(qualifiedName)) {
117 return annotation;
120 if (!skipExternal) {
121 final ExternalAnnotationsManager annotationsManager = ExternalAnnotationsManager.getInstance(listOwner.getProject());
122 for (String annotationName : annotationNames) {
123 final PsiAnnotation annotation = annotationsManager.findExternalAnnotation(listOwner, annotationName);
124 if (annotation != null) {
125 return annotation;
129 return null;
132 @NotNull
133 public static PsiAnnotation[] findAnnotations(final PsiModifierListOwner modifierListOwner, @NotNull Collection<String> annotationNames) {
134 if (modifierListOwner == null) return PsiAnnotation.EMPTY_ARRAY;
135 final PsiModifierList modifierList = modifierListOwner.getModifierList();
136 if (modifierList == null) return PsiAnnotation.EMPTY_ARRAY;
137 final PsiAnnotation[] annotations = modifierList.getAnnotations();
138 ArrayList<PsiAnnotation> result = null;
139 for (final PsiAnnotation psiAnnotation : annotations) {
140 if (annotationNames.contains(psiAnnotation.getQualifiedName())) {
141 if (result == null) result = new ArrayList<PsiAnnotation>();
142 result.add(psiAnnotation);
145 return result == null ? PsiAnnotation.EMPTY_ARRAY : result.toArray(new PsiAnnotation[result.size()]);
148 @Nullable
149 public static PsiAnnotation findAnnotationInHierarchy(PsiModifierListOwner listOwner, Set<String> annotationNames) {
150 PsiAnnotation directAnnotation = findAnnotation(listOwner, annotationNames);
151 if (directAnnotation != null) return directAnnotation;
152 if (listOwner instanceof PsiMethod) {
153 PsiMethod method = (PsiMethod)listOwner;
154 PsiClass aClass = method.getContainingClass();
155 if (aClass == null) return null;
156 HierarchicalMethodSignature methodSignature = method.getHierarchicalMethodSignature();
157 return findAnnotationInHierarchy(methodSignature, annotationNames, method, null);
158 } else if (listOwner instanceof PsiClass) {
159 return findAnnotationInHierarchy(((PsiClass)listOwner), annotationNames, null);
161 return null;
164 @Nullable
165 private static PsiAnnotation findAnnotationInHierarchy(final @NotNull PsiClass psiClass, final Set<String> annotationNames, Set<PsiClass> processed) {
166 final PsiClass[] superClasses = psiClass.getSupers();
167 for (final PsiClass superClass : superClasses) {
168 if (processed == null) processed = new THashSet<PsiClass>();
169 if (!processed.add(superClass)) return null;
170 final PsiAnnotation annotation = findAnnotation(superClass, annotationNames);
171 if (annotation != null) return annotation;
172 final PsiAnnotation annotationInHierarchy = findAnnotationInHierarchy(superClass, annotationNames, processed);
173 if (annotationInHierarchy != null) return annotationInHierarchy;
175 return null;
178 @Nullable
179 private static PsiAnnotation findAnnotationInHierarchy(HierarchicalMethodSignature signature,
180 Set<String> annotationNames,
181 PsiElement place,
182 Set<PsiMethod> processed) {
183 final List<HierarchicalMethodSignature> superSignatures = signature.getSuperSignatures();
184 final PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(place.getProject()).getResolveHelper();
185 for (final HierarchicalMethodSignature superSignature : superSignatures) {
186 final PsiMethod superMethod = superSignature.getMethod();
187 if (processed == null) processed = new THashSet<PsiMethod>();
188 if (!processed.add(superMethod)) continue;
189 if (!resolveHelper.isAccessible(superMethod, place, null)) continue;
190 PsiAnnotation direct = findAnnotation(superMethod, annotationNames);
191 if (direct != null) return direct;
192 PsiAnnotation superResult = findAnnotationInHierarchy(superSignature, annotationNames, place, processed);
193 if (superResult != null) return superResult;
196 return null;
199 public static boolean isAnnotated(PsiModifierListOwner listOwner, Collection<String> annotations) {
200 for (String annotation : annotations) {
201 if (isAnnotated(listOwner, annotation, false)) return true;
203 return false;
206 public static boolean isAnnotated(@NotNull PsiModifierListOwner listOwner, @NonNls String annotationFQN, boolean checkHierarchy) {
207 return isAnnotated(listOwner, annotationFQN, checkHierarchy, false, null);
210 public static boolean isAnnotated(@NotNull PsiModifierListOwner listOwner, @NonNls String annotationFQN, boolean checkHierarchy,
211 boolean skipExternal) {
212 return isAnnotated(listOwner, annotationFQN, checkHierarchy, skipExternal, null);
215 private static boolean isAnnotated(@NotNull PsiModifierListOwner listOwner,
216 @NonNls String annotationFQN,
217 boolean checkHierarchy, final boolean skipExternal, Set<PsiMember> processed) {
218 if (!listOwner.isValid()) return false;
219 final PsiModifierList modifierList = listOwner.getModifierList();
220 if (modifierList == null) return false;
221 PsiAnnotation annotation = modifierList.findAnnotation(annotationFQN);
222 if (annotation != null) return true;
223 if (!skipExternal && ExternalAnnotationsManager.getInstance(listOwner.getProject()).findExternalAnnotation(listOwner, annotationFQN) != null) {
224 return true;
226 if (checkHierarchy) {
227 if (listOwner instanceof PsiMethod) {
228 PsiMethod method = (PsiMethod)listOwner;
229 if (processed == null) processed = new THashSet<PsiMember>();
230 if (!processed.add(method)) return false;
231 final PsiMethod[] superMethods = method.findSuperMethods();
232 for (PsiMethod superMethod : superMethods) {
233 if (isAnnotated(superMethod, annotationFQN, checkHierarchy, skipExternal, processed)) return true;
235 } else if (listOwner instanceof PsiClass) {
236 final PsiClass clazz = (PsiClass)listOwner;
237 if (processed == null) processed = new THashSet<PsiMember>();
238 if (!processed.add(clazz)) return false;
239 final PsiClass[] superClasses = clazz.getSupers();
240 for (PsiClass superClass : superClasses) {
241 if (isAnnotated(superClass, annotationFQN, checkHierarchy, skipExternal, processed)) return true;
245 return false;
248 public static boolean isAnnotatingApplicable(PsiElement elt) {
249 return PsiUtil.isLanguageLevel5OrHigher(elt) &&
250 JavaPsiFacade.getInstance(elt.getProject()).findClass(NULLABLE, elt.getResolveScope()) != null;
253 public static boolean isJetbrainsAnnotation(@NonNls final String simpleName) {
254 return ArrayUtil.find(SIMPLE_NAMES, simpleName) != -1;
258 * Works similar to #isAnnotated(PsiModifierListOwner, Collection<String>) but supports FQN patters
259 * like "javax.ws.rs.*". Supports ending "*" only.
261 * @param owner modifier list
262 * @param annotations annotations qualified names or patterns. Patterns can have '*' at the end
263 * @return <code>true</code> if annotated of at least one annotation from the annotations list
265 public static boolean checkAnnotatedUsingPatterns(PsiModifierListOwner owner, Collection<String> annotations) {
266 List<String> fqns = null;
267 final PsiModifierList modList;
268 if (owner == null || (modList = owner.getModifierList()) == null) return false;
270 for (String fqn : annotations) {
271 if (! fqn.endsWith("*") && isAnnotated(owner, fqn, false)) {
272 return true;
273 } else {
274 if (fqns == null) {
275 fqns = new ArrayList<String>();
276 final PsiAnnotation[] annos = modList.getAnnotations();
277 for (PsiAnnotation anno : annos) {
278 final String qName = anno.getQualifiedName();
279 if (qName != null) {
280 fqns.add(qName);
283 if (fqns.isEmpty()) return false;
285 fqn = fqn.substring(0, fqn.length() - 2);
286 for (String annoFQN : fqns) {
287 if (annoFQN.startsWith(fqn)) {
288 return true;
293 return false;