EditorConfig documentation test
[idea/community.git] / java / java-analysis-impl / src / com / intellij / codeInsight / daemon / impl / analysis / AnnotationsHighlightUtil.java
1 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.codeInsight.daemon.impl.analysis;
3
4 import com.intellij.codeInsight.AnnotationTargetUtil;
5 import com.intellij.codeInsight.daemon.JavaErrorMessages;
6 import com.intellij.codeInsight.daemon.impl.HighlightInfo;
7 import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
8 import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction;
9 import com.intellij.codeInsight.intention.IntentionAction;
10 import com.intellij.codeInsight.intention.QuickFixFactory;
11 import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
12 import com.intellij.openapi.diagnostic.Logger;
13 import com.intellij.openapi.editor.Editor;
14 import com.intellij.openapi.project.Project;
15 import com.intellij.openapi.util.Comparing;
16 import com.intellij.patterns.ElementPattern;
17 import com.intellij.pom.java.LanguageLevel;
18 import com.intellij.psi.*;
19 import com.intellij.psi.impl.PsiImplUtil;
20 import com.intellij.psi.impl.source.PsiClassReferenceType;
21 import com.intellij.psi.impl.source.PsiImmediateClassType;
22 import com.intellij.psi.infos.CandidateInfo;
23 import com.intellij.psi.util.ClassUtil;
24 import com.intellij.psi.util.PsiTreeUtil;
25 import com.intellij.psi.util.PsiUtil;
26 import com.intellij.psi.util.TypeConversionUtil;
27 import com.intellij.util.IncorrectOperationException;
28 import com.intellij.util.ObjectUtils;
29 import org.jetbrains.annotations.Contract;
30 import org.jetbrains.annotations.NotNull;
31 import org.jetbrains.annotations.Nullable;
32
33 import java.lang.annotation.RetentionPolicy;
34 import java.util.ArrayList;
35 import java.util.HashSet;
36 import java.util.List;
37 import java.util.Set;
38
39 import static com.intellij.patterns.PsiJavaPatterns.psiElement;
40
41 /**
42  * @author ven
43  */
44 public class AnnotationsHighlightUtil {
45   private static final Logger LOG = Logger.getInstance("com.intellij.codeInsight.daemon.impl.analysis.AnnotationsHighlightUtil");
46
47   @Nullable
48   static HighlightInfo checkNameValuePair(@NotNull PsiNameValuePair pair,
49                                           RefCountHolder refCountHolder) {
50     PsiAnnotation annotation = PsiTreeUtil.getParentOfType(pair, PsiAnnotation.class);
51     if (annotation == null) return null;
52     PsiJavaCodeReferenceElement annotationNameReferenceElement = annotation.getNameReferenceElement();
53     if (annotationNameReferenceElement == null) return null;
54     PsiElement annotationClass = annotationNameReferenceElement.resolve();
55     if (!(annotationClass instanceof PsiClass && ((PsiClass)annotationClass).isAnnotationType())) return null;
56     PsiReference ref = pair.getReference();
57     if (ref == null) return null;
58     PsiMethod method = (PsiMethod)ref.resolve();
59     if (refCountHolder != null) {
60       refCountHolder.registerReference(ref, method != null ? new CandidateInfo(method, PsiSubstitutor.EMPTY) : JavaResolveResult.EMPTY);
61     }
62     if (method == null) {
63       if (pair.getName() != null) {
64         final String description = JavaErrorMessages.message("annotation.unknown.method", ref.getCanonicalText());
65         final HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF)
66           .range(ref.getElement(), ref.getRangeInElement())
67           .descriptionAndTooltip(description)
68           .create();
69         QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createCreateAnnotationMethodFromUsageFix(pair));
70         return highlightInfo;
71       }
72       else {
73         String description = JavaErrorMessages.message("annotation.missing.method", ref.getCanonicalText());
74         PsiElement element = ref.getElement();
75         final HighlightInfo highlightInfo =
76           HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create();
77         for (IntentionAction action : QuickFixFactory.getInstance().createAddAnnotationAttributeNameFixes(pair)) {
78           QuickFixAction.registerQuickFixAction(highlightInfo, action);
79         }
80         return highlightInfo;
81       }
82     }
83     else {
84       PsiType returnType = method.getReturnType();
85       assert returnType != null : method;
86       PsiAnnotationMemberValue value = pair.getValue();
87       if (value != null) {
88         HighlightInfo info = checkMemberValueType(value, returnType);
89         if (info != null) return info;
90       }
91
92       return checkDuplicateAttribute(pair);
93     }
94   }
95
96   @Nullable
97   private static HighlightInfo checkDuplicateAttribute(@NotNull PsiNameValuePair pair) {
98     PsiAnnotationParameterList annotation = (PsiAnnotationParameterList)pair.getParent();
99     PsiNameValuePair[] attributes = annotation.getAttributes();
100     for (PsiNameValuePair attribute : attributes) {
101       if (attribute == pair) break;
102       String name = pair.getName();
103       if (Comparing.equal(attribute.getName(), name)) {
104         String description = JavaErrorMessages.message("annotation.duplicate.attribute",
105                                                        name == null ? PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME : name);
106         return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(pair).descriptionAndTooltip(description).create();
107       }
108     }
109
110     return null;
111   }
112
113   @Nullable
114   static HighlightInfo checkMemberValueType(@NotNull PsiAnnotationMemberValue value, @NotNull PsiType expectedType) {
115     if (expectedType instanceof PsiClassType && expectedType.equalsToText(CommonClassNames.JAVA_LANG_CLASS)) {
116       if (!(value instanceof PsiClassObjectAccessExpression)) {
117         String description = JavaErrorMessages.message("annotation.non.class.literal.attribute.value");
118         return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(value).descriptionAndTooltip(description).create();
119       }
120     }
121
122     if (value instanceof PsiAnnotation) {
123       PsiJavaCodeReferenceElement nameRef = ((PsiAnnotation)value).getNameReferenceElement();
124       if (nameRef == null) return null;
125
126       if (expectedType instanceof PsiClassType) {
127         PsiClass aClass = ((PsiClassType)expectedType).resolve();
128         if (aClass != null && nameRef.isReferenceTo(aClass)) return null;
129       }
130
131       if (expectedType instanceof PsiArrayType) {
132         PsiType componentType = ((PsiArrayType)expectedType).getComponentType();
133         if (componentType instanceof PsiClassType) {
134           PsiClass aClass = ((PsiClassType)componentType).resolve();
135           if (aClass != null && nameRef.isReferenceTo(aClass)) return null;
136         }
137       }
138
139       String description = JavaErrorMessages.message("incompatible.types", JavaHighlightUtil.formatType(expectedType),
140                                                      nameRef.getCanonicalText());
141       return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(value).descriptionAndTooltip(description).create();
142     }
143
144     if (value instanceof PsiArrayInitializerMemberValue) {
145       if (expectedType instanceof PsiArrayType) return null;
146       String description = JavaErrorMessages.message("annotation.illegal.array.initializer", JavaHighlightUtil.formatType(expectedType));
147       return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(value).descriptionAndTooltip(description).create();
148     }
149
150     if (value instanceof PsiExpression) {
151       PsiExpression expr = (PsiExpression)value;
152       PsiType type = expr.getType();
153
154       final PsiClass psiClass = PsiUtil.resolveClassInType(type);
155       if (psiClass != null && psiClass.isEnum() && !(expr instanceof PsiReferenceExpression && ((PsiReferenceExpression)expr).resolve() instanceof PsiEnumConstant)) {
156         String description = JavaErrorMessages.message("annotation.non.enum.constant.attribute.value");
157         return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(value).descriptionAndTooltip(description).create();
158       }
159
160       if (type != null && TypeConversionUtil.areTypesAssignmentCompatible(expectedType, expr) ||
161           expectedType instanceof PsiArrayType &&
162           TypeConversionUtil.areTypesAssignmentCompatible(((PsiArrayType)expectedType).getComponentType(), expr)) {
163         return null;
164       }
165
166       String description = JavaErrorMessages.message("incompatible.types", JavaHighlightUtil.formatType(expectedType), JavaHighlightUtil.formatType(type));
167       HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(value).descriptionAndTooltip(description).create();
168       QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createSurroundWithQuotesAnnotationParameterValueFix(value, expectedType));
169       return info;
170     }
171
172     LOG.error("Unknown annotation member value: " + value);
173     return null;
174   }
175
176   static HighlightInfo checkDuplicateAnnotations(@NotNull PsiAnnotation annotationToCheck, @NotNull LanguageLevel languageLevel) {
177     PsiAnnotationOwner owner = annotationToCheck.getOwner();
178     if (owner == null) return null;
179
180     PsiJavaCodeReferenceElement element = annotationToCheck.getNameReferenceElement();
181     if (element == null) return null;
182     PsiElement resolved = element.resolve();
183     if (!(resolved instanceof PsiClass)) return null;
184
185     PsiClass annotationType = (PsiClass)resolved;
186
187     PsiClass contained = contained(annotationType);
188     String containedElementFQN = contained == null ? null : contained.getQualifiedName();
189
190     if (containedElementFQN != null) {
191       String containerName = annotationType.getQualifiedName();
192       if (isAnnotationRepeatedTwice(owner, containedElementFQN)) {
193         String description = JavaErrorMessages.message("annotation.container.wrong.place", containerName);
194         return annotationError(annotationToCheck, description);
195       }
196     }
197     else if (isAnnotationRepeatedTwice(owner, annotationType.getQualifiedName())) {
198       if (!languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) {
199         String description = JavaErrorMessages.message("annotation.duplicate.annotation");
200         return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create();
201       }
202
203       PsiAnnotation metaAnno = PsiImplUtil.findAnnotation(annotationType.getModifierList(), CommonClassNames.JAVA_LANG_ANNOTATION_REPEATABLE);
204       if (metaAnno == null) {
205         String explanation = JavaErrorMessages.message("annotation.non.repeatable", annotationType.getQualifiedName());
206         String description = JavaErrorMessages.message("annotation.duplicate.explained", explanation);
207         HighlightInfo info =
208           HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create();
209         QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createCollapseAnnotationsFix(annotationToCheck));
210         return info;
211       }
212
213       String explanation = doCheckRepeatableAnnotation(metaAnno);
214       if (explanation != null) {
215         String description = JavaErrorMessages.message("annotation.duplicate.explained", explanation);
216         return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create();
217       }
218
219       PsiClass container = getRepeatableContainer(metaAnno);
220       if (container != null) {
221         PsiAnnotation.TargetType[] targets = AnnotationTargetUtil.getTargetsForLocation(owner);
222         PsiAnnotation.TargetType applicable = AnnotationTargetUtil.findAnnotationTarget(container, targets);
223         if (applicable == null) {
224           String target = JavaErrorMessages.message("annotation.target." + targets[0]);
225           String message = JavaErrorMessages.message("annotation.container.not.applicable", container.getName(), target);
226           return annotationError(annotationToCheck, message);
227         }
228       }
229     }
230
231     return null;
232   }
233
234   // returns contained element
235   private static PsiClass contained(@NotNull PsiClass annotationType) {
236     if (!annotationType.isAnnotationType()) return null;
237     PsiMethod[] values = annotationType.findMethodsByName("value", false);
238     if (values.length != 1) return null;
239     PsiMethod value = values[0];
240     PsiType returnType = value.getReturnType();
241     if (!(returnType instanceof PsiArrayType)) return null;
242     PsiType type = ((PsiArrayType)returnType).getComponentType();
243     if (!(type instanceof PsiClassType)) return null;
244     PsiClass contained = ((PsiClassType)type).resolve();
245     if (contained == null || !contained.isAnnotationType()) return null;
246     if (PsiImplUtil.findAnnotation(contained.getModifierList(), CommonClassNames.JAVA_LANG_ANNOTATION_REPEATABLE) == null) return null;
247
248     return contained;
249   }
250
251   private static boolean isAnnotationRepeatedTwice(@NotNull PsiAnnotationOwner owner, @Nullable String qualifiedName) {
252     int count = 0;
253     for (PsiAnnotation annotation : owner.getAnnotations()) {
254       PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement();
255       if (nameRef == null) continue;
256       PsiElement resolved = nameRef.resolve();
257       if (!(resolved instanceof PsiClass) || !Comparing.equal(qualifiedName, ((PsiClass)resolved).getQualifiedName())) continue;
258       if (++count == 2) return true;
259     }
260     return false;
261   }
262
263   @Nullable
264   static HighlightInfo checkMissingAttributes(@NotNull PsiAnnotation annotation) {
265     PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement();
266     if (nameRef == null) return null;
267     PsiClass aClass = (PsiClass)nameRef.resolve();
268     if (aClass != null && aClass.isAnnotationType()) {
269       Set<String> names = new HashSet<>();
270       PsiNameValuePair[] attributes = annotation.getParameterList().getAttributes();
271       for (PsiNameValuePair attribute : attributes) {
272         final String name = attribute.getName();
273         if (name != null) {
274           names.add(name);
275         }
276         else {
277           names.add(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME);
278         }
279       }
280
281       PsiMethod[] annotationMethods = aClass.getMethods();
282       List<String> missed = new ArrayList<>();
283       for (PsiMethod method : annotationMethods) {
284         if (PsiUtil.isAnnotationMethod(method)) {
285           PsiAnnotationMethod annotationMethod = (PsiAnnotationMethod)method;
286           if (annotationMethod.getDefaultValue() == null) {
287             if (!names.contains(annotationMethod.getName())) {
288               missed.add(annotationMethod.getName());
289             }
290           }
291         }
292       }
293
294       if (!missed.isEmpty()) {
295         StringBuffer buff = new StringBuffer("'" + missed.get(0) + "'");
296         for (int i = 1; i < missed.size(); i++) {
297           buff.append(", ");
298           buff.append("'").append(missed.get(i)).append("'");
299         }
300
301         String description = JavaErrorMessages.message("annotation.missing.attribute", buff);
302         HighlightInfo info =
303           HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(nameRef).descriptionAndTooltip(description).create();
304         IntentionAction fix = QuickFixFactory.getInstance().createAddMissingRequiredAnnotationParametersFix(
305           annotation, annotationMethods, missed);
306         QuickFixAction.registerQuickFixAction(info, fix);
307         return info;
308       }
309     }
310
311     return null;
312   }
313
314   @Nullable
315   static HighlightInfo checkConstantExpression(@NotNull PsiExpression expression) {
316     final PsiElement parent = expression.getParent();
317     if (PsiUtil.isAnnotationMethod(parent) || parent instanceof PsiNameValuePair || parent instanceof PsiArrayInitializerMemberValue) {
318       if (!PsiUtil.isConstantExpression(expression)) {
319         String description = JavaErrorMessages.message("annotation.non.constant.attribute.value");
320         return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create();
321       }
322     }
323
324     return null;
325   }
326
327   @Nullable
328   static HighlightInfo checkValidAnnotationType(@Nullable PsiType type, @NotNull PsiTypeElement typeElement) {
329     if (type != null && type.accept(AnnotationReturnTypeVisitor.INSTANCE).booleanValue()) {
330       return null;
331     }
332     String description = JavaErrorMessages.message("annotation.invalid.annotation.member.type", type != null ? type.getPresentableText() : null);
333     return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(description).create();
334   }
335
336   private static final ElementPattern<PsiElement> ANY_ANNOTATION_ALLOWED = psiElement().andOr(
337     psiElement().withParent(PsiNameValuePair.class),
338     psiElement().withParents(PsiArrayInitializerMemberValue.class, PsiNameValuePair.class),
339     psiElement().withParents(PsiArrayInitializerMemberValue.class, PsiAnnotationMethod.class),
340     psiElement().withParent(PsiAnnotationMethod.class).afterLeaf(PsiKeyword.DEFAULT)
341   );
342
343   @Nullable
344   public static HighlightInfo checkApplicability(@NotNull PsiAnnotation annotation, @NotNull LanguageLevel level, @NotNull PsiFile file) {
345     if (ANY_ANNOTATION_ALLOWED.accepts(annotation)) {
346       return null;
347     }
348
349     PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement();
350     if (nameRef == null) return null;
351
352     PsiAnnotationOwner owner = annotation.getOwner();
353     PsiAnnotation.TargetType[] targets = AnnotationTargetUtil.getTargetsForLocation(owner);
354     if (owner == null || targets.length == 0) {
355       String message = JavaErrorMessages.message("annotation.not.allowed.here");
356       return annotationError(annotation, message);
357     }
358
359     if (!(owner instanceof PsiModifierList)) {
360       HighlightInfo info = HighlightUtil.checkFeature(annotation, HighlightUtil.Feature.TYPE_ANNOTATIONS, level, file);
361       if (info != null) return info;
362     }
363
364     PsiAnnotation.TargetType applicable = AnnotationTargetUtil.findAnnotationTarget(annotation, targets);
365     if (applicable == PsiAnnotation.TargetType.UNKNOWN) return null;
366
367     if (applicable == null) {
368       String target = JavaErrorMessages.message("annotation.target." + targets[0]);
369       String message = JavaErrorMessages.message("annotation.not.applicable", nameRef.getText(), target);
370       return annotationError(annotation, message);
371     }
372
373     if (applicable == PsiAnnotation.TargetType.TYPE_USE) {
374       if (owner instanceof PsiClassReferenceType) {
375         PsiJavaCodeReferenceElement ref = ((PsiClassReferenceType)owner).getReference();
376         HighlightInfo info = checkReferenceTarget(annotation, ref);
377         if (info != null) return info;
378       }
379       else if (owner instanceof PsiModifierList || owner instanceof PsiTypeElement) {
380         PsiElement nextElement = owner instanceof PsiTypeElement
381             ? (PsiTypeElement)owner
382             : PsiTreeUtil.skipSiblingsForward((PsiModifierList)owner, PsiComment.class, PsiWhiteSpace.class, PsiTypeParameterList.class);
383         if (nextElement instanceof PsiTypeElement) {
384           PsiTypeElement typeElement = (PsiTypeElement)nextElement;
385           PsiType type = typeElement.getType();
386           if (PsiType.VOID.equals(type)) {
387             String message = JavaErrorMessages.message("annotation.not.allowed.void");
388             return annotationError(annotation, message);
389           }
390           if (!(type instanceof PsiPrimitiveType || type instanceof PsiArrayType)) {
391             PsiJavaCodeReferenceElement ref = getOutermostReferenceElement(typeElement.getInnermostComponentReferenceElement());
392             HighlightInfo info = checkReferenceTarget(annotation, ref);
393             if (info != null) return info;
394           }
395           PsiElement context = PsiTreeUtil.skipParentsOfType(typeElement, PsiTypeElement.class);
396           if (context instanceof PsiClassObjectAccessExpression) {
397             String message = JavaErrorMessages.message("annotation.not.allowed.class");
398             return annotationError(annotation, message);
399           }
400         }
401       }
402     }
403
404     return null;
405   }
406
407   private static HighlightInfo annotationError(@NotNull PsiAnnotation annotation, @NotNull String message) {
408     HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(annotation).descriptionAndTooltip(message).create();
409     QuickFixAction.registerQuickFixAction(info, new DeleteAnnotationAction(annotation));
410     return info;
411   }
412
413   @Nullable
414   private static HighlightInfo checkReferenceTarget(@NotNull PsiAnnotation annotation, @Nullable PsiJavaCodeReferenceElement ref) {
415     if (ref == null) return null;
416     PsiElement refTarget = ref.resolve();
417     if (refTarget == null) return null;
418
419     String message = null;
420     if (!(refTarget instanceof PsiClass)) {
421       message = JavaErrorMessages.message("annotation.not.allowed.ref");
422     }
423     else {
424       PsiElement parent = ref.getParent();
425       if (parent instanceof PsiJavaCodeReferenceElement) {
426         PsiElement qualified = ((PsiJavaCodeReferenceElement)parent).resolve();
427         if (qualified instanceof PsiMember && ((PsiMember)qualified).hasModifierProperty(PsiModifier.STATIC)) {
428           message = JavaErrorMessages.message("annotation.not.allowed.static");
429         }
430       }
431     }
432
433     return message != null ? annotationError(annotation, message) : null;
434   }
435
436   @Contract("null->null; !null->!null")
437   private static PsiJavaCodeReferenceElement getOutermostReferenceElement(@Nullable PsiJavaCodeReferenceElement ref) {
438     if (ref == null) return null;
439
440     PsiElement qualifier;
441     while ((qualifier = ref.getQualifier()) instanceof PsiJavaCodeReferenceElement) {
442       ref = (PsiJavaCodeReferenceElement)qualifier;
443     }
444     return ref;
445   }
446
447   @Nullable
448   static HighlightInfo checkAnnotationType(@NotNull PsiAnnotation annotation) {
449     PsiJavaCodeReferenceElement nameReferenceElement = annotation.getNameReferenceElement();
450     if (nameReferenceElement != null) {
451       PsiElement resolved = nameReferenceElement.resolve();
452       if (!(resolved instanceof PsiClass) || !((PsiClass)resolved).isAnnotationType()) {
453         String description = JavaErrorMessages.message("annotation.annotation.type.expected");
454         return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(nameReferenceElement).descriptionAndTooltip(description).create();
455       }
456     }
457     return null;
458   }
459
460   @Nullable
461   static HighlightInfo checkCyclicMemberType(@NotNull PsiTypeElement typeElement, @NotNull PsiClass aClass) {
462     PsiType type = typeElement.getType();
463     Set<PsiClass> checked = new HashSet<>();
464     if (cyclicDependencies(aClass, type, checked, aClass.getManager())) {
465       String description = JavaErrorMessages.message("annotation.cyclic.element.type");
466       return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(description).create();
467     }
468     return null;
469   }
470
471   private static boolean cyclicDependencies(@NotNull PsiClass aClass,
472                                             @Nullable PsiType type,
473                                             @NotNull Set<? super PsiClass> checked,
474                                             @NotNull PsiManager manager) {
475     final PsiClass resolvedClass = PsiUtil.resolveClassInType(type);
476     if (resolvedClass != null && resolvedClass.isAnnotationType()) {
477       if (aClass == resolvedClass) {
478         return true;
479       }
480       if (!checked.add(resolvedClass) || !BaseIntentionAction.canModify(resolvedClass)) return false;
481       final PsiMethod[] methods = resolvedClass.getMethods();
482       for (PsiMethod method : methods) {
483         if (cyclicDependencies(aClass, method.getReturnType(), checked,manager)) return true;
484       }
485     }
486     return false;
487   }
488
489   static HighlightInfo checkClashesWithSuperMethods(@NotNull PsiAnnotationMethod psiMethod) {
490     final PsiIdentifier nameIdentifier = psiMethod.getNameIdentifier();
491     if (nameIdentifier != null) {
492       final PsiMethod[] methods = psiMethod.findDeepestSuperMethods();
493       for (PsiMethod method : methods) {
494         final PsiClass containingClass = method.getContainingClass();
495         if (containingClass != null) {
496           final String qualifiedName = containingClass.getQualifiedName();
497           if (CommonClassNames.JAVA_LANG_OBJECT.equals(qualifiedName) || CommonClassNames.JAVA_LANG_ANNOTATION_ANNOTATION.equals(qualifiedName)) {
498             return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(nameIdentifier).descriptionAndTooltip(
499               "@interface member clashes with '" + JavaHighlightUtil.formatMethod(method) + "' in " + HighlightUtil.formatClass(containingClass)).create();
500           }
501         }
502       }
503     }
504     return null;
505   }
506
507   @Nullable
508   static HighlightInfo checkAnnotationDeclaration(@Nullable PsiElement parent, @NotNull PsiReferenceList list) {
509     if (PsiUtil.isAnnotationMethod(parent)) {
510       PsiAnnotationMethod method = (PsiAnnotationMethod)parent;
511       if (list == method.getThrowsList()) {
512         String description = JavaErrorMessages.message("annotation.members.may.not.have.throws.list");
513         return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(list).descriptionAndTooltip(description).create();
514       }
515     }
516     else if (parent instanceof PsiClass && ((PsiClass)parent).isAnnotationType()) {
517       if (PsiKeyword.EXTENDS.equals(list.getFirstChild().getText())) {
518         String description = JavaErrorMessages.message("annotation.may.not.have.extends.list");
519         return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(list).descriptionAndTooltip(description).create();
520       }
521     }
522     return null;
523   }
524
525   @Nullable
526   static HighlightInfo checkPackageAnnotationContainingFile(@NotNull PsiPackageStatement statement, @NotNull PsiFile file) {
527     PsiModifierList annotationList = statement.getAnnotationList();
528     if (annotationList != null && !PsiPackage.PACKAGE_INFO_FILE.equals(file.getName())) {
529       String message = JavaErrorMessages.message("invalid.package.annotation.containing.file");
530       return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(annotationList.getTextRange()).descriptionAndTooltip(message).create();
531     }
532     return null;
533   }
534
535   @Nullable
536   static HighlightInfo checkTargetAnnotationDuplicates(@NotNull PsiAnnotation annotation) {
537     PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement();
538     if (nameRef == null) return null;
539
540     PsiElement resolved = nameRef.resolve();
541     if (!(resolved instanceof PsiClass) || !CommonClassNames.JAVA_LANG_ANNOTATION_TARGET.equals(((PsiClass)resolved).getQualifiedName())) {
542       return null;
543     }
544
545     PsiNameValuePair[] attributes = annotation.getParameterList().getAttributes();
546     if (attributes.length < 1) return null;
547     PsiAnnotationMemberValue value = attributes[0].getValue();
548     if (!(value instanceof PsiArrayInitializerMemberValue)) return null;
549     PsiAnnotationMemberValue[] arrayInitializers = ((PsiArrayInitializerMemberValue) value).getInitializers();
550     Set<PsiElement> targets = new HashSet<>();
551     for (PsiAnnotationMemberValue initializer : arrayInitializers) {
552       if (initializer instanceof PsiReferenceExpression) {
553         PsiElement target = ((PsiReferenceExpression) initializer).resolve();
554         if (target != null) {
555           if (targets.contains(target)) {
556             String description = JavaErrorMessages.message("repeated.annotation.target");
557             return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(initializer).descriptionAndTooltip(description).create();
558           }
559           targets.add(target);
560         }
561       }
562     }
563     return null;
564   }
565
566   @Nullable
567   static HighlightInfo checkFunctionalInterface(@NotNull PsiAnnotation annotation, @NotNull LanguageLevel languageLevel) {
568     if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8) && Comparing.strEqual(annotation.getQualifiedName(), CommonClassNames.JAVA_LANG_FUNCTIONAL_INTERFACE)) {
569       final PsiAnnotationOwner owner = annotation.getOwner();
570       if (owner instanceof PsiModifierList) {
571         final PsiElement parent = ((PsiModifierList)owner).getParent();
572         if (parent instanceof PsiClass) {
573           final String errorMessage = LambdaHighlightingUtil.checkInterfaceFunctional((PsiClass)parent, ((PsiClass)parent).getName() + " is not a functional interface");
574           if (errorMessage != null) {
575             return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(annotation).descriptionAndTooltip(errorMessage).create();
576           }
577         }
578       }
579     }
580     return null;
581   }
582
583   @Nullable
584   static HighlightInfo checkRepeatableAnnotation(@NotNull PsiAnnotation annotation) {
585     String qualifiedName = annotation.getQualifiedName();
586     if (!CommonClassNames.JAVA_LANG_ANNOTATION_REPEATABLE.equals(qualifiedName)) return null;
587
588     String description = doCheckRepeatableAnnotation(annotation);
589     if (description != null) {
590       PsiAnnotationMemberValue containerRef = PsiImplUtil.findAttributeValue(annotation, null);
591       if (containerRef != null) {
592         return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(containerRef).descriptionAndTooltip(description).create();
593       }
594     }
595
596     return null;
597   }
598
599   @Nullable
600   private static String doCheckRepeatableAnnotation(@NotNull PsiAnnotation annotation) {
601     PsiAnnotationOwner owner = annotation.getOwner();
602     if (!(owner instanceof PsiModifierList)) return null;
603     PsiElement target = ((PsiModifierList)owner).getParent();
604     if (!(target instanceof PsiClass) || !((PsiClass)target).isAnnotationType()) return null;
605     PsiClass container = getRepeatableContainer(annotation);
606     if (container == null) return null;
607
608     PsiMethod[] methods = container.findMethodsByName("value", false);
609     if (methods.length == 0) {
610       return JavaErrorMessages.message("annotation.container.no.value", container.getQualifiedName());
611     }
612
613     if (methods.length == 1) {
614       PsiType expected = new PsiImmediateClassType((PsiClass)target, PsiSubstitutor.EMPTY).createArrayType();
615       if (!expected.equals(methods[0].getReturnType())) {
616         return JavaErrorMessages.message("annotation.container.bad.type", container.getQualifiedName(), JavaHighlightUtil.formatType(expected));
617       }
618     }
619
620     RetentionPolicy targetPolicy = getRetentionPolicy((PsiClass)target);
621     if (targetPolicy != null) {
622       RetentionPolicy containerPolicy = getRetentionPolicy(container);
623       if (containerPolicy != null && targetPolicy.compareTo(containerPolicy) > 0) {
624         return JavaErrorMessages.message("annotation.container.low.retention", container.getQualifiedName(), containerPolicy);
625       }
626     }
627
628     Set<PsiAnnotation.TargetType> repeatableTargets = AnnotationTargetUtil.getAnnotationTargets((PsiClass)target);
629     if (repeatableTargets != null) {
630       Set<PsiAnnotation.TargetType> containerTargets = AnnotationTargetUtil.getAnnotationTargets(container);
631       if (containerTargets != null && !repeatableTargets.containsAll(containerTargets)) {
632         return JavaErrorMessages.message("annotation.container.wide.target", container.getQualifiedName());
633       }
634     }
635
636     for (PsiMethod method : container.getMethods()) {
637       if (method instanceof PsiAnnotationMethod && !"value".equals(method.getName()) && ((PsiAnnotationMethod)method).getDefaultValue() == null) {
638         return JavaErrorMessages.message("annotation.container.abstract", container.getQualifiedName(), method.getName());
639       }
640     }
641
642     return null;
643   }
644
645   @Nullable
646   private static PsiClass getRepeatableContainer(@NotNull PsiAnnotation annotation) {
647     PsiAnnotationMemberValue containerRef = PsiImplUtil.findAttributeValue(annotation, null);
648     if (!(containerRef instanceof PsiClassObjectAccessExpression)) return null;
649     PsiType containerType = ((PsiClassObjectAccessExpression)containerRef).getOperand().getType();
650     if (!(containerType instanceof PsiClassType)) return null;
651     PsiClass container = ((PsiClassType)containerType).resolve();
652     if (container == null || !container.isAnnotationType()) return null;
653     return container;
654   }
655
656   @Nullable
657   static HighlightInfo checkReceiverPlacement(@NotNull PsiReceiverParameter parameter) {
658     PsiElement owner = parameter.getParent().getParent();
659     if (owner == null) return null;
660
661     if (!(owner instanceof PsiMethod)) {
662       String text = JavaErrorMessages.message("receiver.wrong.context");
663       return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(parameter.getIdentifier()).descriptionAndTooltip(text).create();
664     }
665
666     PsiMethod method = (PsiMethod)owner;
667     if (isStatic(method) || method.isConstructor() && isStatic(method.getContainingClass())) {
668       String text = JavaErrorMessages.message("receiver.static.context");
669       return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(parameter.getIdentifier()).descriptionAndTooltip(text).create();
670     }
671
672     PsiElement leftNeighbour = PsiTreeUtil.skipWhitespacesBackward(parameter);
673     if (leftNeighbour != null && !PsiUtil.isJavaToken(leftNeighbour, JavaTokenType.LPARENTH)) {
674       String text = JavaErrorMessages.message("receiver.wrong.position");
675       return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(parameter.getIdentifier()).descriptionAndTooltip(text).create();
676     }
677
678     return null;
679   }
680
681   @Nullable
682   static HighlightInfo checkReceiverType(@NotNull PsiReceiverParameter parameter) {
683     PsiElement owner = parameter.getParent().getParent();
684     if (!(owner instanceof PsiMethod)) return null;
685
686     PsiMethod method = (PsiMethod)owner;
687     PsiClass enclosingClass = method.getContainingClass();
688     if (method.isConstructor() && enclosingClass != null) {
689       enclosingClass = enclosingClass.getContainingClass();
690     }
691
692     if (enclosingClass != null) {
693       PsiClassType type = PsiElementFactory.getInstance(parameter.getProject()).createType(enclosingClass, PsiSubstitutor.EMPTY);
694       if (!type.equals(parameter.getType())) {
695         PsiElement range = ObjectUtils.notNull(parameter.getTypeElement(), parameter);
696         String text = JavaErrorMessages.message("receiver.type.mismatch");
697         return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(text).create();
698       }
699
700       PsiThisExpression identifier = parameter.getIdentifier();
701       if (!enclosingClass.equals(PsiUtil.resolveClassInType(identifier.getType()))) {
702         String text = JavaErrorMessages.message("receiver.name.mismatch");
703         return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier).descriptionAndTooltip(text).create();
704       }
705     }
706
707     return null;
708   }
709
710   private static boolean isStatic(@Nullable PsiModifierListOwner owner) {
711     if (owner == null) return false;
712     if (owner instanceof PsiClass && ClassUtil.isTopLevelClass((PsiClass)owner)) return true;
713     PsiModifierList modifierList = owner.getModifierList();
714     return modifierList != null && modifierList.hasModifierProperty(PsiModifier.STATIC);
715   }
716
717   @Nullable
718   public static RetentionPolicy getRetentionPolicy(@NotNull PsiClass annotation) {
719     PsiModifierList modifierList = annotation.getModifierList();
720     if (modifierList != null) {
721       PsiAnnotation retentionAnno = modifierList.findAnnotation(CommonClassNames.JAVA_LANG_ANNOTATION_RETENTION);
722       if (retentionAnno == null) return RetentionPolicy.CLASS;
723
724       PsiAnnotationMemberValue policyRef = PsiImplUtil.findAttributeValue(retentionAnno, null);
725       if (policyRef instanceof PsiReference) {
726         PsiElement field = ((PsiReference)policyRef).resolve();
727         if (field instanceof PsiEnumConstant) {
728           String name = ((PsiEnumConstant)field).getName();
729           try {
730             //noinspection ConstantConditions
731             return Enum.valueOf(RetentionPolicy.class, name);
732           }
733           catch (Exception e) {
734             LOG.warn("Unknown policy: " + name);
735           }
736         }
737       }
738     }
739
740     return null;
741   }
742
743   public static class AnnotationReturnTypeVisitor extends PsiTypeVisitor<Boolean> {
744     public static final AnnotationReturnTypeVisitor INSTANCE = new AnnotationReturnTypeVisitor();
745     @Override
746     public Boolean visitType(PsiType type) {
747       return Boolean.FALSE;
748     }
749
750     @Override
751     public Boolean visitPrimitiveType(PsiPrimitiveType primitiveType) {
752       return PsiType.VOID.equals(primitiveType) || PsiType.NULL.equals(primitiveType) ? Boolean.FALSE : Boolean.TRUE;
753     }
754
755     @Override
756     public Boolean visitArrayType(PsiArrayType arrayType) {
757       if (arrayType.getArrayDimensions() != 1) return Boolean.FALSE;
758       PsiType componentType = arrayType.getComponentType();
759       return componentType.accept(this);
760     }
761
762     @Override
763     public Boolean visitClassType(PsiClassType classType) {
764       if (classType.getParameters().length > 0) {
765         PsiClassType rawType = classType.rawType();
766         return rawType.equalsToText(CommonClassNames.JAVA_LANG_CLASS);
767       }
768
769       PsiClass aClass = classType.resolve();
770       if (aClass != null && (aClass.isAnnotationType() || aClass.isEnum())) {
771         return Boolean.TRUE;
772       }
773
774       return classType.equalsToText(CommonClassNames.JAVA_LANG_CLASS) || classType.equalsToText(CommonClassNames.JAVA_LANG_STRING);
775     }
776   }
777
778   private static class DeleteAnnotationAction implements IntentionAction {
779     private final PsiAnnotation myAnnotation;
780
781     private DeleteAnnotationAction(@NotNull PsiAnnotation annotation) {
782       myAnnotation = annotation;
783     }
784
785     @NotNull
786     @Override
787     public String getText() {
788       return "Remove";
789     }
790
791     @NotNull
792     @Override
793     public String getFamilyName() {
794       return getText();
795     }
796
797     @Nullable
798     @Override
799     public PsiElement getElementToMakeWritable(@NotNull PsiFile currentFile) {
800       return myAnnotation;
801     }
802
803     @Override
804     public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
805       return myAnnotation.isValid();
806     }
807
808     @Override
809     public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
810       myAnnotation.delete();
811     }
812
813     @Override
814     public boolean startInWriteAction() {
815       return true;
816     }
817   }
818 }