Files
openide/java/java-psi-api/src/com/intellij/codeInsight/AnnotationUtil.java
Tagir Valeev c79ec86e5d [java-psi] Rename parameter
GitOrigin-RevId: 670591fc3392e669d34eee803b0905aab84e8e82
2024-09-17 07:28:39 +00:00

831 lines
36 KiB
Java

// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.pom.java.JavaFeature;
import com.intellij.psi.*;
import com.intellij.psi.util.*;
import com.intellij.util.*;
import com.intellij.util.containers.ConcurrentFactoryMap;
import com.intellij.util.containers.ContainerUtil;
import org.intellij.lang.annotations.MagicConstant;
import org.jetbrains.annotations.*;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.*;
import java.util.stream.Stream;
@ApiStatus.NonExtendable
public class AnnotationUtil {
public static final String NULLABLE = "org.jetbrains.annotations.Nullable";
public static final String UNKNOWN_NULLABILITY = "org.jetbrains.annotations.UnknownNullability";
public static final String NOT_NULL = "org.jetbrains.annotations.NotNull";
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";
public static final String PROPERTY_KEY_RESOURCE_BUNDLE_PARAMETER = "resourceBundle";
public static final String TEST_ONLY = "org.jetbrains.annotations.TestOnly";
public static final String LANGUAGE = "org.intellij.lang.annotations.Language";
@Nullable
public static PsiAnnotation findAnnotation(@Nullable PsiModifierListOwner listOwner, String @NotNull ... annotationNames) {
return findAnnotation(listOwner, false, annotationNames);
}
@Nullable
public static PsiAnnotation findAnnotation(@Nullable PsiModifierListOwner listOwner, boolean skipExternal, String @NotNull ... annotationNames) {
if (annotationNames.length == 0) return null;
Set<String> set = annotationNames.length == 1 ? Collections.singleton(annotationNames[0]) : ContainerUtil.newHashSet(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, boolean skipExternal) {
if (listOwner == null) return null;
List<PsiAnnotation> result = findAllAnnotations(listOwner, annotationNames, skipExternal);
return result.isEmpty() ? null : result.get(0);
}
/**
* Returns all annotations associated with {@code listOwner} having fully qualified names from {@code annotationNames},
* including repeatable annotations and annotations from several external annotations roots.
*
* @param listOwner element to search annotations of
* @param annotationNames fully-qualified annotations names to search for
* @param skipExternal {@code false} if external and inferred annotations must also be searched,
* {@code true} only to search for own annotations declared in source code
* @return all annotations of {@code listOwner}, including repeatable annotation
* and annotations from several source roots, having FQ names from {@code annotationNames}.
*/
@NotNull
public static List<PsiAnnotation> findAllAnnotations(@NotNull PsiModifierListOwner listOwner, @NotNull Collection<String> annotationNames,
boolean skipExternal) {
List<PsiAnnotation> ownAnnotations = findOwnAnnotations(listOwner, annotationNames);
List<PsiAnnotation> nonCodeAnnotations = skipExternal ? null : findNonCodeAnnotations(listOwner, annotationNames);
List<PsiAnnotation> annotations = null;
if (ownAnnotations != null || nonCodeAnnotations != null) {
annotations = new SmartList<>();
if (ownAnnotations != null) {
annotations.addAll(ownAnnotations);
}
if (nonCodeAnnotations != null) {
annotations.addAll(nonCodeAnnotations);
}
}
return annotations == null ? Collections.emptyList() : annotations;
}
@Nullable
private static List<PsiAnnotation> findOwnAnnotations(@NotNull final PsiModifierListOwner listOwner, @NotNull Iterable<String> annotationNames) {
final PsiModifierList list = listOwner.getModifierList();
if (list == null) {
return null;
}
List<PsiAnnotation> result = null;
for (PsiAnnotation annotation : list.getAnnotations()) {
if (ContainerUtil.exists(annotationNames, annotation::hasQualifiedName) && isApplicableToDeclaration(annotation, list)) {
if (result == null) {
result = new SmartList<>();
}
result.add(annotation);
}
}
return result;
}
private static boolean isApplicableToDeclaration(PsiAnnotation annotation, PsiModifierList list) {
PsiAnnotation.TargetType[] allTargets = AnnotationTargetUtil.getTargetsForLocation(list);
if (allTargets.length == 0) return true;
PsiAnnotation.TargetType[] nonTypeUse = Stream
.of(allTargets)
.filter(t -> t != PsiAnnotation.TargetType.TYPE_USE)
.toArray(PsiAnnotation.TargetType[]::new);
return AnnotationTargetUtil.findAnnotationTarget(annotation, nonTypeUse) != null;
}
private static final ParameterizedCachedValueProvider<Map<Collection<String>, List<PsiAnnotation>>, PsiModifierListOwner> NON_CODE_ANNOTATIONS_PROVIDER =
(PsiModifierListOwner listOwner) -> {
Map<Collection<String>, List<PsiAnnotation>> value = ConcurrentFactoryMap.createMap(
annotationNames -> {
PsiUtilCore.ensureValid(listOwner);
final Project project = listOwner.getProject();
List<PsiAnnotation> annotations = null;
final ExternalAnnotationsManager externalAnnotationsManager = ExternalAnnotationsManager.getInstance(project);
for (String annotationName : annotationNames) {
List<PsiAnnotation> externalAnnotations = externalAnnotationsManager.findExternalAnnotations(listOwner, annotationName);
if (!externalAnnotations.isEmpty()) {
if (annotations == null) {
annotations = new SmartList<>();
}
annotations.addAll(externalAnnotations);
}
}
final InferredAnnotationsManager inferredAnnotationsManager = InferredAnnotationsManager.getInstance(project);
for (String annotationName : annotationNames) {
final PsiAnnotation annotation = inferredAnnotationsManager.findInferredAnnotation(listOwner, annotationName);
if (annotation != null) {
if (annotations == null) {
annotations = new SmartList<>();
}
annotations.add(annotation);
}
}
return annotations;
}
);
return CachedValueProvider.Result.create(value, PsiModificationTracker.MODIFICATION_COUNT);
};
private static final Key<ParameterizedCachedValue<Map<Collection<String>, List<PsiAnnotation>>, PsiModifierListOwner>> NON_CODE_ANNOTATIONS_KEY =
Key.create("NON_CODE_ANNOTATIONS");
@Nullable
private static List<PsiAnnotation> findNonCodeAnnotations(@NotNull PsiModifierListOwner element,
@NotNull Collection<String> annotationNames) {
if (element instanceof PsiLocalVariable) {
// Non-code annotations for local variables are not supported: don't bother to search them
return null;
}
PsiModifierListOwner listOwner = AnnotationCacheOwnerNormalizer.normalize(element);
Map<Collection<String>, List<PsiAnnotation>> map = CachedValuesManager.getManager(element.getProject())
.getParameterizedCachedValue(
listOwner,
NON_CODE_ANNOTATIONS_KEY,
NON_CODE_ANNOTATIONS_PROVIDER,
false,
listOwner
);
return map.get(annotationNames);
}
public static PsiAnnotation @NotNull [] 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) {
String qualifiedName = psiAnnotation.getQualifiedName();
if (qualifiedName != null && annotationNames.contains(qualifiedName)) {
if (result == null) result = new ArrayList<>();
result.add(psiAnnotation);
}
}
return result == null ? PsiAnnotation.EMPTY_ARRAY : result.toArray(PsiAnnotation.EMPTY_ARRAY);
}
@NotNull
public static <T extends PsiModifierListOwner> List<T> getSuperAnnotationOwners(@NotNull T element) {
PsiModifierListOwner listOwner = AnnotationCacheOwnerNormalizer.normalize(element);
return CachedValuesManager.getCachedValue(listOwner, () -> {
Set<PsiModifierListOwner> result = new LinkedHashSet<>();
if (listOwner instanceof PsiMethod) {
if (!listOwner.hasModifierProperty(PsiModifier.STATIC)) {
collectSuperMethods(result, ((PsiMethod)listOwner).getHierarchicalMethodSignature(), listOwner,
JavaPsiFacade.getInstance(listOwner.getProject()).getResolveHelper());
}
}
else if (listOwner instanceof PsiClass) {
InheritanceUtil.processSupers((PsiClass)listOwner, false, Processors.cancelableCollectProcessor(result));
}
else if (listOwner instanceof PsiParameter) {
collectSuperParameters(result, (PsiParameter)listOwner);
}
List<T> list;
if(result.isEmpty()) {
list = Collections.emptyList();
}
else {
PsiModifierListOwner[] array = result.toArray(new PsiModifierListOwner[0]);
//noinspection unchecked
list = Arrays.asList((T[])array);
}
return CachedValueProvider.Result.create(list, PsiModificationTracker.MODIFICATION_COUNT);
});
}
@Nullable
public static PsiAnnotation findAnnotationInHierarchy(@NotNull final PsiModifierListOwner listOwner, @NotNull Set<String> annotationNames) {
return findAnnotationInHierarchy(listOwner, annotationNames, false);
}
@Nullable
public static PsiAnnotation findAnnotationInHierarchy(@NotNull final PsiModifierListOwner listOwner,
@NotNull Set<String> annotationNames, boolean skipExternal) {
AnnotationAndOwner result = findAnnotationAndOwnerInHierarchy(listOwner, annotationNames, skipExternal);
return result == null ? null : result.annotation;
}
static final class AnnotationAndOwner {
final @NotNull PsiModifierListOwner owner;
final @NotNull PsiAnnotation annotation;
AnnotationAndOwner(@NotNull PsiModifierListOwner owner, @NotNull PsiAnnotation annotation) {
this.owner = owner;
this.annotation = annotation;
}
}
@Nullable
static AnnotationAndOwner findAnnotationAndOwnerInHierarchy(@NotNull PsiModifierListOwner listOwner,
@NotNull Set<String> annotationNames,
boolean skipExternal) {
PsiAnnotation directAnnotation = findAnnotation(listOwner, annotationNames, skipExternal);
if (directAnnotation != null) return new AnnotationAndOwner(listOwner, directAnnotation);
for (PsiModifierListOwner superOwner : getSuperAnnotationOwners(listOwner)) {
PsiAnnotation annotation = findAnnotation(superOwner, annotationNames, skipExternal);
if (annotation != null) {
return new AnnotationAndOwner(superOwner, annotation);
}
}
return null;
}
private static void collectSuperParameters(@NotNull final Set<? super PsiModifierListOwner> result, @NotNull PsiParameter parameter) {
PsiElement parent = parameter.getParent();
if (!(parent instanceof PsiParameterList)) {
return;
}
final int index = ((PsiParameterList)parent).getParameterIndex(parameter);
Consumer<PsiMethod> forEachSuperMethod = method -> {
PsiParameter[] superParameters = method.getParameterList().getParameters();
if (index < superParameters.length) {
result.add(superParameters[index]);
}
};
PsiElement scope = parent.getParent();
if (scope instanceof PsiLambdaExpression) {
PsiMethod method = LambdaUtil.getFunctionalInterfaceMethod(((PsiLambdaExpression)scope).getFunctionalInterfaceType());
if (method != null) {
forEachSuperMethod.consume(method);
for (PsiMethod superMethod : getSuperAnnotationOwners(method)) {
forEachSuperMethod.consume(superMethod);
}
}
}
else if (scope instanceof PsiMethod) {
for (PsiMethod superMethod : getSuperAnnotationOwners((PsiMethod)scope)) {
forEachSuperMethod.consume(superMethod);
}
}
}
private static void collectSuperMethods(@NotNull Set<? super PsiModifierListOwner> result,
@NotNull HierarchicalMethodSignature signature,
@NotNull PsiElement place,
@NotNull PsiResolveHelper resolveHelper) {
for (final HierarchicalMethodSignature superSignature : signature.getSuperSignatures()) {
final PsiMethod superMethod = superSignature.getMethod();
if (!resolveHelper.isAccessible(superMethod, place, null)) continue;
if (!result.add(superMethod)) continue;
collectSuperMethods(result, superSignature, place, resolveHelper);
}
}
public static final int CHECK_HIERARCHY = 0x01;
public static final int CHECK_EXTERNAL = 0x02;
public static final int CHECK_INFERRED = 0x04;
public static final int CHECK_TYPE = 0x08;
@MagicConstant(flags = {CHECK_HIERARCHY, CHECK_EXTERNAL, CHECK_INFERRED, CHECK_TYPE})
@Target({ElementType.PARAMETER, ElementType.METHOD})
private @interface Flags { }
public static boolean isAnnotated(@NotNull PsiModifierListOwner listOwner, @NotNull Collection<String> annotations, @Flags int flags) {
return ContainerUtil.exists(annotations, annotation -> isAnnotated(listOwner, annotation, flags, null));
}
public static boolean isAnnotated(@NotNull PsiModifierListOwner listOwner, @NotNull String annotationFqn, @Flags int flags) {
return isAnnotated(listOwner, annotationFqn, flags, null);
}
private static boolean isAnnotated(@NotNull PsiModifierListOwner listOwner, @NotNull String annotationFQN, @Flags int flags, @Nullable Set<? super PsiMember> processed) {
PsiModifierList modifierList = listOwner.getModifierList();
if (modifierList == null) return false;
PsiAnnotation annotation = modifierList.findAnnotation(annotationFQN);
if (annotation != null) return true;
if (BitUtil.isSet(flags, CHECK_TYPE)) {
PsiType type = null;
if (listOwner instanceof PsiMethod) {
type = ((PsiMethod)listOwner).getReturnType();
}
else if (listOwner instanceof PsiParameter &&
listOwner.getParent() instanceof PsiParameterList &&
listOwner.getParent().getParent() instanceof PsiLambdaExpression) {
if (((PsiParameter)listOwner).getTypeElement() != null) {
// Avoid lambda parameter type inference: anyway it doesn't have any explicit annotations
type = ((PsiParameter)listOwner).getType();
}
}
else if (listOwner instanceof PsiVariable) {
type = ((PsiVariable)listOwner).getType();
}
if (type != null && type.hasAnnotation(annotationFQN)) {
return true;
}
}
if (BitUtil.isSet(flags, CHECK_EXTERNAL)) {
Project project = listOwner.getProject();
if (ExternalAnnotationsManager.getInstance(project).findExternalAnnotation(listOwner, annotationFQN) != null) {
return true;
}
}
if (BitUtil.isSet(flags, CHECK_INFERRED)) {
Project project = listOwner.getProject();
if (InferredAnnotationsManager.getInstance(project).findInferredAnnotation(listOwner, annotationFQN) != null) {
return true;
}
}
if (BitUtil.isSet(flags, CHECK_HIERARCHY)) {
if (listOwner instanceof PsiMethod) {
PsiMethod method = (PsiMethod)listOwner;
if (processed == null) {
processed = new HashSet<>();
}
if (!processed.add(method)) {
return false;
}
for (PsiMethod superMethod : method.findSuperMethods()) {
if (isAnnotated(superMethod, annotationFQN, flags, processed)) {
return true;
}
}
}
else if (listOwner instanceof PsiClass) {
PsiClass clazz = (PsiClass)listOwner;
if (processed == null) {
processed = new HashSet<>();
}
if (!processed.add(clazz)) {
return false;
}
for (PsiClass superClass : clazz.getSupers()) {
if (isAnnotated(superClass, annotationFQN, flags, 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.isAvailable(JavaFeature.ANNOTATIONS, elt) &&
JavaPsiFacade.getInstance(project).findClass(annotationFQN, elt.getResolveScope()) != null;
}
/**
* 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} if annotated of at least one annotation from the annotations list
*/
@Contract("null,_ -> false")
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, 0)) {
return true;
}
else if (isPattern) {
if (fqns == null) {
fqns = new ArrayList<>();
final PsiAnnotation[] ownAnnotations = modList.getAnnotations();
for (PsiAnnotation anno : ownAnnotations) {
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;
}
public static PsiAnnotation @NotNull [] getAllAnnotations(@NotNull PsiModifierListOwner owner,
boolean inHierarchy,
@Nullable Set<? super PsiModifierListOwner> visited) {
return getAllAnnotations(owner, inHierarchy, visited, true);
}
public static PsiAnnotation @NotNull [] getAllAnnotations(@NotNull PsiModifierListOwner owner,
boolean inHierarchy,
@Nullable Set<? super 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 HashSet<>();
}
if (visited.add(superClass)) annotations = ArrayUtil.mergeArrays(annotations, getAllAnnotations(superClass, true, visited, withInferred));
}
}
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.getInstance(aClass.getProject());
for (final HierarchicalMethodSignature superSignature : superSignatures) {
final PsiMethod superMethod = superSignature.getMethod();
if (visited == null) {
visited = new HashSet<>();
}
if (!visited.add(superMethod) || !resolveHelper.isAccessible(superMethod, owner, null)) {
continue;
}
annotations = ArrayUtil.mergeArrays(annotations, getAllAnnotations(superMethod, true, visited, withInferred));
}
}
}
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.getInstance(aClass.getProject());
for (final HierarchicalMethodSignature superSignature : superSignatures) {
final PsiMethod superMethod = superSignature.getMethod();
if (visited == null) {
visited = new HashSet<>();
}
if (!visited.add(superMethod) || !resolveHelper.isAccessible(superMethod, owner, null)) {
continue;
}
PsiParameter[] superParameters = superMethod.getParameterList().getParameters();
if (index < superParameters.length) {
annotations = ArrayUtil.mergeArrays(annotations, getAllAnnotations(superParameters[index], true, visited, withInferred));
}
}
}
}
}
}
return annotations;
}
public static boolean isInsideAnnotation(@NotNull PsiElement element) {
for (int level = 0; level<4; level++) {
if (element instanceof PsiNameValuePair) return true;
element = element.getParent();
if (element == null) return false;
}
return false;
}
public static boolean isInferredAnnotation(@NotNull PsiAnnotation annotation) {
return InferredAnnotationsManager.getInstance(annotation.getProject()).isInferredAnnotation(annotation);
}
public static boolean isExternalAnnotation(@NotNull PsiAnnotation annotation) {
return ExternalAnnotationsManager.getInstance(annotation.getProject()).isExternalAnnotation(annotation);
}
@Nullable
public static @NlsSafe String getStringAttributeValue(@NotNull PsiAnnotation anno, @Nullable final String attributeName) {
PsiAnnotationMemberValue attrValue = anno.findAttributeValue(attributeName);
return attrValue == null ? null : getStringAttributeValue(attrValue);
}
@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 Long getLongAttributeValue(@NotNull PsiAnnotation anno, @Nullable final String attributeName) {
PsiAnnotationMemberValue attrValue = anno.findAttributeValue(attributeName);
Object constValue = JavaPsiFacade.getInstance(anno.getProject()).getConstantEvaluationHelper().computeConstantExpression(attrValue);
return constValue instanceof Number ? ((Number)constValue).longValue() : null;
}
@Nullable
public static String getDeclaredStringAttributeValue(@NotNull PsiAnnotation anno, @Nullable final String attributeName) {
PsiAnnotationMemberValue attrValue = anno.findDeclaredAttributeValue(attributeName);
return attrValue == null ? null : getStringAttributeValue(attrValue);
}
@Nullable
public static String getStringAttributeValue(@NotNull PsiAnnotationMemberValue attrValue) {
PsiConstantEvaluationHelper evaluationHelper = JavaPsiFacade.getInstance(attrValue.getProject()).getConstantEvaluationHelper();
Object constValue = evaluationHelper.computeConstantExpression(attrValue);
return constValue instanceof String ? (String)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;
AnnotationInvocationHandler handler = new AnnotationInvocationHandler(annotationClass, annotation);
return ReflectionUtil.proxy(annotationClass, handler);
}
/**
* Get an attribute as an instance of {@link PsiNameValuePair} by its name from the annotation
* @param annotation annotation to look for the attribute
* @param attributeName attribute name
* @return an attribute as an instance of {@link PsiNameValuePair} or null
*/
@Contract(pure = true)
@Nullable
public static PsiNameValuePair findDeclaredAttribute(@NotNull PsiAnnotation annotation, @Nullable("null means 'value'") @NonNls String attributeName) {
if (PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME.equals(attributeName)) attributeName = null;
for (PsiNameValuePair attribute : annotation.getParameterList().getAttributes()) {
final String name = attribute.getName();
if (Objects.equals(name, attributeName) || attributeName == null && PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME.equals(name)) {
return attribute;
}
}
return null;
}
public static boolean equal(@Nullable PsiAnnotation a, @Nullable PsiAnnotation b) {
if (a == null) {
return b == null;
}
if (b == null) {
return false;
}
final String name = a.getQualifiedName();
if (name == null || !name.equals(b.getQualifiedName())) {
return false;
}
final Map<String, PsiAnnotationMemberValue> valueMap1 = new HashMap<>(2);
final Map<String, PsiAnnotationMemberValue> valueMap2 = new HashMap<>(2);
if (!fillValueMap(a.getParameterList(), valueMap1) || !fillValueMap(b.getParameterList(), valueMap2) ||
valueMap1.size() != valueMap2.size()) {
return false;
}
for (Map.Entry<String, PsiAnnotationMemberValue> entry : valueMap1.entrySet()) {
if (!equal(entry.getValue(), valueMap2.get(entry.getKey()))) {
return false;
}
}
return true;
}
private static boolean fillValueMap(@NotNull PsiAnnotationParameterList parameterList, @NotNull Map<String, PsiAnnotationMemberValue> valueMap) {
final PsiNameValuePair[] attributes1 = parameterList.getAttributes();
for (PsiNameValuePair attribute : attributes1) {
final PsiReference reference = attribute.getReference();
if (reference == null) {
return false;
}
final PsiElement target = reference.resolve();
if (!(target instanceof PsiAnnotationMethod)) {
return false;
}
final PsiAnnotationMethod annotationMethod = (PsiAnnotationMethod)target;
final PsiAnnotationMemberValue defaultValue = annotationMethod.getDefaultValue();
final PsiAnnotationMemberValue value = attribute.getValue();
if (equal(value, defaultValue)) {
continue;
}
final String name1 = attribute.getName();
valueMap.put(name1 == null ? PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME : name1, value);
}
return true;
}
public static boolean equal(@Nullable PsiAnnotationMemberValue value1, @Nullable PsiAnnotationMemberValue value2) {
if (value1 instanceof PsiArrayInitializerMemberValue && value2 instanceof PsiArrayInitializerMemberValue) {
final PsiAnnotationMemberValue[] initializers1 = ((PsiArrayInitializerMemberValue)value1).getInitializers();
final PsiAnnotationMemberValue[] initializers2 = ((PsiArrayInitializerMemberValue)value2).getInitializers();
if (initializers1.length != initializers2.length) {
return false;
}
return ArrayUtil.areEqual(initializers1, initializers2, AnnotationUtil::equal);
}
if (value1 != null && value2 != null) {
final PsiConstantEvaluationHelper constantEvaluationHelper =
JavaPsiFacade.getInstance(value1.getProject()).getConstantEvaluationHelper();
final Object const1 = constantEvaluationHelper.computeConstantExpression(value1);
final Object const2 = constantEvaluationHelper.computeConstantExpression(value2);
return const1 != null && const1.equals(const2);
}
return false;
}
/**
* Ignores Override and SuppressWarnings annotations.
*/
public static boolean equal(PsiAnnotation @NotNull [] annotations1, PsiAnnotation @NotNull [] annotations2) {
final Map<String, PsiAnnotation> map1 = buildAnnotationMap(annotations1);
final Map<String, PsiAnnotation> map2 = buildAnnotationMap(annotations2);
if (map1.size() != map2.size()) {
return false;
}
for (Map.Entry<String, PsiAnnotation> entry : map1.entrySet()) {
if (!equal(entry.getValue(), map2.get(entry.getKey()))) {
return false;
}
}
return true;
}
@NotNull
private static Map<String, PsiAnnotation> buildAnnotationMap(PsiAnnotation @NotNull [] annotations) {
final Map<String, PsiAnnotation> map = new HashMap<>();
for (PsiAnnotation annotation : annotations) {
map.put(annotation.getQualifiedName(), annotation);
}
map.remove(CommonClassNames.JAVA_LANG_OVERRIDE);
map.remove("java.lang.SuppressWarnings");
return map;
}
//<editor-fold desc="Deprecated stuff.">
private static final String[] SIMPLE_NAMES =
{"NotNull", "Nullable", "NonNls", "PropertyKey", "TestOnly", "Language", "Identifier", "Pattern", "PrintFormat", "RegExp", "Subst"};
/** @deprecated simple name is not enough for reliable identification */
@ApiStatus.ScheduledForRemoval
@Deprecated
public static boolean isJetbrainsAnnotation(@NotNull String simpleName) {
return ArrayUtil.find(SIMPLE_NAMES, simpleName) != -1;
}
/** @deprecated use {@link #isAnnotated(PsiModifierListOwner, Collection, int)} */
@Deprecated
@ApiStatus.ScheduledForRemoval
public static boolean isAnnotated(@NotNull PsiModifierListOwner listOwner, @NotNull Collection<String> annotations) {
return isAnnotated(listOwner, annotations, CHECK_TYPE);
}
/** @deprecated use {@link #isAnnotated(PsiModifierListOwner, String, int)} */
@Deprecated
@ApiStatus.ScheduledForRemoval
public static boolean isAnnotated(@NotNull PsiModifierListOwner listOwner, @NotNull String annotationFQN, boolean checkHierarchy) {
return isAnnotated(listOwner, annotationFQN, flags(checkHierarchy, true, true));
}
/** @deprecated use {@link #isAnnotated(PsiModifierListOwner, String, int)} */
@Deprecated
@ApiStatus.ScheduledForRemoval
public static boolean isAnnotated(@NotNull PsiModifierListOwner listOwner,
@NotNull String annotationFQN,
boolean checkHierarchy,
boolean skipExternal) {
return isAnnotated(listOwner, annotationFQN, flags(checkHierarchy, skipExternal, skipExternal));
}
@Flags
private static int flags(boolean checkHierarchy, boolean skipExternal, boolean skipInferred) {
int flags = CHECK_TYPE;
if (checkHierarchy) flags |= CHECK_HIERARCHY;
if (!skipExternal) flags |= CHECK_EXTERNAL;
if (!skipInferred) flags |= CHECK_INFERRED;
return flags;
}
//</editor-fold>
@NotNull
public static List<PsiAnnotationMemberValue> arrayAttributeValues(@Nullable PsiAnnotationMemberValue attributeValue) {
if (attributeValue instanceof PsiArrayInitializerMemberValue) {
return Arrays.asList(((PsiArrayInitializerMemberValue)attributeValue).getInitializers());
}
return ContainerUtil.createMaybeSingletonList(attributeValue);
}
/**
* @param annotation annotation
* @return type that relates to that annotation
*/
public static @Nullable PsiType getRelatedType(PsiAnnotation annotation) {
PsiAnnotationOwner owner = annotation.getOwner();
if (owner instanceof PsiType) {
return (PsiType)owner;
}
PsiType type = null;
if (owner instanceof PsiModifierList) {
PsiElement parent = ((PsiModifierList)owner).getParent();
PsiTypeElement typeElement = null;
if (parent instanceof PsiVariable) {
type = ((PsiVariable)parent).getType();
typeElement = ((PsiVariable)parent).getTypeElement();
}
if (parent instanceof PsiMethod) {
type = ((PsiMethod)parent).getReturnType();
typeElement = ((PsiMethod)parent).getReturnTypeElement();
}
if (type != null) {
PsiClass annoClass = annotation.resolveAnnotationType();
if (annoClass != null) {
Set<PsiAnnotation.TargetType> targets = AnnotationTargetUtil.getAnnotationTargets(annoClass);
if (targets != null && targets.contains(PsiAnnotation.TargetType.TYPE_USE) &&
PsiUtil.isAvailable(JavaFeature.TYPE_ANNOTATIONS, parent)) {
if (typeElement != null && targets.size() == 1) {
// For ambiguous annotations, we assume that annotation on the outer type relates to the inner type
// E.g. @Nullable Outer.Inner is equivalent to Outer.@Nullable Inner (if @Nullable is not type-use only)
PsiJavaCodeReferenceElement ref = typeElement.getInnermostComponentReferenceElement();
// See com.intellij.psi.impl.source.tree.java.PsiAnnotationImpl.getOwner
while (ref != null && ref.isQualified()) {
ref = ObjectUtils.tryCast(ref.getQualifier(), PsiJavaCodeReferenceElement.class);
}
if (ref != null) {
return JavaPsiFacade.getElementFactory(annotation.getProject()).createType(ref);
}
}
return type.getDeepComponentType();
}
}
return type;
}
}
return null;
}
}