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
;
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() {
47 * @see AnnotationUtilEx#getAnnotatedElementFor(com.intellij.psi.PsiElement, LookupType)
49 public enum LookupType
{
50 PREFER_CONTEXT
, PREFER_DECLARATION
, CONTEXT_ONLY
, DECLRARATION_ONLY
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").
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
) {
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();
79 else if (parent
instanceof PsiAssignmentExpression
) {
80 final PsiAssignmentExpression p
= (PsiAssignmentExpression
)parent
;
81 if (p
.getRExpression() == element
) {
82 element
= p
.getLExpression();
86 else if (parent
instanceof PsiReturnStatement
) {
87 final PsiMethod m
= PsiTreeUtil
.getParentOfType(parent
, PsiMethod
.class);
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
);
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
;
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;
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.
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]
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.
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
};
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
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
;
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
) {
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
;
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()));
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()));
266 return all
.toArray(new PsiAnnotation
[all
.size()]);
269 return modifierList
.getAnnotations();