remove incorrect assertion
[fedora-idea.git] / plugins / IntelliLang / src / org / intellij / plugins / intelliLang / util / AnnotationUtilEx.java
blobb7044f422f2c56375819d53a0ec187b34bcc9ac4
1 /*
2 * Copyright 2006 Sascha Weinreuter
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 org.intellij.plugins.intelliLang.util;
18 import com.intellij.codeInsight.AnnotationUtil;
19 import com.intellij.openapi.util.Comparing;
20 import com.intellij.openapi.util.Pair;
21 import com.intellij.psi.*;
22 import com.intellij.psi.impl.PsiConstantEvaluationHelperImpl;
23 import com.intellij.psi.search.searches.SuperMethodsSearch;
24 import com.intellij.psi.util.MethodSignatureBackedByPsiMethod;
25 import com.intellij.psi.util.PsiTreeUtil;
26 import com.intellij.util.ArrayUtil;
27 import com.intellij.util.Processor;
28 import gnu.trove.Equality;
29 import org.jetbrains.annotations.NonNls;
30 import org.jetbrains.annotations.NotNull;
31 import org.jetbrains.annotations.Nullable;
33 import java.util.Arrays;
34 import java.util.HashSet;
35 import java.util.Set;
37 /**
38 * Contains some extended utility functions for dealing with annotations.
40 public class AnnotationUtilEx {
41 private static final PsiConstantEvaluationHelperImpl CONSTANT_EVALUATION_HELPER = new PsiConstantEvaluationHelperImpl();
43 private AnnotationUtilEx() {
46 /**
47 * @see AnnotationUtilEx#getAnnotatedElementFor(com.intellij.psi.PsiElement, LookupType)
49 public enum LookupType {
50 PREFER_CONTEXT, PREFER_DECLARATION, CONTEXT_ONLY, DECLRARATION_ONLY
53 /**
54 * Determines the PsiModifierListOwner for the passed element depending of the specified LookupType. The LookupType
55 * decides whether to prefer the element a reference expressions resolves to, or the element that is implied by the
56 * usage context ("expected type").
58 @Nullable
59 public static PsiModifierListOwner getAnnotatedElementFor(@Nullable PsiElement element, LookupType type) {
60 while (element != null) {
61 if (type == LookupType.PREFER_DECLARATION || type == LookupType.DECLRARATION_ONLY) {
62 if (element instanceof PsiReferenceExpression) {
63 final PsiElement e = ((PsiReferenceExpression)element).resolve();
64 if (e instanceof PsiModifierListOwner) {
65 return (PsiModifierListOwner)e;
67 if (type == LookupType.DECLRARATION_ONLY) {
68 return null;
72 element = ContextComputationProcessor.getTopLevelInjectionTarget(element);
73 final PsiElement parent = element.getParent();
75 if (element instanceof PsiAssignmentExpression && ((PsiAssignmentExpression)element).getOperationSign().getTokenType() == JavaTokenType.PLUSEQ) {
76 element = ((PsiAssignmentExpression)element).getLExpression();
77 continue;
79 else if (parent instanceof PsiAssignmentExpression) {
80 final PsiAssignmentExpression p = (PsiAssignmentExpression)parent;
81 if (p.getRExpression() == element) {
82 element = p.getLExpression();
83 continue;
86 else if (parent instanceof PsiReturnStatement) {
87 final PsiMethod m = PsiTreeUtil.getParentOfType(parent, PsiMethod.class);
88 if (m != null) {
89 return m;
92 else if (parent instanceof PsiModifierListOwner) {
93 return (PsiModifierListOwner)parent;
95 else if (parent instanceof PsiArrayInitializerMemberValue) {
96 final PsiArrayInitializerMemberValue value = (PsiArrayInitializerMemberValue)parent;
97 final PsiElement pair = value.getParent();
98 if (pair instanceof PsiNameValuePair) {
99 return getAnnotationMethod((PsiNameValuePair)pair, element);
102 else if (parent instanceof PsiNameValuePair) {
103 return getAnnotationMethod((PsiNameValuePair)parent, element);
105 else {
106 return PsiUtilEx.getParameterForArgument(element);
109 // If no annotation has been found through the usage context, check if the element
110 // (i.e. the element the reference refers to) is annotated itself
111 if (type != LookupType.DECLRARATION_ONLY) {
112 if (element instanceof PsiReferenceExpression) {
113 final PsiElement e = ((PsiReferenceExpression)element).resolve();
114 if (e instanceof PsiModifierListOwner) {
115 return (PsiModifierListOwner)e;
119 return null;
121 return null;
124 @Nullable
125 private static PsiModifierListOwner getAnnotationMethod(PsiNameValuePair pair, PsiElement element) {
126 final PsiAnnotation annotation = PsiTreeUtil.getParentOfType(pair.getParent(), PsiAnnotation.class);
127 assert annotation != null;
129 final String fqn = annotation.getQualifiedName();
130 if (fqn == null) return null;
132 final PsiClass psiClass = JavaPsiFacade.getInstance(element.getProject()).findClass(fqn, element.getResolveScope());
133 if (psiClass != null && psiClass.isAnnotationType()) {
134 final String name = pair.getName();
135 final PsiMethod[] methods = psiClass.findMethodsByName(name != null ? name : "value", false);
136 return methods.length > 0 ? methods[0] : null;
138 return null;
142 * Utility method to obtain annotations of a specific type from the supplied PsiModifierListOwner.
143 * For optimization reasons, this method only looks at elements of type java.lang.String.
144 * <p/>
145 * The parameter <code>allowIndirect</code> determines if the method should look for indirect annotations, i.e.
146 * annotations which have themselves been annotated by the supplied annotation name. Currently, this only allows
147 * one level of indirection and returns an array of [base-annotation, indirect annotation]
148 * <p/>
149 * The <code>annotationName</code> parameter is a pair of the target annotation class' fully qualified name as a
150 * String and as a Set. This is done for performance reasons because the Set is required by the
151 * {@link com.intellij.codeInsight.AnnotationUtil} utility class and allows to avoid unecessary object constructions.
153 @NotNull
154 public static PsiAnnotation[] getAnnotationFrom(PsiModifierListOwner owner,
155 Pair<String, ? extends Set<String>> annotationName,
156 boolean allowIndirect,
157 boolean inHierarchy) {
158 if (!PsiUtilEx.isLanguageAnnotationTarget(owner)) return PsiAnnotation.EMPTY_ARRAY;
160 final PsiAnnotation directAnnotation = inHierarchy?
161 AnnotationUtil.findAnnotationInHierarchy(owner, annotationName.second) :
162 AnnotationUtil.findAnnotation(owner, annotationName.second);
163 if (directAnnotation != null) {
164 return new PsiAnnotation[]{directAnnotation};
166 if (allowIndirect) {
167 final PsiAnnotation[] annotations = getAnnotations(owner, inHierarchy);
168 for (PsiAnnotation annotation : annotations) {
169 PsiJavaCodeReferenceElement nameReference = annotation.getNameReferenceElement();
170 if (nameReference == null) continue;
171 PsiElement resolved = nameReference.resolve();
172 if (resolved instanceof PsiClass) {
173 final PsiAnnotation psiAnnotation = AnnotationUtil.findAnnotationInHierarchy((PsiModifierListOwner)resolved, annotationName.second);
174 if (psiAnnotation != null) {
175 return new PsiAnnotation[]{psiAnnotation, annotation};
180 return PsiAnnotation.EMPTY_ARRAY;
183 public static PsiAnnotation[] getAnnotationFrom(@NotNull PsiModifierListOwner owner,
184 @NotNull Pair<String, ? extends Set<String>> annotationName,
185 boolean allowIndirect) {
186 return getAnnotationFrom(owner, annotationName, allowIndirect, true);
190 * Calculates the value of the annotation's attribute referenced by the <code>attr</code> parameter by trying to
191 * find the attribute in the supplied list of annotations and calculating the constant value for the first attribute
192 * it finds.
194 @Nullable
195 public static String calcAnnotationValue(PsiAnnotation[] annotation, @NonNls String attr) {
196 for (PsiAnnotation psiAnnotation : annotation) {
197 final String value = calcAnnotationValue(psiAnnotation, attr);
198 if (value != null) return value;
200 return null;
203 @Nullable
204 public static String calcAnnotationValue(@NotNull PsiAnnotation annotation, @NonNls String attr) {
205 PsiElement value = annotation.findAttributeValue(attr);
206 if (value instanceof PsiExpression) {
207 Object o = CONSTANT_EVALUATION_HELPER.computeConstantExpression(value);
208 if (o instanceof String) {
209 return (String)o;
212 return null;
216 * Returns all annotations for <code>listOwner</code>, possibly walking up the method hierarchy.
218 * @see com.intellij.codeInsight.AnnotationUtil#isAnnotated(com.intellij.psi.PsiModifierListOwner, java.lang.String, boolean)
220 private static PsiAnnotation[] getAnnotations(@NotNull PsiModifierListOwner listOwner, boolean inHierarchy) {
221 final PsiModifierList modifierList = listOwner.getModifierList();
222 if (modifierList == null) {
223 return PsiAnnotation.EMPTY_ARRAY;
225 if (!inHierarchy) {
226 return modifierList.getAnnotations();
228 final Set<PsiAnnotation> all = new HashSet<PsiAnnotation>() {
229 public boolean add(PsiAnnotation o) {
230 // don't overwrite "higher level" annotations
231 return !contains(o) && super.add(o);
234 if (listOwner instanceof PsiMethod) {
235 all.addAll(Arrays.asList(modifierList.getAnnotations()));
236 SuperMethodsSearch.search((PsiMethod)listOwner, null, true, true).forEach(new Processor<MethodSignatureBackedByPsiMethod>() {
237 public boolean process(final MethodSignatureBackedByPsiMethod superMethod) {
238 all.addAll(Arrays.asList(superMethod.getMethod().getModifierList().getAnnotations()));
239 return true;
242 return all.toArray(new PsiAnnotation[all.size()]);
244 if (listOwner instanceof PsiParameter) {
245 PsiParameter parameter = (PsiParameter)listOwner;
246 PsiElement declarationScope = parameter.getDeclarationScope();
247 if (declarationScope instanceof PsiMethod && parameter.getParent() == ((PsiMethod)declarationScope).getParameterList()) {
248 final PsiMethod method = (PsiMethod)declarationScope;
249 final int byNameWorkAround = ArrayUtil.indexOf(method.getParameterList().getParameters(), parameter, new Equality<PsiParameter>() {
250 public boolean equals(PsiParameter o1, PsiParameter o2) {
251 return Comparing.equal(o1.getName(), o2.getName());
254 final int parameterIndex = byNameWorkAround > -1? byNameWorkAround : method.getParameterList().getParameterIndex(parameter);
255 all.addAll(Arrays.asList(modifierList.getAnnotations()));
256 SuperMethodsSearch.search(method, null, true, true).forEach(new Processor<MethodSignatureBackedByPsiMethod>() {
257 public boolean process(final MethodSignatureBackedByPsiMethod superMethod) {
258 PsiParameter superParameter = superMethod.getMethod().getParameterList().getParameters()[parameterIndex];
259 PsiModifierList modifierList = superParameter.getModifierList();
260 if (modifierList != null) {
261 all.addAll(Arrays.asList(modifierList.getAnnotations()));
263 return true;
266 return all.toArray(new PsiAnnotation[all.size()]);
269 return modifierList.getAnnotations();