| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.codeInsight.daemon.impl.analysis; |
| |
| import com.intellij.codeInsight.FileModificationService; |
| import com.intellij.codeInsight.daemon.JavaErrorMessages; |
| import com.intellij.codeInsight.daemon.impl.HighlightInfo; |
| import com.intellij.codeInsight.daemon.impl.HighlightInfoType; |
| import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction; |
| import com.intellij.codeInsight.intention.IntentionAction; |
| import com.intellij.codeInsight.intention.QuickFixFactory; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.patterns.ElementPattern; |
| import com.intellij.pom.java.LanguageLevel; |
| import com.intellij.psi.*; |
| import com.intellij.psi.impl.PsiImplUtil; |
| import com.intellij.psi.impl.source.PsiClassReferenceType; |
| import com.intellij.psi.impl.source.PsiImmediateClassType; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.PsiUtil; |
| import com.intellij.psi.util.TypeConversionUtil; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.containers.HashSet; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Set; |
| |
| import static com.intellij.patterns.PsiJavaPatterns.psiElement; |
| |
| /** |
| * @author ven |
| */ |
| public class AnnotationsHighlightUtil { |
| private static final Logger LOG = Logger.getInstance("com.intellij.codeInsight.daemon.impl.analysis.AnnotationsHighlightUtil"); |
| |
| @Nullable |
| public static HighlightInfo checkNameValuePair(PsiNameValuePair pair) { |
| PsiReference ref = pair.getReference(); |
| if (ref == null) return null; |
| PsiMethod method = (PsiMethod)ref.resolve(); |
| if (method == null) { |
| if (pair.getName() != null) { |
| final String description = JavaErrorMessages.message("annotation.unknown.method", ref.getCanonicalText()); |
| PsiElement element = ref.getElement(); |
| final HighlightInfo highlightInfo = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(element).descriptionAndTooltip(description).create(); |
| QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createCreateAnnotationMethodFromUsageFix(pair)); |
| return highlightInfo; |
| } |
| else { |
| String description = JavaErrorMessages.message("annotation.missing.method", ref.getCanonicalText()); |
| PsiElement element = ref.getElement(); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create(); |
| } |
| } |
| else { |
| PsiType returnType = method.getReturnType(); |
| assert returnType != null : method; |
| PsiAnnotationMemberValue value = pair.getValue(); |
| HighlightInfo info = checkMemberValueType(value, returnType); |
| if (info != null) return info; |
| |
| return checkDuplicateAttribute(pair); |
| } |
| } |
| |
| @Nullable |
| private static HighlightInfo checkDuplicateAttribute(PsiNameValuePair pair) { |
| PsiAnnotationParameterList annotation = (PsiAnnotationParameterList)pair.getParent(); |
| PsiNameValuePair[] attributes = annotation.getAttributes(); |
| for (PsiNameValuePair attribute : attributes) { |
| if (attribute == pair) break; |
| String name = pair.getName(); |
| if (Comparing.equal(attribute.getName(), name)) { |
| String description = JavaErrorMessages.message("annotation.duplicate.attribute", |
| name == null ? PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME : name); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(pair).descriptionAndTooltip(description).create(); |
| } |
| } |
| |
| return null; |
| } |
| |
| private static String formatReference(PsiJavaCodeReferenceElement ref) { |
| return ref.getCanonicalText(); |
| } |
| |
| @Nullable |
| public static HighlightInfo checkMemberValueType(@Nullable PsiAnnotationMemberValue value, PsiType expectedType) { |
| if (value == null) return null; |
| |
| if (expectedType instanceof PsiClassType && expectedType.equalsToText(CommonClassNames.JAVA_LANG_CLASS)) { |
| if (!(value instanceof PsiClassObjectAccessExpression)) { |
| String description = JavaErrorMessages.message("annotation.non.class.literal.attribute.value"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(value).descriptionAndTooltip(description).create(); |
| } |
| } |
| |
| if (value instanceof PsiAnnotation) { |
| PsiJavaCodeReferenceElement nameRef = ((PsiAnnotation)value).getNameReferenceElement(); |
| if (nameRef == null) return null; |
| |
| if (expectedType instanceof PsiClassType) { |
| PsiClass aClass = ((PsiClassType)expectedType).resolve(); |
| if (aClass != null && nameRef.isReferenceTo(aClass)) return null; |
| } |
| |
| if (expectedType instanceof PsiArrayType) { |
| PsiType componentType = ((PsiArrayType)expectedType).getComponentType(); |
| if (componentType instanceof PsiClassType) { |
| PsiClass aClass = ((PsiClassType)componentType).resolve(); |
| if (aClass != null && nameRef.isReferenceTo(aClass)) return null; |
| } |
| } |
| |
| String description = JavaErrorMessages.message("annotation.incompatible.types", |
| formatReference(nameRef), JavaHighlightUtil.formatType(expectedType)); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(value).descriptionAndTooltip(description).create(); |
| } |
| if (value instanceof PsiArrayInitializerMemberValue) { |
| if (expectedType instanceof PsiArrayType) return null; |
| String description = JavaErrorMessages.message("annotation.illegal.array.initializer", JavaHighlightUtil.formatType(expectedType)); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(value).descriptionAndTooltip(description).create(); |
| } |
| if (value instanceof PsiExpression) { |
| PsiExpression expr = (PsiExpression)value; |
| PsiType type = expr.getType(); |
| if (type != null && TypeConversionUtil.areTypesAssignmentCompatible(expectedType, expr) || |
| expectedType instanceof PsiArrayType && |
| TypeConversionUtil.areTypesAssignmentCompatible(((PsiArrayType)expectedType).getComponentType(), expr)) { |
| return null; |
| } |
| |
| String description = JavaErrorMessages.message("annotation.incompatible.types", |
| JavaHighlightUtil.formatType(type), JavaHighlightUtil.formatType(expectedType)); |
| final HighlightInfo info = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(value).descriptionAndTooltip(description).create(); |
| QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createSurroundWithQuotesAnnotationParameterValueFix(value, expectedType)); |
| return info; |
| } |
| |
| LOG.error("Unknown annotation member value: " + value); |
| return null; |
| } |
| |
| static HighlightInfo checkDuplicateAnnotations(@NotNull PsiAnnotation annotationToCheck, @NotNull LanguageLevel languageLevel) { |
| PsiAnnotationOwner owner = annotationToCheck.getOwner(); |
| if (owner == null) return null; |
| |
| PsiJavaCodeReferenceElement element = annotationToCheck.getNameReferenceElement(); |
| if (element == null) return null; |
| PsiElement resolved = element.resolve(); |
| if (!(resolved instanceof PsiClass)) return null; |
| |
| PsiClass annotationType = (PsiClass)resolved; |
| |
| PsiClass contained = contained(annotationType); |
| String containedElementFQN = contained == null ? null : contained.getQualifiedName(); |
| |
| if (containedElementFQN != null) { |
| PsiClass container = annotationType; |
| String containerName = container.getQualifiedName(); |
| if (isAnnotationRepeatedTwice(owner, containedElementFQN)) { |
| String description = JavaErrorMessages.message("annotation.container.wrong.place", containerName); |
| return annotationError(annotationToCheck, description); |
| } |
| } |
| else if (isAnnotationRepeatedTwice(owner, annotationType.getQualifiedName())) { |
| if (!languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { |
| String description = JavaErrorMessages.message("annotation.duplicate.annotation"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create(); |
| } |
| PsiAnnotation metaAnno = PsiImplUtil.findAnnotation(annotationType.getModifierList(), CommonClassNames.JAVA_LANG_ANNOTATION_REPEATABLE); |
| |
| if (metaAnno == null) { |
| String explanation = JavaErrorMessages.message("annotation.non.repeatable", annotationType.getQualifiedName()); |
| String description = JavaErrorMessages.message("annotation.duplicate.explained", explanation); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create(); |
| } |
| |
| String explanation = doCheckRepeatableAnnotation(metaAnno); |
| if (explanation != null) { |
| String description = JavaErrorMessages.message("annotation.duplicate.explained", explanation); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create(); |
| } |
| |
| PsiClass container = getRepeatableContainer(metaAnno); |
| if (container != null) { |
| PsiAnnotation.TargetType[] targets = PsiImplUtil.getTargetsForLocation(owner); |
| PsiAnnotation.TargetType applicable = PsiImplUtil.findApplicableTarget(container, targets); |
| if (applicable == null) { |
| String target = JavaErrorMessages.message("annotation.target." + targets[0]); |
| String message = JavaErrorMessages.message("annotation.container.not.applicable", container.getName(), target); |
| return annotationError(annotationToCheck, message); |
| } |
| } |
| } |
| |
| for (PsiAnnotation annotation : owner.getAnnotations()) { |
| if (annotation == annotationToCheck) continue; |
| PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement(); |
| if (nameRef == null) continue; |
| PsiElement aClass = nameRef.resolve(); |
| if (!resolved.equals(aClass)) continue; |
| |
| } |
| |
| return null; |
| } |
| |
| // returns contained element |
| private static PsiClass contained(PsiClass annotationType) { |
| if (!annotationType.isAnnotationType()) return null; |
| PsiMethod[] values = annotationType.findMethodsByName("value", false); |
| if (values.length != 1) return null; |
| PsiMethod value = values[0]; |
| PsiType returnType = value.getReturnType(); |
| if (!(returnType instanceof PsiArrayType)) return null; |
| PsiType type = ((PsiArrayType)returnType).getComponentType(); |
| if (!(type instanceof PsiClassType)) return null; |
| PsiClass contained = ((PsiClassType)type).resolve(); |
| if (contained == null || !contained.isAnnotationType()) return null; |
| if (PsiImplUtil.findAnnotation(contained.getModifierList(), CommonClassNames.JAVA_LANG_ANNOTATION_REPEATABLE) == null) return null; |
| |
| return contained; |
| } |
| |
| private static boolean isAnnotationRepeatedTwice(@NotNull PsiAnnotationOwner owner, @NotNull String qualifiedName) { |
| int count = 0; |
| for (PsiAnnotation annotation : owner.getAnnotations()) { |
| PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement(); |
| if (nameRef == null) continue; |
| PsiElement resolved = nameRef.resolve(); |
| if (!(resolved instanceof PsiClass) || !qualifiedName.equals(((PsiClass)resolved).getQualifiedName())) continue; |
| count++; |
| if (count == 2) return true; |
| } |
| return false; |
| } |
| |
| @Nullable |
| public static HighlightInfo checkMissingAttributes(PsiAnnotation annotation) { |
| PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement(); |
| if (nameRef == null) return null; |
| PsiClass aClass = (PsiClass)nameRef.resolve(); |
| if (aClass != null && aClass.isAnnotationType()) { |
| Set<String> names = new HashSet<String>(); |
| PsiNameValuePair[] attributes = annotation.getParameterList().getAttributes(); |
| for (PsiNameValuePair attribute : attributes) { |
| final String name = attribute.getName(); |
| if (name != null) { |
| names.add(name); |
| } |
| else { |
| names.add(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME); |
| } |
| } |
| |
| PsiMethod[] annotationMethods = aClass.getMethods(); |
| List<String> missed = new ArrayList<String>(); |
| for (PsiMethod method : annotationMethods) { |
| if (PsiUtil.isAnnotationMethod(method)) { |
| PsiAnnotationMethod annotationMethod = (PsiAnnotationMethod)method; |
| if (annotationMethod.getDefaultValue() == null) { |
| if (!names.contains(annotationMethod.getName())) { |
| missed.add(annotationMethod.getName()); |
| } |
| } |
| } |
| } |
| |
| if (!missed.isEmpty()) { |
| StringBuffer buff = new StringBuffer("'" + missed.get(0) + "'"); |
| for (int i = 1; i < missed.size(); i++) { |
| buff.append(", "); |
| buff.append("'").append(missed.get(i)).append("'"); |
| } |
| |
| String description = JavaErrorMessages.message("annotation.missing.attribute", buff); |
| HighlightInfo info = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(nameRef).descriptionAndTooltip(description).create(); |
| IntentionAction fix = QuickFixFactory.getInstance().createAddMissingRequiredAnnotationParametersFix( |
| annotation, annotationMethods, missed); |
| QuickFixAction.registerQuickFixAction(info, fix); |
| return info; |
| } |
| } |
| |
| return null; |
| } |
| |
| @Nullable |
| public static HighlightInfo checkConstantExpression(PsiExpression expression) { |
| final PsiElement parent = expression.getParent(); |
| if (PsiUtil.isAnnotationMethod(parent) || parent instanceof PsiNameValuePair || parent instanceof PsiArrayInitializerMemberValue) { |
| if (!PsiUtil.isConstantExpression(expression)) { |
| String description = JavaErrorMessages.message("annotation.non.constant.attribute.value"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); |
| } |
| } |
| |
| return null; |
| } |
| |
| @Nullable |
| public static HighlightInfo checkValidAnnotationType(final PsiTypeElement typeElement) { |
| PsiType type = typeElement.getType(); |
| if (type.accept(AnnotationReturnTypeVisitor.INSTANCE).booleanValue()) { |
| return null; |
| } |
| String description = JavaErrorMessages.message("annotation.invalid.annotation.member.type"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(description).create(); |
| } |
| |
| private static final ElementPattern<PsiElement> ANY_ANNOTATION_ALLOWED = psiElement().andOr( |
| psiElement().withParent(PsiNameValuePair.class), |
| psiElement().withParents(PsiArrayInitializerMemberValue.class, PsiNameValuePair.class), |
| psiElement().withParents(PsiArrayInitializerMemberValue.class, PsiAnnotationMethod.class), |
| psiElement().withParent(PsiAnnotationMethod.class).afterLeaf(PsiKeyword.DEFAULT) |
| ); |
| |
| @Nullable |
| public static HighlightInfo checkApplicability(@NotNull PsiAnnotation annotation, @NotNull LanguageLevel level, @NotNull PsiFile file) { |
| if (ANY_ANNOTATION_ALLOWED.accepts(annotation)) { |
| return null; |
| } |
| |
| PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement(); |
| if (nameRef == null) return null; |
| |
| PsiAnnotationOwner owner = annotation.getOwner(); |
| PsiAnnotation.TargetType[] targets = PsiImplUtil.getTargetsForLocation(owner); |
| if (owner == null || targets.length == 0) { |
| String message = JavaErrorMessages.message("annotation.not.allowed.here"); |
| return annotationError(annotation, message); |
| } |
| |
| if (!(owner instanceof PsiModifierList)) { |
| HighlightInfo info = HighlightUtil.checkFeature(annotation, HighlightUtil.Feature.TYPE_ANNOTATIONS, level, file); |
| if (info != null) return info; |
| } |
| |
| PsiAnnotation.TargetType applicable = PsiImplUtil.findApplicableTarget(annotation, targets); |
| if (applicable == PsiAnnotation.TargetType.UNKNOWN) return null; |
| |
| if (applicable == null) { |
| String target = JavaErrorMessages.message("annotation.target." + targets[0]); |
| String message = JavaErrorMessages.message("annotation.not.applicable", nameRef.getText(), target); |
| return annotationError(annotation, message); |
| } |
| |
| if (applicable == PsiAnnotation.TargetType.TYPE_USE) { |
| if (owner instanceof PsiClassReferenceType) { |
| PsiJavaCodeReferenceElement ref = ((PsiClassReferenceType)owner).getReference(); |
| HighlightInfo info = checkReferenceTarget(annotation, ref); |
| if (info != null) return info; |
| } |
| else if (owner instanceof PsiModifierList) { |
| PsiElement nextElement = PsiTreeUtil.skipSiblingsForward((PsiModifierList)owner, |
| PsiComment.class, PsiWhiteSpace.class, PsiTypeParameterList.class); |
| if (nextElement instanceof PsiTypeElement) { |
| PsiTypeElement typeElement = (PsiTypeElement)nextElement; |
| PsiType type = typeElement.getType(); |
| if (PsiType.VOID.equals(type)) { |
| String message = JavaErrorMessages.message("annotation.not.allowed.void"); |
| return annotationError(annotation, message); |
| } |
| if (!(type instanceof PsiPrimitiveType)) { |
| PsiJavaCodeReferenceElement ref = getOutermostReferenceElement(typeElement.getInnermostComponentReferenceElement()); |
| HighlightInfo info = checkReferenceTarget(annotation, ref); |
| if (info != null) return info; |
| } |
| } |
| } |
| else if (owner instanceof PsiTypeElement) { |
| PsiElement context = PsiTreeUtil.skipParentsOfType((PsiTypeElement)owner, PsiTypeElement.class); |
| if (context instanceof PsiClassObjectAccessExpression) { |
| String message = JavaErrorMessages.message("annotation.not.allowed.class"); |
| return annotationError(annotation, message); |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| private static HighlightInfo annotationError(PsiAnnotation annotation, String message) { |
| HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(annotation).descriptionAndTooltip(message).create(); |
| QuickFixAction.registerQuickFixAction(info, new DeleteAnnotationAction(annotation)); |
| return info; |
| } |
| |
| @Nullable |
| private static HighlightInfo checkReferenceTarget(PsiAnnotation annotation, @Nullable PsiJavaCodeReferenceElement ref) { |
| if (ref == null) return null; |
| PsiElement refTarget = ref.resolve(); |
| if (refTarget == null) return null; |
| |
| String message = null; |
| if (!(refTarget instanceof PsiClass)) { |
| message = JavaErrorMessages.message("annotation.not.allowed.ref"); |
| } |
| else { |
| PsiElement parent = ref.getParent(); |
| if (parent instanceof PsiJavaCodeReferenceElement) { |
| PsiElement qualified = ((PsiJavaCodeReferenceElement)parent).resolve(); |
| if (qualified instanceof PsiMember && ((PsiMember)qualified).hasModifierProperty(PsiModifier.STATIC)) { |
| message = JavaErrorMessages.message("annotation.not.allowed.static"); |
| } |
| } |
| } |
| |
| return message != null ? annotationError(annotation, message) : null; |
| } |
| |
| @Nullable |
| private static PsiJavaCodeReferenceElement getOutermostReferenceElement(@Nullable PsiJavaCodeReferenceElement ref) { |
| if (ref == null) return null; |
| |
| PsiElement qualifier; |
| while ((qualifier = ref.getQualifier()) instanceof PsiJavaCodeReferenceElement) { |
| ref = (PsiJavaCodeReferenceElement)qualifier; |
| } |
| return ref; |
| } |
| |
| public static HighlightInfo checkForeignInnerClassesUsed(final PsiAnnotation annotation) { |
| final HighlightInfo[] infos = new HighlightInfo[1]; |
| final PsiAnnotationOwner owner = annotation.getOwner(); |
| if (owner instanceof PsiModifierList) { |
| final PsiElement parent = ((PsiModifierList)owner).getParent(); |
| if (parent instanceof PsiClass) { |
| annotation.accept(new JavaRecursiveElementWalkingVisitor() { |
| @Override |
| public void visitElement(PsiElement element) { |
| if (infos[0] != null) return; |
| super.visitElement(element); |
| } |
| |
| @Override |
| public void visitClassObjectAccessExpression(PsiClassObjectAccessExpression expression) { |
| super.visitClassObjectAccessExpression(expression); |
| final PsiTypeElement operand = expression.getOperand(); |
| final PsiClass classType = PsiUtil.resolveClassInType(operand.getType()); |
| if (classType != null) { |
| checkAccessibility(expression, classType, HighlightUtil.formatClass(classType)); |
| } |
| } |
| |
| @Override |
| public void visitReferenceExpression(PsiReferenceExpression expression) { |
| super.visitReferenceExpression(expression); |
| final PsiElement resolve = expression.resolve(); |
| if (resolve instanceof PsiField) { |
| checkAccessibility(expression, (PsiMember)resolve, HighlightUtil.formatField((PsiField)resolve)); |
| } |
| } |
| |
| private void checkAccessibility(PsiExpression expression, PsiMember resolve, String memberString) { |
| if (resolve.hasModifierProperty(PsiModifier.PRIVATE) && |
| PsiTreeUtil.isAncestor(parent, resolve, true)) { |
| String description = JavaErrorMessages.message("private.symbol", |
| memberString, |
| HighlightUtil.formatClass((PsiClass)parent)); |
| infos[0] = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); |
| } |
| } |
| }); |
| } |
| } |
| return infos[0]; |
| } |
| |
| @Nullable |
| public static HighlightInfo checkAnnotationType(PsiAnnotation annotation) { |
| PsiJavaCodeReferenceElement nameReferenceElement = annotation.getNameReferenceElement(); |
| if (nameReferenceElement != null) { |
| PsiElement resolved = nameReferenceElement.resolve(); |
| if (!(resolved instanceof PsiClass) || !((PsiClass)resolved).isAnnotationType()) { |
| String description = JavaErrorMessages.message("annotation.annotation.type.expected"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(nameReferenceElement).descriptionAndTooltip(description).create(); |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| public static HighlightInfo checkCyclicMemberType(PsiTypeElement typeElement, PsiClass aClass) { |
| LOG.assertTrue(aClass.isAnnotationType()); |
| PsiType type = typeElement.getType(); |
| final Set<PsiClass> checked = new HashSet<PsiClass>(); |
| if (cyclicDependencies(aClass, type, checked, aClass.getManager())) { |
| String description = JavaErrorMessages.message("annotation.cyclic.element.type"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(description).create(); |
| } |
| return null; |
| } |
| |
| private static boolean cyclicDependencies(PsiClass aClass, PsiType type, @NotNull Set<PsiClass> checked,@NotNull PsiManager manager) { |
| final PsiClass resolvedClass = PsiUtil.resolveClassInType(type); |
| if (resolvedClass != null && resolvedClass.isAnnotationType()) { |
| if (aClass == resolvedClass) { |
| return true; |
| } |
| if (!checked.add(resolvedClass) || !manager.isInProject(resolvedClass)) return false; |
| final PsiMethod[] methods = resolvedClass.getMethods(); |
| for (PsiMethod method : methods) { |
| if (cyclicDependencies(aClass, method.getReturnType(), checked,manager)) return true; |
| } |
| } |
| return false; |
| } |
| |
| public static HighlightInfo checkClashesWithSuperMethods(@NotNull PsiAnnotationMethod psiMethod) { |
| final PsiIdentifier nameIdentifier = psiMethod.getNameIdentifier(); |
| if (nameIdentifier != null) { |
| final PsiMethod[] methods = psiMethod.findDeepestSuperMethods(); |
| for (PsiMethod method : methods) { |
| final PsiClass containingClass = method.getContainingClass(); |
| if (containingClass != null) { |
| final String qualifiedName = containingClass.getQualifiedName(); |
| if (CommonClassNames.JAVA_LANG_OBJECT.equals(qualifiedName) || CommonClassNames.JAVA_LANG_ANNOTATION_ANNOTATION.equals(qualifiedName)) { |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(nameIdentifier).descriptionAndTooltip( |
| "@interface member clashes with '" + JavaHighlightUtil.formatMethod(method) + "' in " + HighlightUtil.formatClass(containingClass)).create(); |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| public static HighlightInfo checkAnnotationDeclaration(final PsiElement parent, final PsiReferenceList list) { |
| if (PsiUtil.isAnnotationMethod(parent)) { |
| PsiAnnotationMethod method = (PsiAnnotationMethod)parent; |
| if (list == method.getThrowsList()) { |
| String description = JavaErrorMessages.message("annotation.members.may.not.have.throws.list"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(list).descriptionAndTooltip(description).create(); |
| } |
| } |
| else if (parent instanceof PsiClass && ((PsiClass)parent).isAnnotationType()) { |
| if (PsiKeyword.EXTENDS.equals(list.getFirstChild().getText())) { |
| String description = JavaErrorMessages.message("annotation.may.not.have.extends.list"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(list).descriptionAndTooltip(description).create(); |
| } |
| } |
| return null; |
| } |
| |
| |
| @Nullable |
| public static HighlightInfo checkPackageAnnotationContainingFile(final PsiPackageStatement statement) { |
| if (statement.getAnnotationList() == null) { |
| return null; |
| } |
| PsiFile file = statement.getContainingFile(); |
| if (file != null && !PsiPackage.PACKAGE_INFO_FILE.equals(file.getName())) { |
| String description = JavaErrorMessages.message("invalid.package.annotation.containing.file"); |
| HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR); |
| builder.range(statement.getAnnotationList().getTextRange()); |
| builder.descriptionAndTooltip(description); |
| return builder.create(); |
| } |
| return null; |
| } |
| |
| @Nullable |
| public static HighlightInfo checkTargetAnnotationDuplicates(PsiAnnotation annotation) { |
| PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement(); |
| if (nameRef == null) return null; |
| |
| PsiElement resolved = nameRef.resolve(); |
| if (!(resolved instanceof PsiClass) || !CommonClassNames.JAVA_LANG_ANNOTATION_TARGET.equals(((PsiClass)resolved).getQualifiedName())) { |
| return null; |
| } |
| |
| PsiNameValuePair[] attributes = annotation.getParameterList().getAttributes(); |
| if (attributes.length < 1) return null; |
| PsiAnnotationMemberValue value = attributes[0].getValue(); |
| if (!(value instanceof PsiArrayInitializerMemberValue)) return null; |
| PsiAnnotationMemberValue[] arrayInitializers = ((PsiArrayInitializerMemberValue) value).getInitializers(); |
| Set<PsiElement> targets = new HashSet<PsiElement>(); |
| for (PsiAnnotationMemberValue initializer : arrayInitializers) { |
| if (initializer instanceof PsiReferenceExpression) { |
| PsiElement target = ((PsiReferenceExpression) initializer).resolve(); |
| if (target != null) { |
| if (targets.contains(target)) { |
| String description = JavaErrorMessages.message("repeated.annotation.target"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(initializer).descriptionAndTooltip(description).create(); |
| } |
| targets.add(target); |
| } |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| public static HighlightInfo checkFunctionalInterface(@NotNull PsiAnnotation annotation, @NotNull LanguageLevel languageLevel) { |
| final String errorMessage = LambdaUtil.checkFunctionalInterface(annotation, languageLevel); |
| if (errorMessage != null) { |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(annotation).descriptionAndTooltip(errorMessage).create(); |
| } |
| return null; |
| } |
| |
| @Nullable |
| public static HighlightInfo checkRepeatableAnnotation(PsiAnnotation annotation) { |
| String qualifiedName = annotation.getQualifiedName(); |
| if (!CommonClassNames.JAVA_LANG_ANNOTATION_REPEATABLE.equals(qualifiedName)) return null; |
| |
| String description = doCheckRepeatableAnnotation(annotation); |
| if (description != null) { |
| PsiAnnotationMemberValue containerRef = PsiImplUtil.findAttributeValue(annotation, null); |
| if (containerRef != null) { |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(containerRef).descriptionAndTooltip(description).create(); |
| } |
| } |
| |
| return null; |
| } |
| |
| @Nullable |
| private static String doCheckRepeatableAnnotation(@NotNull PsiAnnotation annotation) { |
| PsiAnnotationOwner owner = annotation.getOwner(); |
| if (!(owner instanceof PsiModifierList)) return null; |
| PsiElement target = ((PsiModifierList)owner).getParent(); |
| if (!(target instanceof PsiClass) || !((PsiClass)target).isAnnotationType()) return null; |
| PsiClass container = getRepeatableContainer(annotation); |
| if (container == null) return null; |
| |
| PsiMethod[] methods = container.findMethodsByName("value", false); |
| if (methods.length == 0) { |
| return JavaErrorMessages.message("annotation.container.no.value", container.getQualifiedName()); |
| } |
| |
| if (methods.length == 1) { |
| PsiType expected = new PsiImmediateClassType((PsiClass)target, PsiSubstitutor.EMPTY).createArrayType(); |
| if (!expected.equals(methods[0].getReturnType())) { |
| return JavaErrorMessages.message("annotation.container.bad.type", container.getQualifiedName(), JavaHighlightUtil |
| .formatType(expected)); |
| } |
| } |
| |
| RetentionPolicy targetPolicy = getRetentionPolicy((PsiClass)target); |
| if (targetPolicy != null) { |
| RetentionPolicy containerPolicy = getRetentionPolicy(container); |
| if (containerPolicy != null && targetPolicy.compareTo(containerPolicy) > 0) { |
| return JavaErrorMessages.message("annotation.container.low.retention", container.getQualifiedName(), containerPolicy); |
| } |
| } |
| |
| Set<PsiAnnotation.TargetType> repeatableTargets = PsiImplUtil.getAnnotationTargets((PsiClass)target); |
| if (repeatableTargets != null) { |
| Set<PsiAnnotation.TargetType> containerTargets = PsiImplUtil.getAnnotationTargets(container); |
| if (containerTargets != null && !repeatableTargets.containsAll(containerTargets)) { |
| return JavaErrorMessages.message("annotation.container.wide.target", container.getQualifiedName()); |
| } |
| } |
| |
| return null; |
| } |
| |
| @Nullable |
| private static PsiClass getRepeatableContainer(@NotNull PsiAnnotation annotation) { |
| PsiAnnotationMemberValue containerRef = PsiImplUtil.findAttributeValue(annotation, null); |
| if (!(containerRef instanceof PsiClassObjectAccessExpression)) return null; |
| PsiType containerType = ((PsiClassObjectAccessExpression)containerRef).getOperand().getType(); |
| if (!(containerType instanceof PsiClassType)) return null; |
| PsiClass container = ((PsiClassType)containerType).resolve(); |
| if (container == null || !container.isAnnotationType()) return null; |
| return container; |
| } |
| |
| @Nullable |
| private static RetentionPolicy getRetentionPolicy(PsiClass annotation) { |
| PsiModifierList modifierList = annotation.getModifierList(); |
| if (modifierList != null) { |
| PsiAnnotation retentionAnno = modifierList.findAnnotation(CommonClassNames.JAVA_LANG_ANNOTATION_RETENTION); |
| if (retentionAnno == null) return RetentionPolicy.CLASS; |
| |
| PsiAnnotationMemberValue policyRef = PsiImplUtil.findAttributeValue(retentionAnno, null); |
| if (policyRef instanceof PsiReference) { |
| PsiElement field = ((PsiReference)policyRef).resolve(); |
| if (field instanceof PsiEnumConstant) { |
| String name = ((PsiEnumConstant)field).getName(); |
| try { |
| return RetentionPolicy.valueOf(RetentionPolicy.class, name); |
| } |
| catch (Exception e) { |
| LOG.warn("Unknown policy: " + name); |
| } |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| public static class AnnotationReturnTypeVisitor extends PsiTypeVisitor<Boolean> { |
| public static final AnnotationReturnTypeVisitor INSTANCE = new AnnotationReturnTypeVisitor(); |
| @Override |
| public Boolean visitType(PsiType type) { |
| return Boolean.FALSE; |
| } |
| |
| @Override |
| public Boolean visitPrimitiveType(PsiPrimitiveType primitiveType) { |
| return PsiType.VOID.equals(primitiveType) || PsiType.NULL.equals(primitiveType) ? Boolean.FALSE : Boolean.TRUE; |
| } |
| |
| @Override |
| public Boolean visitArrayType(PsiArrayType arrayType) { |
| if (arrayType.getArrayDimensions() != 1) return Boolean.FALSE; |
| PsiType componentType = arrayType.getComponentType(); |
| return componentType.accept(this); |
| } |
| |
| @Override |
| public Boolean visitClassType(PsiClassType classType) { |
| if (classType.getParameters().length > 0) { |
| PsiClassType rawType = classType.rawType(); |
| return rawType.equalsToText(CommonClassNames.JAVA_LANG_CLASS); |
| } |
| |
| PsiClass aClass = classType.resolve(); |
| if (aClass != null && (aClass.isAnnotationType() || aClass.isEnum())) { |
| return Boolean.TRUE; |
| } |
| |
| return classType.equalsToText(CommonClassNames.JAVA_LANG_CLASS) || classType.equalsToText(CommonClassNames.JAVA_LANG_STRING); |
| } |
| } |
| |
| private static class DeleteAnnotationAction implements IntentionAction { |
| private final PsiAnnotation myAnnotation; |
| |
| public DeleteAnnotationAction(PsiAnnotation annotation) { |
| myAnnotation = annotation; |
| } |
| |
| @NotNull |
| @Override |
| public String getText() { |
| return "Remove"; |
| } |
| |
| @NotNull |
| @Override |
| public String getFamilyName() { |
| return getText(); |
| } |
| |
| @Override |
| public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { |
| return true; |
| } |
| |
| @Override |
| public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { |
| if (!FileModificationService.getInstance().prepareFileForWrite(file)) return; |
| myAnnotation.delete(); |
| } |
| |
| @Override |
| public boolean startInWriteAction() { |
| return true; |
| } |
| } |
| } |