diff --git a/java/debugger/impl/src/com/intellij/debugger/settings/CaptureConfigurable.java b/java/debugger/impl/src/com/intellij/debugger/settings/CaptureConfigurable.java index f078182b5de4..a44496b25551 100644 --- a/java/debugger/impl/src/com/intellij/debugger/settings/CaptureConfigurable.java +++ b/java/debugger/impl/src/com/intellij/debugger/settings/CaptureConfigurable.java @@ -514,16 +514,14 @@ public final class CaptureConfigurable implements SearchableConfigurable, NoScro mySettings = DebuggerProjectSettings.getInstance(myProject); myAsyncSchedulePanel = new AnnotationsPanel(project, JavaDebuggerBundle.message("settings.async.schedule"), - "", getAsyncAnnotations(mySettings, true), - Collections.singletonList(getAnnotationName(true)), - Collections.emptySet(), false, false); + Collections.singletonList(getAnnotationName(true)) + ); myAsyncExecutePanel = new AnnotationsPanel(project, JavaDebuggerBundle.message("settings.async.execute"), - "", getAsyncAnnotations(mySettings, false), - Collections.singletonList(getAnnotationName(false)), - Collections.emptySet(), false, false); + Collections.singletonList(getAnnotationName(false)) + ); init(); setTitle(JavaDebuggerBundle.message("settings.async.annotations.configuration")); } diff --git a/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties b/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties index b03920ca9acc..91c0d5dc90c1 100644 --- a/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties +++ b/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties @@ -157,7 +157,7 @@ inspection.annotate.overridden.method.and.self.quickfix.family.name=Annotate ove inspection.annotate.overridden.method.and.self.quickfix.name=Annotate overriding methods and self with ''@{0}'' inspection.annotate.overridden.method.quickfix.family.name=Annotate overriding methods inspection.annotate.overridden.method.quickfix.name=Annotate overriding methods with ''@{0}'' -inspection.annotate.overridden.method.nullable.quickfix.name=Annotate overriding methods as ''@{0}'' +inspection.annotate.overridden.method.nullable.quickfix.name=Add missing nullability annotation to overriding methods inspection.c.style.array.declarations.option=Ignore C-style declarations in variables inspection.can.be.final.accept.quickfix=Make final inspection.can.be.final.option1=Report methods @@ -310,7 +310,6 @@ inspection.visibility.package.private.top.level.classes=Suggest package-private inspection.visibility.private.inner.members=Suggest 'private' for inner class members when referenced from outer class only instance.member.guarded.by.static.0.loc=Instance member guarded by static "{0}" #loc instance.member.guarded.by.static.ref.loc=Instance member guarded by static #ref #loc -make.0.default.annotation=Make "{0}" default annotation make.default.the.last.case.family.name=Make 'default' the last case make.final.and.annotate.as.safevarargs=Make final and annotate as '@SafeVarargs' method.reference.mapped.to.comparator=Method reference mapped to 'Comparator' interface does not fulfill the 'Comparator' contract @@ -565,7 +564,6 @@ mutation.signature.problem.invalid.token=Invalid token: {0}; supported are ''thi required.type=Required type provided.type=Provided type.mismatch.reason=reason: {0} -inspection.message.code.generation.different.nullability.annotation.will.be.used=The generated code will use ''@{1}'' instead of ''@{0}'' change.visibility.level=Make {0} {1} inspection.redundant.tostring.option.notnull.qualifier=Report only when qualifier is known to be not-null highlighting.try.blocks=Highlighting try blocks\u2026 diff --git a/java/java-analysis-impl/resources/META-INF/JavaAnalysisPlugin.xml b/java/java-analysis-impl/resources/META-INF/JavaAnalysisPlugin.xml index 153ac86d44dd..44c2aee521fa 100644 --- a/java/java-analysis-impl/resources/META-INF/JavaAnalysisPlugin.xml +++ b/java/java-analysis-impl/resources/META-INF/JavaAnalysisPlugin.xml @@ -337,14 +337,14 @@ description="Assume any implementation of methods like Object.toString() or Iterable.iterator() to be pure during bytecode inference. This assumption might lead to false-positives in some inspections, though it's believed to uncover more bugs."/> + - - - - + + + fixes = createNPEFixes(expression, top, reporter.isOnTheFly(), alwaysNull); fixes.add(LocalQuickFix.from(AddAnnotationModCommandAction.createAddNullableFix(target))); reporter.registerProblem(expression, message, fixes.toArray(LocalQuickFix.EMPTY_ARRAY)); @@ -806,16 +804,18 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec } reporter.registerProblem(expr, text, fixes.toArray(LocalQuickFix.EMPTY_ARRAY)); } - else if (AnnotationUtil.isAnnotatingApplicable(anchor)) { - final String defaultNullable = manager.getDefaultNullable(); - final String presentableNullable = StringUtil.getShortName(defaultNullable); - final String text = exactlyNull - ? JavaAnalysisBundle.message("dataflow.message.return.null.from.notnullable", presentableNullable) - : JavaAnalysisBundle.message("dataflow.message.return.nullable.from.notnullable", presentableNullable); - PsiMethod surroundingMethod = PsiTreeUtil.getParentOfType(anchor, PsiMethod.class, true, PsiLambdaExpression.class); - final LocalQuickFix fix = surroundingMethod == null ? null : - LocalQuickFix.from(AddAnnotationModCommandAction.createAddNullableFix(surroundingMethod)); - reporter.registerProblem(expr, text, LocalQuickFix.notNullElements(fix)); + else { + final String defaultNullable = manager.getDefaultAnnotation(Nullability.NULLABLE, anchor); + if (AnnotationUtil.isAnnotatingApplicable(anchor, defaultNullable)) { + final String presentableNullable = StringUtil.getShortName(defaultNullable); + final String text = exactlyNull + ? JavaAnalysisBundle.message("dataflow.message.return.null.from.notnullable", presentableNullable) + : JavaAnalysisBundle.message("dataflow.message.return.nullable.from.notnullable", presentableNullable); + PsiMethod surroundingMethod = PsiTreeUtil.getParentOfType(anchor, PsiMethod.class, true, PsiLambdaExpression.class); + final LocalQuickFix fix = surroundingMethod == null ? null : + LocalQuickFix.from(AddAnnotationModCommandAction.createAddNullableFix(surroundingMethod)); + reporter.registerProblem(expr, text, LocalQuickFix.notNullElements(fix)); + } } } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/ChangeNullableDefaultsFix.java b/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/ChangeNullableDefaultsFix.java deleted file mode 100644 index b3e99d699491..000000000000 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/ChangeNullableDefaultsFix.java +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.codeInspection.nullable; - -import com.intellij.codeInspection.ProblemDescriptor; -import com.intellij.java.analysis.JavaAnalysisBundle; -import com.intellij.modcommand.ModCommand; -import com.intellij.modcommand.ModCommandQuickFix; -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiAnnotation; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import static com.intellij.modcommand.ModCommand.*; - -class ChangeNullableDefaultsFix extends ModCommandQuickFix { - private final String myNotNullName; - private final String myNullableName; - - ChangeNullableDefaultsFix(@Nullable PsiAnnotation notNull, @Nullable PsiAnnotation nullable) { - myNotNullName = notNull != null ? notNull.getQualifiedName() : null; - myNullableName = nullable != null ? nullable.getQualifiedName() : null; - } - - ChangeNullableDefaultsFix(String notNull, String nullable) { - myNotNullName = notNull; - myNullableName = nullable; - } - - @Override - public @NotNull String getFamilyName() { - return JavaAnalysisBundle.message("make.0.default.annotation", myNotNullName != null ? myNotNullName : myNullableName); - } - - @Override - public @NotNull ModCommand perform(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { - ModCommand command = nop(); - if (myNotNullName != null) { - command = command.andThen( - updateOptionList(descriptor.getPsiElement(), "NullableNotNullManager.myNotNulls", list -> { - if (!list.contains(myNotNullName)) { - list.add(myNotNullName); - } - })).andThen(updateOption(descriptor.getPsiElement(), "NullableNotNullManager.myDefaultNotNull", myNotNullName)); - } - else { - command = command.andThen( - updateOptionList(descriptor.getPsiElement(), "NullableNotNullManager.myNullables", list -> { - if (!list.contains(myNullableName)) { - list.add(myNullableName); - } - })).andThen(updateOption(descriptor.getPsiElement(), "NullableNotNullManager.myDefaultNullable", myNullableName)); - } - return command; - } -} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java index 145e182e7272..5123c1214544 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java @@ -33,7 +33,6 @@ import com.intellij.psi.impl.search.JavaOverridingMethodsSearcher; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.searches.OverridingMethodsSearch; import com.intellij.psi.util.*; -import com.intellij.psi.util.ClassUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.ArrayUtilRt; import com.intellij.util.containers.ContainerUtil; @@ -153,7 +152,7 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection if (classType.getPsiContext() instanceof PsiJavaCodeReferenceElement typeRef && typeRef.getParent() instanceof PsiTypeElement typeElement && typeElement.getType().equals(classType) && typeElement.acceptsAnnotations()) { - fix = new AddTypeAnnotationFix(typeElement, manager.getDefaultNullable(), notNulls); + fix = new AddTypeAnnotationFix(typeElement, manager.getDefaultAnnotation(Nullability.NULLABLE, expression), notNulls); } holder.problem(expression, JavaAnalysisBundle.message("inspection.nullable.problems.constructor.not.compatible.non.null.type.argument")) @@ -183,11 +182,10 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection } Project project = holder.getProject(); if (annotated.isDeclaredNotNull ^ annotated.isDeclaredNullable) { - final String anno = annotated.isDeclaredNotNull ? manager.getDefaultNotNull() : manager.getDefaultNullable(); + final String anno = + manager.getDefaultAnnotation(annotated.isDeclaredNotNull ? Nullability.NOT_NULL : Nullability.NULLABLE, field); final List annoToRemove = annotated.isDeclaredNotNull ? nullables : notNulls; - if (!checkNonStandardAnnotations(field, annotated, anno, holder)) return; - checkAccessors(field, annotated, project, manager, anno, annoToRemove, holder); checkConstructorParameters(field, annotated, manager, anno, annoToRemove, holder); @@ -413,7 +411,7 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection Nullability typeNullability = DfaPsiUtil.getTypeNullability(type); if (typeNullability != Nullability.NOT_NULL && !(typeNullability == Nullability.UNKNOWN && type instanceof PsiWildcardType wildcardType && !wildcardType.isExtends())) { - String annotationToAdd = manager.getDefaultNotNull(); + String annotationToAdd = manager.getDefaultAnnotation(Nullability.NOT_NULL, reference); PsiClass annotationClass = JavaPsiFacade.getInstance(project).findClass(annotationToAdd, element.getResolveScope()); AddTypeAnnotationFix fix = null; if (annotationClass != null && @@ -624,23 +622,6 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection return ContainerUtil.find(NullableNotNullManager.getInstance(project).getNullables(), s -> facade.findClass(s, scope) != null) == null; } - private static boolean checkNonStandardAnnotations(PsiField field, - Annotated annotated, - String anno, @NotNull ProblemsHolder holder) { - if (!isAnnotatingApplicable(field, anno)) { - PsiAnnotation annotation = requireNonNull(annotated.isDeclaredNullable ? annotated.nullable : annotated.notNull); - String message = JavaAnalysisBundle.message("inspection.message.code.generation.different.nullability.annotation.will.be.used", - annotation.getQualifiedName(), anno); - final PsiJavaCodeReferenceElement annotationNameReferenceElement = annotation.getNameReferenceElement(); - holder.registerProblem(annotationNameReferenceElement != null && annotationNameReferenceElement.isPhysical() ? annotationNameReferenceElement : field.getNameIdentifier(), - message, - ProblemHighlightType.WEAK_WARNING, - new ChangeNullableDefaultsFix(annotated.notNull, annotated.nullable)); - return false; - } - return true; - } - private void checkAccessors(PsiField field, Annotated annotated, Project project, @@ -882,7 +863,7 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection } if (isNonAnnotatedOverridingNotNull(method, superMethod)) { - reportProblem(holder, identifier, createFixForNonAnnotatedOverridesNotNull(method, superMethod), + reportProblem(holder, identifier, createFixForNonAnnotatedOverridesNotNull(method), "inspection.nullable.problems.method.overrides.NotNull", getPresentableAnnoName(superMethod)); break; } @@ -900,12 +881,11 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection return NullableNotNullManager.getInstance(method.getProject()); } - private static @Nullable LocalQuickFix createFixForNonAnnotatedOverridesNotNull(PsiMethod method, - PsiMethod superMethod) { + private static @Nullable LocalQuickFix createFixForNonAnnotatedOverridesNotNull(PsiMethod method) { NullableNotNullManager nullableManager = getNullityManager(method); return isAnnotatingApplicable(method, nullableManager.getDefaultNotNull()) ? LocalQuickFix.from(AddAnnotationModCommandAction.createAddNotNullFix(method)) - : createChangeDefaultNotNullFix(nullableManager, superMethod); + : null; } private boolean isNullableOverridingNotNull(Annotated methodInfo, PsiMethod superMethod) { @@ -975,9 +955,9 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection } PsiParameter notNullSuper = findNotNullSuperForNonAnnotatedParameter(nullableManager, parameter, superParameters); if (notNullSuper != null) { - LocalQuickFix fix = isAnnotatingApplicable(parameter, nullableManager.getDefaultNotNull()) + LocalQuickFix fix = isAnnotatingApplicable(parameter, nullableManager.getDefaultAnnotation(Nullability.NOT_NULL, parameter)) ? LocalQuickFix.from(AddAnnotationModCommandAction.createAddNotNullFix(parameter)) - : createChangeDefaultNotNullFix(nullableManager, notNullSuper); + : null; reportProblem(holder, nameIdentifier, fix, "inspection.nullable.problems.parameter.overrides.NotNull", getPresentableAnnoName(notNullSuper)); } @@ -1095,14 +1075,13 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection } boolean checkReturnType = annotated.isDeclaredNotNull && !hasInheritableNotNull(method) && !(method.getReturnType() instanceof PsiPrimitiveType); if (hasAnnotatedParameter || checkReturnType) { - final String defaultNotNull = nullableManager.getDefaultNotNull(); - final boolean superMethodApplicable = isAnnotatingApplicable(method, defaultNotNull); PsiMethod[] overridings = OverridingMethodsSearch.search(method).toArray(PsiMethod.EMPTY_ARRAY); boolean methodQuickFixSuggested = false; for (PsiMethod overriding : overridings) { if (shouldSkipOverriderAsGenerated(overriding)) continue; + final String defaultNotNull = nullableManager.getDefaultAnnotation(Nullability.NOT_NULL, overriding); if (!methodQuickFixSuggested && checkReturnType && !isNotNullNotInferred(overriding, false, false) @@ -1115,8 +1094,8 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection final String[] annotationsToRemove = ArrayUtilRt.toStringArray(nullableManager.getNullables()); LocalQuickFix fix = isAnnotatingApplicable(overriding, defaultNotNull) - ? new MyAnnotateMethodFix(defaultNotNull, annotationsToRemove) - : superMethodApplicable ? null : createChangeDefaultNotNullFix(nullableManager, method); + ? new MyAnnotateMethodFix(Nullability.NOT_NULL, annotationsToRemove) + : null; PsiElement psiElement = annotation; if (!annotation.isPhysical() || !PsiTreeUtil.isAncestor(method, annotation, true)) { @@ -1145,7 +1124,7 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection } LocalQuickFix fix = isAnnotatingApplicable(parameter, defaultNotNull) ? new AnnotateOverriddenMethodParameterFix(Nullability.NOT_NULL, defaultNotNull) - : createChangeDefaultNotNullFix(nullableManager, parameters[i]); + : null; reportProblem(holder, psiElement, fix, "nullable.stuff.problems.overridden.method.parameters.are.not.annotated"); parameterQuickFixSuggested[i] = true; @@ -1183,28 +1162,12 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection (checkBases || info.getInheritedFrom() == null); } - private static LocalQuickFix createChangeDefaultNotNullFix(NullableNotNullManager nullableManager, PsiModifierListOwner modifierListOwner) { - final PsiAnnotation annotation = findAnnotation(modifierListOwner, nullableManager.getNotNulls()); - if (annotation != null) { - final PsiJavaCodeReferenceElement referenceElement = annotation.getNameReferenceElement(); - if (referenceElement != null) { - JavaResolveResult resolveResult = referenceElement.advancedResolve(false); - if (resolveResult.getElement() != null && - resolveResult.isValidResult() && - !nullableManager.getDefaultNotNull().equals(annotation.getQualifiedName())) { - return new ChangeNullableDefaultsFix(annotation.getQualifiedName(), null); - } - } - } - return null; - } - private static class MyAnnotateMethodFix implements LocalQuickFix { - protected final String myAnnotation; + private final @NotNull Nullability myNullability; private final String[] myAnnotationsToRemove; - MyAnnotateMethodFix(@NotNull String fqn, String @NotNull ... annotationsToRemove) { - myAnnotation = fqn; + MyAnnotateMethodFix(@NotNull Nullability nullability, String @NotNull ... annotationsToRemove) { + myNullability = nullability; myAnnotationsToRemove = annotationsToRemove.length == 0 ? ArrayUtilRt.EMPTY_STRING_ARRAY : annotationsToRemove; } @@ -1225,10 +1188,14 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection PsiMethod method = PsiTreeUtil.getParentOfType(psiElement, PsiMethod.class); if (method == null) return; final List toAnnotate = new ArrayList<>(); + NullableNotNullManager manager = NullableNotNullManager.getInstance(project); if (!AnnotateOverriddenMethodParameterFix.processModifiableInheritorsUnderProgress(method, (Consumer)psiMethod -> { - if (isAnnotatingApplicable(psiMethod, myAnnotation) && - !isAnnotated(psiMethod, myAnnotation, CHECK_EXTERNAL | CHECK_TYPE)) { + NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(psiMethod); + if (info != null && info.getNullability() == myNullability && !info.isInferred() && info.getInheritedFrom() == null) return; + String annotation = manager.getDefaultAnnotation(myNullability, psiMethod); + if (isAnnotatingApplicable(psiMethod, annotation) && + !isAnnotated(psiMethod, annotation, CHECK_EXTERNAL | CHECK_TYPE)) { toAnnotate.add(psiMethod); } })) { @@ -1237,7 +1204,8 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection FileModificationService.getInstance().preparePsiElementsForWrite(toAnnotate); for (PsiMethod psiMethod : toAnnotate) { - AddAnnotationPsiFix fix = new AddAnnotationPsiFix(myAnnotation, psiMethod, myAnnotationsToRemove); + String annotation = manager.getDefaultAnnotation(myNullability, psiMethod); + AddAnnotationPsiFix fix = new AddAnnotationPsiFix(annotation, psiMethod, myAnnotationsToRemove); fix.invoke(psiMethod.getProject(), psiMethod.getContainingFile(), psiMethod, psiMethod); } UndoUtil.markPsiFileForUndo(method.getContainingFile()); @@ -1250,8 +1218,7 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection @Override public @NotNull String getName() { - return JavaAnalysisBundle.message("inspection.annotate.overridden.method.nullable.quickfix.name", - ClassUtil.extractClassName(myAnnotation)); + return JavaAnalysisBundle.message("inspection.annotate.overridden.method.nullable.quickfix.name"); } } } diff --git a/java/java-analysis-impl/src/com/siyeh/ig/assignment/AssignmentToNullInspection.java b/java/java-analysis-impl/src/com/siyeh/ig/assignment/AssignmentToNullInspection.java index 7378b13ce89d..194971342187 100644 --- a/java/java-analysis-impl/src/com/siyeh/ig/assignment/AssignmentToNullInspection.java +++ b/java/java-analysis-impl/src/com/siyeh/ig/assignment/AssignmentToNullInspection.java @@ -15,6 +15,7 @@ */ package com.siyeh.ig.assignment; +import com.intellij.codeInsight.Nullability; import com.intellij.codeInsight.NullableNotNullManager; import com.intellij.codeInsight.intention.AddAnnotationModCommandAction; import com.intellij.codeInspection.LocalQuickFix; @@ -58,7 +59,7 @@ public final class AssignmentToNullInspection extends BaseInspection { return null; } final NullableNotNullManager manager = NullableNotNullManager.getInstance(target.getProject()); - String annotation = manager.getDefaultNullable(); + String annotation = manager.getDefaultAnnotation(Nullability.NULLABLE, variable); if (JavaPsiFacade.getInstance(variable.getProject()).findClass(annotation, variable.getResolveScope()) == null) { return null; } diff --git a/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethod/ExtractMethodDialog.java b/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethod/ExtractMethodDialog.java index 5abae78932e9..12e872aa99c7 100644 --- a/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethod/ExtractMethodDialog.java +++ b/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethod/ExtractMethodDialog.java @@ -591,11 +591,11 @@ public class ExtractMethodDialog extends RefactoringDialog implements AbstractEx protected String getSignature() { final @NonNls StringBuilder buffer = new StringBuilder(); - if (myGenerateAnnotations != null && myGenerateAnnotations.isSelected()) { + if (myGenerateAnnotations != null && myGenerateAnnotations.isSelected() && myNullability != null) { final NullableNotNullManager nullManager = NullableNotNullManager.getInstance(myProject); buffer.append("@"); buffer.append( - StringUtil.getShortName(myNullability == Nullability.NULLABLE ? nullManager.getDefaultNullable() : nullManager.getDefaultNotNull())); + StringUtil.getShortName(nullManager.getDefaultAnnotation(myNullability, myTargetClass))); buffer.append("\n"); } final int declarationOffset = buffer.length(); diff --git a/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethod/ExtractMethodProcessor.java b/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethod/ExtractMethodProcessor.java index 844f05c20c06..588930a1bcdc 100644 --- a/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethod/ExtractMethodProcessor.java +++ b/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethod/ExtractMethodProcessor.java @@ -711,8 +711,9 @@ public class ExtractMethodProcessor implements MatchProvider { private Nullability initNullability() { if (!PsiUtil.isAvailable(JavaFeature.ANNOTATIONS, myElements[0]) || PsiUtil.resolveClassInType(myReturnType) == null) return null; final NullableNotNullManager manager = NullableNotNullManager.getInstance(myProject); + String defaultAnnotation = manager.getDefaultAnnotation(Nullability.NULLABLE, myElements[0]); final PsiClass nullableAnnotationClass = JavaPsiFacade.getInstance(myProject) - .findClass(manager.getDefaultNullable(), myElements[0].getResolveScope()); + .findClass(defaultAnnotation, myElements[0].getResolveScope()); if (nullableAnnotationClass != null) { final PsiElement elementInCopy = myTargetClass.getContainingFile().copy().findElementAt(myTargetClass.getTextOffset()); final PsiClass classCopy = PsiTreeUtil.getParentOfType(elementInCopy, PsiClass.class); @@ -1076,9 +1077,9 @@ public class ExtractMethodProcessor implements MatchProvider { NullableNotNullManager nullManager = NullableNotNullManager.getInstance(myProject); switch (myNullability) { case NOT_NULL -> - updateAnnotations(newMethod, nullManager.getNullables(), nullManager.getDefaultNotNull(), nullManager.getNotNulls()); + updateAnnotations(newMethod, nullManager.getNullables(), nullManager.getDefaultAnnotation(myNullability, myTargetClass), nullManager.getNotNulls()); case NULLABLE -> - updateAnnotations(newMethod, nullManager.getNotNulls(), nullManager.getDefaultNullable(), nullManager.getNullables()); + updateAnnotations(newMethod, nullManager.getNotNulls(), nullManager.getDefaultAnnotation(myNullability, myTargetClass), nullManager.getNullables()); default -> { } } } @@ -1691,7 +1692,7 @@ public class ExtractMethodProcessor implements MatchProvider { boolean isNotNull = nullability == Nullability.NOT_NULL; final List toKeep = isNotNull ? notNullAnnotations : nullableAnnotations; final List toRemove = isNotNull ? nullableAnnotations : notNullAnnotations; - final String toAdd = isNotNull ? nullabilityManager.getDefaultNotNull() : nullabilityManager.getDefaultNullable(); + final String toAdd = nullabilityManager.getDefaultAnnotation(nullability, parm); updateAnnotations(parm, toRemove, toAdd, toKeep); } } diff --git a/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethod/newImpl/ExtractMethodHelper.kt b/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethod/newImpl/ExtractMethodHelper.kt index 0905af3cea5f..f250e25c494c 100644 --- a/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethod/newImpl/ExtractMethodHelper.kt +++ b/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethod/newImpl/ExtractMethodHelper.kt @@ -55,8 +55,9 @@ object ExtractMethodHelper { fun isNullabilityAvailable(extractOptions: ExtractOptions): Boolean { val project = extractOptions.project - val scope = extractOptions.elements.first().resolveScope - val defaultNullable = NullableNotNullManager.getInstance(project).defaultNullable + val element = extractOptions.elements.first() + val scope = element.resolveScope + val defaultNullable = NullableNotNullManager.getInstance(project).getDefaultAnnotation(Nullability.NULLABLE, element) val annotationClass = JavaPsiFacade.getInstance(project).findClass(defaultNullable, scope) return annotationClass != null } @@ -119,12 +120,9 @@ object ExtractMethodHelper { fun addNullabilityAnnotation(typeElement: PsiTypeElement?, nullability: Nullability) { if (typeElement == null) return + if (nullability == Nullability.UNKNOWN) return val nullabilityManager = NullableNotNullManager.getInstance(typeElement.project) - val annotation = when (nullability) { - Nullability.NOT_NULL -> nullabilityManager.defaultNotNull - Nullability.NULLABLE -> nullabilityManager.defaultNullable - else -> return - } + val annotation = nullabilityManager.getDefaultAnnotation(nullability, typeElement) val annotationElement = AddAnnotationPsiFix.addPhysicalAnnotationIfAbsent(annotation, PsiNameValuePair.EMPTY_ARRAY, typeElement) if (annotationElement != null) { JavaCodeStyleManager.getInstance(typeElement.project).shortenClassReferences(annotationElement) diff --git a/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethodObject/ExtractMethodObjectProcessor.java b/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethodObject/ExtractMethodObjectProcessor.java index 142b91423708..3f2002b0f3fc 100644 --- a/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethodObject/ExtractMethodObjectProcessor.java +++ b/java/java-impl-refactorings/src/com/intellij/refactoring/extractMethodObject/ExtractMethodObjectProcessor.java @@ -3,6 +3,7 @@ package com.intellij.refactoring.extractMethodObject; import com.intellij.application.options.CodeStyle; +import com.intellij.codeInsight.Nullability; import com.intellij.codeInsight.NullableNotNullManager; import com.intellij.codeInsight.generation.GenerateMembersUtil; import com.intellij.java.refactoring.JavaRefactoringBundle; @@ -630,7 +631,7 @@ public class ExtractMethodObjectProcessor extends BaseRefactoringProcessor { final PsiModifierList modifierList = field.getModifierList(); LOG.assertTrue(modifierList != null); if (NullableNotNullManager.isNullable(parameter)) { - final String annotationName = NullableNotNullManager.getInstance(myProject).getDefaultNullable(); + final String annotationName = NullableNotNullManager.getInstance(myProject).getDefaultAnnotation(Nullability.NULLABLE, field); modifierList.addAfter(myElementFactory.createAnnotationFromText("@" + annotationName, field), null); } modifierList.setModifierProperty(PsiModifier.FINAL, isFinal); diff --git a/java/java-impl-refactorings/src/com/intellij/refactoring/introduceParameter/IntroduceParameterHandler.java b/java/java-impl-refactorings/src/com/intellij/refactoring/introduceParameter/IntroduceParameterHandler.java index 3e21caeb2ec2..521b2b33dbf6 100644 --- a/java/java-impl-refactorings/src/com/intellij/refactoring/introduceParameter/IntroduceParameterHandler.java +++ b/java/java-impl-refactorings/src/com/intellij/refactoring/introduceParameter/IntroduceParameterHandler.java @@ -33,6 +33,7 @@ import com.intellij.psi.*; import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.psi.codeStyle.SuggestedNameInfo; import com.intellij.psi.codeStyle.VariableKind; +import com.intellij.psi.impl.source.PsiFileImpl; import com.intellij.psi.search.PsiElementProcessor; import com.intellij.psi.util.PsiFormatUtil; import com.intellij.psi.util.PsiFormatUtilBase; @@ -561,6 +562,7 @@ public class IntroduceParameterHandler extends IntroduceHandlerBase { else { final PsiFile copy = PsiFileFactory.getInstance(project) .createFileFromText(file.getName(), file.getFileType(), file.getText(), file.getModificationStamp(), false); + ((PsiFileImpl)copy).setOriginalFile(file); final TextRange range = new TextRange(elements[0].getTextRange().getStartOffset(), elements[elements.length - 1].getTextRange().getEndOffset()); final PsiExpression exprInRange = CodeInsightUtil.findExpressionInRange(copy, range.getStartOffset(), range.getEndOffset()); diff --git a/java/java-impl/src/com/intellij/codeInsight/MakeInferredAnnotationExplicit.java b/java/java-impl/src/com/intellij/codeInsight/MakeInferredAnnotationExplicit.java index b7e4209af9e0..2b5cb5980a99 100644 --- a/java/java-impl/src/com/intellij/codeInsight/MakeInferredAnnotationExplicit.java +++ b/java/java-impl/src/com/intellij/codeInsight/MakeInferredAnnotationExplicit.java @@ -57,7 +57,7 @@ public final class MakeInferredAnnotationExplicit extends BaseIntentionAction { List annotations = getAnnotationsToAdd(owner); if (!annotations.isEmpty()) { String presentation = StreamEx.of(annotations) - .map(MakeInferredAnnotationExplicit::getAnnotationPresentation) + .map(annotation -> getAnnotationPresentation(file, annotation)) .joining(" "); setText(CommonQuickFixBundle.message("fix.insert.x", presentation)); return true; @@ -76,8 +76,8 @@ public final class MakeInferredAnnotationExplicit extends BaseIntentionAction { return ContainerUtil.filter(annotations, anno -> !isJetBrainsAnnotation(anno)); } - private static @NotNull String getAnnotationPresentation(PsiAnnotation annotation) { - final PsiJavaCodeReferenceElement nameRef = correctAnnotation(annotation).getNameReferenceElement(); + private static @NotNull String getAnnotationPresentation(@NotNull PsiFile file, PsiAnnotation annotation) { + final PsiJavaCodeReferenceElement nameRef = correctAnnotation(file, annotation).getNameReferenceElement(); final String name = nameRef != null ? nameRef.getReferenceName() : annotation.getQualifiedName(); return "@" + name + annotation.getParameterList().getText(); } @@ -160,11 +160,12 @@ public final class MakeInferredAnnotationExplicit extends BaseIntentionAction { } private @Unmodifiable @NotNull List getAnnotationsToAdd(@NotNull PsiModifierListOwner owner) { + PsiFile file = owner.getContainingFile(); List allAnnotations = StreamEx.of(InferredAnnotationsManager.getInstance(owner.getProject()).findInferredAnnotations(owner)) .remove(DefaultInferredAnnotationProvider::isExperimentalInferredAnnotation) - .map(MakeInferredAnnotationExplicit::correctAnnotation) + .map(annotation -> correctAnnotation(file, annotation)) .toList(); - return filterAnnotations(owner.getContainingFile(), allAnnotations); + return filterAnnotations(file, allAnnotations); } private static void doMakeAnnotationExplicit(@NotNull Project project, @NotNull PsiModifierListOwner owner, @NotNull List annotations) { @@ -179,15 +180,15 @@ public final class MakeInferredAnnotationExplicit extends BaseIntentionAction { } } - private static @NotNull PsiAnnotation correctAnnotation(@NotNull PsiAnnotation annotation) { + private static @NotNull PsiAnnotation correctAnnotation(@NotNull PsiFile file, @NotNull PsiAnnotation annotation) { Project project = annotation.getProject(); NullableNotNullManager nnnm = NullableNotNullManager.getInstance(project); PsiAnnotation corrected = null; if (annotation.hasQualifiedName(AnnotationUtil.NULLABLE)) { - corrected = createAnnotation(project, nnnm.getDefaultNullable()); + corrected = createAnnotation(project, nnnm.getDefaultAnnotation(Nullability.NULLABLE, file)); } else if (annotation.hasQualifiedName(AnnotationUtil.NOT_NULL)) { - corrected = createAnnotation(project, nnnm.getDefaultNotNull()); + corrected = createAnnotation(project, nnnm.getDefaultAnnotation(Nullability.NOT_NULL, file)); } return corrected != null ? corrected : annotation; } diff --git a/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java b/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java index f97b948c95a1..5f8900c512ab 100644 --- a/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java +++ b/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java @@ -11,12 +11,15 @@ import com.intellij.ide.highlighter.JavaFileType; import com.intellij.java.JavaBundle; import com.intellij.java.codeserver.core.JavaPsiModuleUtil; import com.intellij.java.library.JavaLibraryModificationTracker; +import com.intellij.java.library.JavaLibraryUtil; import com.intellij.openapi.Disposable; import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.State; import com.intellij.openapi.extensions.ExtensionPointListener; import com.intellij.openapi.extensions.PluginDescriptor; import com.intellij.openapi.fileTypes.FileTypeRegistry; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectFileIndex; @@ -27,6 +30,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.profile.codeInspection.ProjectInspectionProfileManager; import com.intellij.psi.*; import com.intellij.psi.impl.java.stubs.index.JavaAnnotationIndex; +import com.intellij.psi.impl.source.DummyHolder; import com.intellij.psi.search.DelegatingGlobalSearchScope; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.CachedValueProvider.Result; @@ -61,6 +65,7 @@ public class NullableNotNullManagerImpl extends NullableNotNullManager implement private List myDefaultAll; public String myDefaultNullable = NULLABLE; public String myDefaultNotNull = NOT_NULL; + public boolean myOrdered = false; public final JDOMExternalizableStringList myNullables = new JDOMExternalizableStringList(); public final JDOMExternalizableStringList myNotNulls = new JDOMExternalizableStringList(); private List myInstrumentedNotNulls = List.of(NOT_NULL); @@ -85,11 +90,11 @@ public class NullableNotNullManagerImpl extends NullableNotNullManager implement private void updateDefaults() { myAnnotationSupports = AnnotationPackageSupport.EP_NAME.getExtensionList(); myDefaultNullables = StreamEx.of(myAnnotationSupports) - .cross(s -> s.getNullabilityAnnotations(Nullability.NULLABLE).stream()).invert().toMap(); + .cross(s -> s.getNullabilityAnnotations(Nullability.NULLABLE).stream()).invert().toCustomMap(LinkedHashMap::new); myDefaultNotNulls = StreamEx.of(myAnnotationSupports) - .cross(s -> s.getNullabilityAnnotations(Nullability.NOT_NULL).stream()).invert().toMap(); + .cross(s -> s.getNullabilityAnnotations(Nullability.NOT_NULL).stream()).invert().toCustomMap(LinkedHashMap::new); myDefaultUnknowns = StreamEx.of(myAnnotationSupports) - .cross(s -> s.getNullabilityAnnotations(Nullability.UNKNOWN).stream()).invert().toMap(); + .cross(s -> s.getNullabilityAnnotations(Nullability.UNKNOWN).stream()).invert().toCustomMap(LinkedHashMap::new); myDefaultAll = StreamEx.of(myDefaultNullables, myDefaultNotNulls, myDefaultUnknowns).toFlatList(Map::keySet); normalizeDefaults(); } @@ -108,6 +113,39 @@ public class NullableNotNullManagerImpl extends NullableNotNullManager implement normalizeDefaults(); } + @Override + public @NotNull String getDefaultAnnotation(@NotNull Nullability nullability, @NotNull PsiElement context) { + Collection annotations = switch(nullability) { + case NOT_NULL -> myNotNulls; + case NULLABLE -> myNullables; + case UNKNOWN -> myDefaultUnknowns.keySet(); + }; + PsiFile containingFile = context.getContainingFile(); + if (containingFile instanceof DummyHolder) { + PsiElement element = containingFile.getContext(); + if (element != null) { + containingFile = element.getContainingFile(); + } + } + PsiFile file = containingFile.getOriginalFile(); + Module module = ModuleUtilCore.findModuleForFile(file); + if (module == null) return getDefaultAnnotation(nullability); + for (String annotation : annotations) { + if (JavaLibraryUtil.hasLibraryClass(module, annotation)) { + return annotation; + } + } + return getDefaultAnnotation(nullability); + } + + private @NotNull String getDefaultAnnotation(@NotNull Nullability nullability) { + return switch (nullability) { + case NOT_NULL -> getDefaultNotNull(); + case NULLABLE -> getDefaultNullable(); + case UNKNOWN -> AnnotationUtil.UNKNOWN_NULLABILITY; + }; + } + @Override public @NotNull String getDefaultNullable() { return myDefaultNullable; @@ -227,15 +265,16 @@ public class NullableNotNullManagerImpl extends NullableNotNullManager implement private boolean hasDefaultValues() { return NOT_NULL.equals(myDefaultNotNull) && NULLABLE.equals(myDefaultNullable) && - new HashSet<>(myNullables).equals(myDefaultNullables.keySet()) && - new HashSet<>(myNotNulls).equals(myDefaultNotNulls.keySet()); + myNullables.equals(new ArrayList<>(myDefaultNullables.keySet())) && + myNotNulls.equals(new ArrayList<>(myDefaultNotNulls.keySet())); } - + @Override public void loadState(@NotNull Element state) { try { DefaultJDOMExternalizer.readExternal(this, state); normalizeDefaults(); + migrateSettings(); } catch (InvalidDataException e) { LOG.error(e); @@ -250,6 +289,31 @@ public class NullableNotNullManagerImpl extends NullableNotNullManager implement } } + /** + * Convert old settings when the order of annotations didn't matter: + * 1. Put the default annotation on top, unless it's JetBrains default annotation + * 2. Next, put all the custom annotations + * 3. Next, put all annotations from {@link AnnotationPackageSupport} extensions in the order they are provided + */ + private void migrateSettings() { + if (!myOrdered) { + myOrdered = true; + // Set the default order and put default to the front + myNullables.removeAll(myDefaultNullables.keySet()); + myNullables.addAll(myDefaultNullables.keySet()); + if (!myDefaultNullable.equals(NULLABLE)) { + myNullables.remove(myDefaultNullable); + myNullables.add(0, myDefaultNullable); + } + myNotNulls.removeAll(myDefaultNotNulls.keySet()); + myNotNulls.addAll(myDefaultNotNulls.keySet()); + if (!myDefaultNotNull.equals(NOT_NULL)) { + myNotNulls.remove(myDefaultNotNull); + myNotNulls.add(0, myDefaultNotNull); + } + } + } + private void normalizeDefaults() { myNotNulls.removeAll(myDefaultNullables.keySet()); myNullables.removeAll(myDefaultNotNulls.keySet()); diff --git a/java/java-impl/src/com/intellij/codeInsight/annoPackages/AnnotationPackageSupport.java b/java/java-impl/src/com/intellij/codeInsight/annoPackages/AnnotationPackageSupport.java index 60d75c1464b1..951c7d42a29b 100644 --- a/java/java-impl/src/com/intellij/codeInsight/annoPackages/AnnotationPackageSupport.java +++ b/java/java-impl/src/com/intellij/codeInsight/annoPackages/AnnotationPackageSupport.java @@ -3,15 +3,19 @@ package com.intellij.codeInsight.annoPackages; import com.intellij.codeInsight.ContextNullabilityInfo; import com.intellij.codeInsight.Nullability; +import com.intellij.codeInsight.NullableNotNullManager; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.psi.PsiAnnotation; +import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; import java.util.Collections; import java.util.List; /** - * Support for custom annotation packages + * Support for custom annotation packages. + * The registration order of extension matters, as the annotations returned from the first {@link #getNullabilityAnnotations(Nullability)} + * will be preferred by default. */ public interface AnnotationPackageSupport { ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.lang.jvm.annotationPackageSupport"); @@ -32,7 +36,9 @@ public interface AnnotationPackageSupport { /** * @param nullability desired nullability - * @return list of explicit annotations which denote given nullability (and may denote additional semantics) + * @return list of explicit annotations which denote given nullability (and may denote additional semantics). + * The annotation returned first will be preferred by default + * in {@link NullableNotNullManager#getDefaultAnnotation(Nullability, PsiElement)}. */ default @NotNull List getNullabilityAnnotations(@NotNull Nullability nullability) { return Collections.emptyList(); diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaClassNameInsertHandler.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaClassNameInsertHandler.java index 8169958e1192..481189cdb955 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaClassNameInsertHandler.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaClassNameInsertHandler.java @@ -3,6 +3,7 @@ package com.intellij.codeInsight.completion; import com.intellij.codeInsight.AutoPopupController; import com.intellij.codeInsight.ExpectedTypesProvider; +import com.intellij.codeInsight.Nullability; import com.intellij.codeInsight.NullableNotNullManager; import com.intellij.codeInsight.lookup.Lookup; import com.intellij.codeInsight.lookup.LookupElement; @@ -109,7 +110,7 @@ class JavaClassNameInsertHandler implements InsertHandler toRemove) { + new AddAnnotationPsiFix(fqn, element, ArrayUtil.toStringArray(toRemove)).applyFix(); } public int getCount() { return myNotNullSet.size() + myNullableSet.size(); } - public static boolean apply(@NotNull Project project, @NotNull NullableNotNullManager manager, UsageInfo info) { + public static boolean apply(@NotNull NullableNotNullManager manager, UsageInfo info) { if (info instanceof NullableUsageInfo) { return annotateNullable(manager, (PsiModifierListOwner)info.getElement()); } diff --git a/java/java-impl/src/com/siyeh/ig/bugs/ReturnNullInspection.java b/java/java-impl/src/com/siyeh/ig/bugs/ReturnNullInspection.java index 843c431f6cb6..f47025c3ab5a 100644 --- a/java/java-impl/src/com/siyeh/ig/bugs/ReturnNullInspection.java +++ b/java/java-impl/src/com/siyeh/ig/bugs/ReturnNullInspection.java @@ -1,7 +1,6 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.siyeh.ig.bugs; -import com.intellij.codeInsight.AnnotationUtil; import com.intellij.codeInsight.Nullability; import com.intellij.codeInsight.NullabilityAnnotationInfo; import com.intellij.codeInsight.NullableNotNullManager; @@ -20,7 +19,6 @@ import com.intellij.openapi.project.Project; import com.intellij.psi.*; import com.intellij.psi.controlFlow.DefUseUtil; import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.util.ArrayUtilRt; import com.siyeh.InspectionGadgetsBundle; import com.siyeh.ig.BaseInspection; import com.siyeh.ig.BaseInspectionVisitor; @@ -79,10 +77,6 @@ public final class ReturnNullInspection extends BaseInspection { @Override protected @Nullable LocalQuickFix buildFix(Object... infos) { final PsiElement elt = (PsiElement)infos[0]; - if (!AnnotationUtil.isAnnotatingApplicable(elt)) { - return null; - } - final PsiMethod method = PsiTreeUtil.getParentOfType(elt, PsiMethod.class, false, PsiLambdaExpression.class); if (method == null) return null; final PsiType type = method.getReturnType(); @@ -91,9 +85,7 @@ public final class ReturnNullInspection extends BaseInspection { return new ReplaceWithEmptyOptionalFix(((PsiClassType)type).rawType().getCanonicalText()); } - final NullableNotNullManager manager = NullableNotNullManager.getInstance(elt.getProject()); - return LocalQuickFix.from(new AddAnnotationModCommandAction(manager.getDefaultNullable(), method, - ArrayUtilRt.toStringArray(manager.getNotNulls()))); + return LocalQuickFix.from(AddAnnotationModCommandAction.createAddNullableFix(method)); } @Override diff --git a/java/java-psi-api/src/com/intellij/codeInsight/AnnotationUtil.java b/java/java-psi-api/src/com/intellij/codeInsight/AnnotationUtil.java index 83463bbdae35..df85d25aabf3 100644 --- a/java/java-psi-api/src/com/intellij/codeInsight/AnnotationUtil.java +++ b/java/java-psi-api/src/com/intellij/codeInsight/AnnotationUtil.java @@ -382,10 +382,6 @@ public class AnnotationUtil { 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) && diff --git a/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java b/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java index c1c286891640..b7b75a1f60b9 100644 --- a/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java +++ b/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java @@ -57,12 +57,45 @@ public abstract class NullableNotNullManager { public abstract void setNullables(String @NotNull ... annotations); + /** + * @param nullability wanted nullability + * @param context PSI context + * @return the best suitable annotation to insert in a specified context + */ + public abstract @NotNull String getDefaultAnnotation(@NotNull Nullability nullability, @NotNull PsiElement context); + + /** + * @return default nullable annotation, + * used for external and inferred annotations, or as a fallback when no other annotation is available + * @see #getDefaultAnnotation(Nullability, PsiElement) + */ public abstract @NotNull String getDefaultNullable(); + /** + * Sets the default nullable annotation, used for external and inferred annotations, + * or as a fallback when no other annotation is available. + * + * @param defaultNullable new default nullable annotation + * @see #getDefaultNullable() + */ public abstract void setDefaultNullable(@NotNull String defaultNullable); + /** + * @return default not-null annotation, + * used for external and inferred annotations, or as a fallback when no other annotation is available + * @see #getDefaultAnnotation(Nullability, PsiElement) + */ public abstract @NotNull String getDefaultNotNull(); + /** + * Sets the default not-null annotation, used for external and inferred annotations, + * or as a fallback when no other annotation is available. + * + * @param defaultNotNull new default nullable annotation + * @see #getDefaultNotNull() + */ + public abstract void setDefaultNotNull(@NotNull String defaultNotNull); + public void copyNotNullAnnotation(@NotNull PsiModifierListOwner original, @NotNull PsiModifierListOwner generated) { NullabilityAnnotationInfo info = findOwnNullabilityInfo(original); if (info == null || info.getNullability() != Nullability.NOT_NULL) return; @@ -110,8 +143,6 @@ public abstract class NullableNotNullManager { return null; } - public abstract void setDefaultNotNull(@NotNull String defaultNotNull); - /** * Returns own nullability annotation info for given element. Returned annotation is not inherited and * not container annotation for class/package. Still it could be inferred or external. diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/annotateMethod/afterOverriddenWithInferred.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/annotateMethod/afterOverriddenWithInferred.java index 99869df0bb52..6bd3f49d8599 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/annotateMethod/afterOverriddenWithInferred.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/annotateMethod/afterOverriddenWithInferred.java @@ -1,4 +1,4 @@ -// "Annotate overriding methods as '@NotNull'" "true" +// "Add missing nullability annotation to overriding methods" "true" import org.jetbrains.annotations.NotNull; diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/annotateMethod/beforeOverriddenWithInferred.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/annotateMethod/beforeOverriddenWithInferred.java index 15d60a531b0b..0b9d188a1004 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/annotateMethod/beforeOverriddenWithInferred.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/annotateMethod/beforeOverriddenWithInferred.java @@ -1,4 +1,4 @@ -// "Annotate overriding methods as '@NotNull'" "true" +// "Add missing nullability annotation to overriding methods" "true" import org.jetbrains.annotations.NotNull; diff --git a/java/java-tests/testData/refactoring/extractMethod/InterfaceMethodVisibility_after.java b/java/java-tests/testData/refactoring/extractMethod/InterfaceMethodVisibility_after.java index 96473d5f021e..86902293cfe5 100644 --- a/java/java-tests/testData/refactoring/extractMethod/InterfaceMethodVisibility_after.java +++ b/java/java-tests/testData/refactoring/extractMethod/InterfaceMethodVisibility_after.java @@ -1,9 +1,12 @@ +import org.jetbrains.annotations.NotNull; + interface TweetParser { static String getTweetMessageFrom(String fullTweet) { String fieldName = "\"text\":\""; return newMethod(fullTweet, fieldName); } + @NotNull static String newMethod(String fullTweet, String fieldName) { int indexOfField = fullTweet.indexOf(fieldName) + fieldName.length(); int indexOfEndOfField = fullTweet.indexOf("\"", indexOfField); diff --git a/java/java-tests/testData/refactoring/extractMethodNew/InterfaceMethodVisibility_after.java b/java/java-tests/testData/refactoring/extractMethodNew/InterfaceMethodVisibility_after.java index 96473d5f021e..86902293cfe5 100644 --- a/java/java-tests/testData/refactoring/extractMethodNew/InterfaceMethodVisibility_after.java +++ b/java/java-tests/testData/refactoring/extractMethodNew/InterfaceMethodVisibility_after.java @@ -1,9 +1,12 @@ +import org.jetbrains.annotations.NotNull; + interface TweetParser { static String getTweetMessageFrom(String fullTweet) { String fieldName = "\"text\":\""; return newMethod(fullTweet, fieldName); } + @NotNull static String newMethod(String fullTweet, String fieldName) { int indexOfField = fullTweet.indexOf(fieldName) + fieldName.length(); int indexOfEndOfField = fullTweet.indexOf("\"", indexOfField); diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/quickfix/AnnotateMethodInGeneratedFilesTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/quickfix/AnnotateMethodInGeneratedFilesTest.java index 2069c3b69272..cced6c439b01 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/quickfix/AnnotateMethodInGeneratedFilesTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/quickfix/AnnotateMethodInGeneratedFilesTest.java @@ -38,7 +38,7 @@ public class AnnotateMethodInGeneratedFilesTest extends LightJavaCodeInsightFixt } public void testAnnotateOverriddenMethod() { - doTest("Annotate overriding methods"); + doTest("Add missing nullability annotation"); } public void testAnnotateOverriddenParameters() { diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/quickfix/AnnotateMethodTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/quickfix/AnnotateMethodTest.java index dc92c8bcc2e2..ded9534ece55 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/quickfix/AnnotateMethodTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/quickfix/AnnotateMethodTest.java @@ -7,10 +7,8 @@ import com.intellij.codeInsight.annoPackages.AnnotationPackageSupport; import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase; import com.intellij.codeInspection.LocalInspectionTool; import com.intellij.codeInspection.nullable.NullableStuffInspection; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.extensions.LoadingOrder; import com.intellij.pom.java.LanguageLevel; -import com.intellij.testFramework.ServiceContainerUtil; import org.jetbrains.annotations.NotNull; import java.util.List; @@ -25,7 +23,6 @@ public class AnnotateMethodTest extends LightQuickFixParameterizedTestCase { protected void setUp() throws Exception { super.setUp(); if (getTestName(false).contains("TypeUse")) { - NullableNotNullManager nnnManager = NullableNotNullManager.getInstance(getProject()); AnnotationPackageSupport mySupport = new AnnotationPackageSupport() { @Override public @NotNull List getNullabilityAnnotations(@NotNull Nullability nullability) { @@ -40,17 +37,10 @@ public class AnnotateMethodTest extends LightQuickFixParameterizedTestCase { public boolean isTypeUseAnnotationLocationRestricted() { return true; } - }; - ServiceContainerUtil.registerExtension(ApplicationManager.getApplication(), AnnotationPackageSupport.EP_NAME, mySupport, getTestRootDisposable()); - String prevNullable = nnnManager.getDefaultNullable(); - String prevNotNull = nnnManager.getDefaultNotNull(); - nnnManager.setDefaultNotNull("typeUse.NotNull"); - nnnManager.setDefaultNullable("typeUse.Nullable"); - Disposer.register(getTestRootDisposable(), () -> { - nnnManager.setDefaultNotNull(prevNotNull); - nnnManager.setDefaultNullable(prevNullable); - }); + }; + AnnotationPackageSupport.EP_NAME.getPoint().registerExtension(mySupport, LoadingOrder.FIRST, getTestRootDisposable()); } + NullableNotNullManager.getInstance(getProject()).setNotNulls(); // to normalize defaults } @Override diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/MakeInferredAnnotationExplicitTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/MakeInferredAnnotationExplicitTest.java index 758dde125a54..785ec7a55f4e 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/MakeInferredAnnotationExplicitTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/MakeInferredAnnotationExplicitTest.java @@ -1,6 +1,5 @@ package com.intellij.java.codeInsight; -import com.intellij.codeInsight.AnnotationUtil; import com.intellij.codeInsight.NullableNotNullManager; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase; @@ -50,7 +49,6 @@ public class MakeInferredAnnotationExplicitTest extends LightJavaCodeInsightFixt public void test_custom_notNull() { myFixture.addClass("package foo; public @interface MyNotNull {}"); NullableNotNullManager.getInstance(getProject()).setNotNulls("foo.MyNotNull"); - NullableNotNullManager.getInstance(getProject()).setDefaultNotNull("foo.MyNotNull"); myFixture.configureByText("a.java", """ class Foo { @@ -75,7 +73,6 @@ public class MakeInferredAnnotationExplicitTest extends LightJavaCodeInsightFixt public void test_type_use() { myFixture.addClass("package foo; import java.lang.annotation.*;@Target(ElementType.TYPE_USE)public @interface MyNotNull {}"); NullableNotNullManager.getInstance(getProject()).setNotNulls("foo.MyNotNull"); - NullableNotNullManager.getInstance(getProject()).setDefaultNotNull("foo.MyNotNull"); myFixture.configureByText("a.java", """ class Foo { static void foo(String[] array) { @@ -96,7 +93,6 @@ public class MakeInferredAnnotationExplicitTest extends LightJavaCodeInsightFixt public void test_type_use_qualified_type() { myFixture.addClass("package foo; import java.lang.annotation.*;@Target(ElementType.TYPE_USE)public @interface MyNotNull {}"); NullableNotNullManager.getInstance(getProject()).setNotNulls("foo.MyNotNull"); - NullableNotNullManager.getInstance(getProject()).setDefaultNotNull("foo.MyNotNull"); myFixture.configureByText("a.java", """ import org.jetbrains.annotations.Contract; import foo.MyNotNull; @@ -125,7 +121,6 @@ public class MakeInferredAnnotationExplicitTest extends LightJavaCodeInsightFixt protected void tearDown() throws Exception { try { NullableNotNullManager.getInstance(getProject()).setNotNulls(ArrayUtil.EMPTY_STRING_ARRAY); - NullableNotNullManager.getInstance(getProject()).setDefaultNotNull(AnnotationUtil.NOT_NULL); } catch (Throwable e) { addSuppressedException(e); diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/OverrideImplement15Test.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/OverrideImplement15Test.java index 2fb1da3f8069..0d07049ed52d 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/OverrideImplement15Test.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/OverrideImplement15Test.java @@ -64,24 +64,16 @@ public class OverrideImplement15Test extends LightJavaCodeInsightTestCase { private void doCustomNotNullAnnotations() { NullableNotNullManager nullableNotNullManager = NullableNotNullManager.getInstance(getProject()); - String defaultNotNull = nullableNotNullManager.getDefaultNotNull(); List notNulls = nullableNotNullManager.getNotNulls(); - String defaultNullable = nullableNotNullManager.getDefaultNullable(); List nullables = nullableNotNullManager.getNullables(); try { - nullableNotNullManager.setNotNulls(ArrayUtil.append(ArrayUtil.toStringArray(notNulls),"p.NN")); - nullableNotNullManager.setDefaultNotNull("p.NN"); - - nullableNotNullManager.setNullables(ArrayUtil.append(ArrayUtil.toStringArray(nullables),"p.N")); - nullableNotNullManager.setDefaultNullable("p.N"); + nullableNotNullManager.setNotNulls(ArrayUtil.prepend("p.NN",ArrayUtil.toStringArray(notNulls))); + nullableNotNullManager.setNullables(ArrayUtil.prepend("p.N",ArrayUtil.toStringArray(nullables))); doTest(true, true); } finally { - nullableNotNullManager.setDefaultNotNull(defaultNotNull); nullableNotNullManager.setNotNulls(ArrayUtil.toStringArray(notNulls)); - - nullableNotNullManager.setDefaultNullable(defaultNullable); nullableNotNullManager.setNullables(ArrayUtil.toStringArray(nullables)); } } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java index b022e0f4ab6d..036fab92629b 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java @@ -2,11 +2,9 @@ package com.intellij.java.codeInspection; import com.intellij.JavaTestUtil; -import com.intellij.codeInsight.AnnotationUtil; import com.intellij.codeInsight.NullableNotNullManager; import com.intellij.codeInspection.dataFlow.ConstantValueInspection; import com.intellij.codeInspection.dataFlow.DataFlowInspection; -import com.intellij.openapi.util.Disposer; import com.intellij.testFramework.LightProjectDescriptor; import org.jetbrains.annotations.NotNull; @@ -306,24 +304,10 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase { } public void testArrayTypeParameterInference() { setupTypeUseAnnotations("typeUse", myFixture); - NullableNotNullManager nnnManager = NullableNotNullManager.getInstance(getProject()); - nnnManager.setDefaultNotNull("typeUse.NotNull"); - nnnManager.setDefaultNullable("typeUse.Nullable"); - Disposer.register(getTestRootDisposable(), () -> { - nnnManager.setDefaultNotNull(AnnotationUtil.NOT_NULL); - nnnManager.setDefaultNullable(AnnotationUtil.NULLABLE); - }); doTest(); } public void testArrayTypeParameterInferenceAmbiguous() { setupAmbiguousAnnotations("ambiguous", myFixture); - NullableNotNullManager nnnManager = NullableNotNullManager.getInstance(getProject()); - nnnManager.setDefaultNotNull("ambiguous.NotNull"); - nnnManager.setDefaultNullable("ambiguous.Nullable"); - Disposer.register(getTestRootDisposable(), () -> { - nnnManager.setDefaultNotNull(AnnotationUtil.NOT_NULL); - nnnManager.setDefaultNullable(AnnotationUtil.NULLABLE); - }); doTest(); } public void testGuavaFunction() { diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java index 0c2c821c2e7d..054449429f88 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java @@ -384,15 +384,7 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest public void testQuickFixOnTypeArgument() { setupTypeUseAnnotations("typeUse", myFixture); - NullableNotNullManager manager = NullableNotNullManager.getInstance(getProject()); - String oldDefault = manager.getDefaultNotNull(); - try { - manager.setDefaultNotNull("typeUse.NotNull"); - doTestWithFix("Annotate as '@NotNull'"); - } - finally { - manager.setDefaultNotNull(oldDefault); - } + doTestWithFix("Annotate as '@NotNull'"); } public void testRemoveAnnotationWithImportQuickFix() { @@ -401,15 +393,7 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest public void testQuickFixOnTypeArgumentNullable() { setupTypeUseAnnotations("typeUse", myFixture); - NullableNotNullManager manager = NullableNotNullManager.getInstance(getProject()); - String oldDefault = manager.getDefaultNotNull(); - try { - manager.setDefaultNotNull("typeUse.NotNull"); - doTestWithFix("Annotate as '@NotNull'"); - } - finally { - manager.setDefaultNotNull(oldDefault); - } + doTestWithFix("Annotate as '@NotNull'"); } public void testCheckerDefaultTypeUseRecursiveGeneric() { @@ -463,10 +447,6 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest } public void testIncompatibleConstructors() { - final NullableNotNullManager nnnManager = NullableNotNullManager.getInstance(getProject()); - String nullable = nnnManager.getDefaultNullable(); - nnnManager.setDefaultNullable("org.jspecify.annotations.Nullable"); - Disposer.register(myFixture.getTestRootDisposable(), () -> nnnManager.setDefaultNullable(nullable)); addJSpecifyNullMarked(myFixture); setupTypeUseAnnotations("org.jspecify.annotations", myFixture); doTest(); diff --git a/java/openapi/resources/messages/JavaBundle.properties b/java/openapi/resources/messages/JavaBundle.properties index c51b94bf4f89..b0a3bcc1d183 100644 --- a/java/openapi/resources/messages/JavaBundle.properties +++ b/java/openapi/resources/messages/JavaBundle.properties @@ -1082,7 +1082,9 @@ node.call.hierarchy.unknown.jsp=unknown jsp node.hierarchy.java.lang.object=All classes are derived from java.lang.Object notification.navigation.to.overriding.classes=Navigation to overriding classes is not possible during index update notification.navigation.to.overriding.methods=Navigation to overriding methods is not possible during index update +annotations.panel.title={0} annotations: nullable.notnull.annotations.panel.title={0} annotations: +nullable.notnull.annotations.panel.description=The first from this list available in the project will be suggested as a quick-fix nullable.notnull.annotations.runtime.instrumentation.tooltip=Add runtime assertions for notnull-annotated methods and parameters nullable.notnull.configuration.dialog.title=Nullable/NotNull Configuration nullable.notnull.annotation.used.label=Annotation used for code generation: diff --git a/java/openapi/src/com/intellij/codeInsight/AnnotationsPanel.java b/java/openapi/src/com/intellij/codeInsight/AnnotationsPanel.java index 5eeeb11bf542..3e2bf7a8c8dd 100644 --- a/java/openapi/src/com/intellij/codeInsight/AnnotationsPanel.java +++ b/java/openapi/src/com/intellij/codeInsight/AnnotationsPanel.java @@ -1,14 +1,10 @@ // Copyright 2000-2024 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.core.JavaPsiBundle; import com.intellij.ide.util.ClassFilter; import com.intellij.ide.util.TreeClassChooser; import com.intellij.ide.util.TreeClassChooserFactory; import com.intellij.java.JavaBundle; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.application.ReadAction; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.ComboBox; import com.intellij.openapi.util.NlsSafe; @@ -18,23 +14,20 @@ import com.intellij.ui.*; import com.intellij.ui.dsl.builder.DslComponentProperty; import com.intellij.ui.dsl.builder.VerticalComponentGap; import com.intellij.ui.table.JBTable; -import com.intellij.util.concurrency.AppExecutorUtil; import com.intellij.util.ui.JBDimension; import com.intellij.util.ui.UI; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import javax.swing.*; -import javax.swing.table.*; +import javax.swing.table.DefaultTableColumnModel; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.TableColumn; +import javax.swing.table.TableRowSorter; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.stream.IntStream; -import java.util.stream.Stream; public class AnnotationsPanel { private final Project myProject; @@ -47,32 +40,16 @@ public class AnnotationsPanel { public AnnotationsPanel(Project project, @NonNls String name, - String defaultAnnotation, List annotations, - List defaultAnnotations, - Set checkedAnnotations, - boolean showInstrumentationOptions, - boolean showDefaultActions) { - this(project, new SimpleAnnotationPanelModel(name, defaultAnnotation, annotations, defaultAnnotations, checkedAnnotations), showInstrumentationOptions, showDefaultActions); - } - - public AnnotationsPanel(Project project, - @NotNull AnnotationPanelModel model, - boolean showInstrumentationOptions, - boolean showDefaultActions) { + List defaultAnnotations) { myProject = project; - myDefaultAnnotations = new HashSet<>(model.getDefaultAnnotations()); + myDefaultAnnotations = new HashSet<>(defaultAnnotations); - List annotations = model.getAnnotations(); myCombo = new ComboBox<>(annotations.stream().sorted().toArray(String[]::new)); - String defaultAnnotation = model.getDefaultAnnotation(); - if (!annotations.contains(defaultAnnotation)) { - addAnnotationToCombo(defaultAnnotation); + if (!annotations.contains("")) { + addAnnotationToCombo(""); } - if (model.hasAdvancedAnnotations()) { - loadAdvancedAnnotations(model); - } - myCombo.setSelectedItem(defaultAnnotation); + myCombo.setSelectedItem(""); myTableModel = new DefaultTableModel() { @Override @@ -80,9 +57,9 @@ public class AnnotationsPanel { return column == 1; } }; - myTableModel.setColumnCount(showInstrumentationOptions ? 2 : 1); + myTableModel.setColumnCount(1); for (String annotation : annotations) { - addRow(annotation, model.getCheckedAnnotations().contains(annotation)); + addRow(annotation); } DefaultTableColumnModel columnModel = new DefaultTableColumnModel(); @@ -107,41 +84,11 @@ public class AnnotationsPanel { mySorter = new TableRowSorter<>(myTableModel); mySorter.setSortKeys(List.of(new RowSorter.SortKey(0, SortOrder.ASCENDING))); myTable.setRowSorter(mySorter); - if (!showInstrumentationOptions) myTable.setTableHeader(null); + myTable.setTableHeader(null); mySorter.sort(); - if (showInstrumentationOptions) { - columnModel.getColumn(0).setHeaderValue(JavaPsiBundle.message("node.annotation.tooltip")); - - TableColumn checkColumn = new TableColumn(1, 100, new BooleanTableCellRenderer(), new BooleanTableCellEditor()); - columnModel.addColumn(checkColumn); - checkColumn.setHeaderValue(" Instrument "); - - TableCellRenderer defaultRenderer = myTable.getTableHeader().getDefaultRenderer(); - - TableCellRenderer headerRenderer = new TableCellRenderer() { - @Override - public Component getTableCellRendererComponent(JTable table, - Object value, - boolean isSelected, - boolean hasFocus, - int row, - int column) { - Component component = defaultRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - if (component instanceof JComponent) { - ((JComponent)component) - .setToolTipText(column == 1 ? JavaBundle.message("nullable.notnull.annotations.runtime.instrumentation.tooltip") : null); - } - return component; - } - }; - myTable.getTableHeader().setDefaultRenderer(headerRenderer); - checkColumn.setHeaderRenderer(headerRenderer); - checkColumn.sizeWidthToFit(); - } - final ToolbarDecorator toolbarDecorator = ToolbarDecorator.createDecorator(myTable).disableUpDownActions() - .setAddAction(b -> chooseAnnotation(model.getName())) + .setAddAction(b -> chooseAnnotation(name)) .setRemoveAction(new AnActionButtonRunnable() { @Override public void run(AnActionButton anActionButton) { @@ -167,7 +114,7 @@ public class AnnotationsPanel { final var tablePanel = UI.PanelFactory .panel(toolbarDecorator.createPanel()) - .withLabel(JavaBundle.message("nullable.notnull.annotations.panel.title", model.getName())) + .withLabel(JavaBundle.message("annotations.panel.title", name)) .moveLabelOnTop() .resizeY(true) .createPanel(); @@ -178,15 +125,6 @@ public class AnnotationsPanel { GridBagConstraints constraints = new GridBagConstraints(); constraints.anchor = GridBagConstraints.WEST; constraints.weightx = 1; - if (showDefaultActions) { - myComponent.add(new JLabel(JavaBundle.message("nullable.notnull.annotation.used.label")), constraints); - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.insets.bottom = 3; - constraints.gridy = 1; - myComponent.add(myCombo, constraints); - constraints.insets.bottom = 0; - constraints.gridy = 2; - } constraints.fill = GridBagConstraints.BOTH; constraints.weighty = 1; myComponent.add(tablePanel, constraints); @@ -196,42 +134,8 @@ public class AnnotationsPanel { return true; } - private void loadAdvancedAnnotations(@NotNull AnnotationPanelModel model) { - // No project-specific annotations are possible for default project - if (myProject.isDefault()) return; - String loading = JavaBundle.message("loading.additional.annotations"); - myCombo.addItem(loading); - DumbService.getInstance(myProject).runWhenSmart(() -> { - ReadAction.nonBlocking(model::getAdvancedAnnotations) - .finishOnUiThread(ModalityState.any(), advancedAnnotations -> { - myCombo.removeItem(loading); - int count = myCombo.getItemCount(); - Object selectedItem = myCombo.getSelectedItem(); - List newItems = Stream.concat( - IntStream.range(0, count).mapToObj(myCombo::getItemAt), - advancedAnnotations.stream()).sorted().distinct().toList(); - myCombo.removeAllItems(); - newItems.forEach(myCombo::addItem); - myCombo.setSelectedItem(selectedItem); - }).submit(AppExecutorUtil.getAppExecutorService()); - }); - myCombo.addActionListener(new ActionListener() { - Object previous = myCombo.getSelectedItem(); - - @Override - public void actionPerformed(ActionEvent e) { - Object item = myCombo.getSelectedItem(); - if (item == loading) { - myCombo.setSelectedItem(previous); - } else { - previous = item; - } - } - }); - } - - private void addRow(String annotation, boolean checked) { - myTableModel.addRow(new Object[]{annotation, checked}); + private void addRow(String annotation) { + myTableModel.addRow(new Object[]{annotation}); } private Integer selectAnnotation(String annotation) { @@ -264,7 +168,7 @@ public class AnnotationsPanel { } final String qualifiedName = selected.getQualifiedName(); if (selectAnnotation(qualifiedName) == null) { - addRow(qualifiedName, false); + addRow(qualifiedName); addAnnotationToCombo(qualifiedName); mySorter.sort(); Object added = selectAnnotation(qualifiedName); @@ -285,10 +189,6 @@ public class AnnotationsPanel { return myComponent; } - String getDefaultAnnotation() { - return myCombo.getItem(); - } - public String[] getAnnotations() { int size = myTable.getRowCount(); String[] result = new String[size]; @@ -298,16 +198,6 @@ public class AnnotationsPanel { return result; } - List getCheckedAnnotations() { - List result = new ArrayList<>(); - for (int i = 0; i < myTable.getRowCount(); i++) { - if (Boolean.TRUE.equals(myTable.getValueAt(i, 1))) { - result.add((String)myTable.getValueAt(i, 0)); - } - } - return result; - } - /** Reset table to contain only annotations from the list. */ public void resetAnnotations(List annotations) { final Set set = new HashSet<>(annotations); @@ -321,57 +211,7 @@ public class AnnotationsPanel { } } for (String annotation : set) { - addRow(annotation, false); - } - } - - private static class SimpleAnnotationPanelModel implements AnnotationPanelModel { - private final @NonNls String myName; - private final String myDefaultAnnotation; - private final List myAnnotations; - private final List myDefaultAnnotations; - private final Set myCheckedAnnotations; - - private SimpleAnnotationPanelModel(@NonNls String name, - String defaultAnnotation, - List annotations, - List defaultAnnotations, - Set checkedAnnotations) { - myName = name; - myDefaultAnnotation = defaultAnnotation; - myAnnotations = annotations; - myDefaultAnnotations = defaultAnnotations; - myCheckedAnnotations = checkedAnnotations; - } - - @Override - public @NotNull String getName() { - return myName; - } - - @Override - public @NotNull String getDefaultAnnotation() { - return myDefaultAnnotation; - } - - @Override - public @NotNull List getAnnotations() { - return myAnnotations; - } - - @Override - public @NotNull List getAdvancedAnnotations() { - return myAnnotations; - } - - @Override - public @NotNull List getDefaultAnnotations() { - return myDefaultAnnotations; - } - - @Override - public @NotNull Set getCheckedAnnotations() { - return myCheckedAnnotations; + addRow(annotation); } } } \ No newline at end of file diff --git a/java/openapi/src/com/intellij/codeInsight/NullableAnnotationsPanel.java b/java/openapi/src/com/intellij/codeInsight/NullableAnnotationsPanel.java new file mode 100644 index 000000000000..75a7d67baa0a --- /dev/null +++ b/java/openapi/src/com/intellij/codeInsight/NullableAnnotationsPanel.java @@ -0,0 +1,319 @@ +// Copyright 2000-2024 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.core.JavaPsiBundle; +import com.intellij.ide.util.ClassFilter; +import com.intellij.ide.util.TreeClassChooser; +import com.intellij.ide.util.TreeClassChooserFactory; +import com.intellij.java.JavaBundle; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.application.ReadAction; +import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ComboBox; +import com.intellij.openapi.util.NlsSafe; +import com.intellij.psi.PsiClass; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.ui.*; +import com.intellij.ui.dsl.builder.DslComponentProperty; +import com.intellij.ui.dsl.builder.VerticalComponentGap; +import com.intellij.ui.table.JBTable; +import com.intellij.util.concurrency.AppExecutorUtil; +import com.intellij.util.ui.JBDimension; +import com.intellij.util.ui.UI; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import javax.swing.table.DefaultTableColumnModel; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +public class NullableAnnotationsPanel { + private final Project myProject; + private final Set myDefaultAnnotations; + private final JBTable myTable; + private final JPanel myComponent; + private final ComboBox myCombo; + protected final DefaultTableModel myTableModel; + + public NullableAnnotationsPanel(Project project, + @NotNull AnnotationPanelModel model, + boolean showInstrumentationOptions) { + myProject = project; + myDefaultAnnotations = new HashSet<>(model.getDefaultAnnotations()); + + List annotations = model.getAnnotations(); + myCombo = new ComboBox<>(annotations.stream().sorted().toArray(String[]::new)); + String defaultAnnotation = model.getDefaultAnnotation(); + if (!annotations.contains(defaultAnnotation)) { + addAnnotationToCombo(defaultAnnotation); + } + if (model.hasAdvancedAnnotations()) { + loadAdvancedAnnotations(model); + } + myCombo.setSelectedItem(defaultAnnotation); + + myTableModel = new DefaultTableModel() { + @Override + public boolean isCellEditable(int row, int column) { + return column == 1; + } + }; + myTableModel.setColumnCount(showInstrumentationOptions ? 2 : 1); + for (String annotation : annotations) { + addRow(annotation, model.getCheckedAnnotations().contains(annotation)); + } + + DefaultTableColumnModel columnModel = new DefaultTableColumnModel(); + columnModel.addColumn(new TableColumn(0, 100, new ColoredTableCellRenderer() { + @Override + public void acquireState(JTable table, boolean isSelected, boolean hasFocus, int row, int column) { + super.acquireState(table, isSelected, false, row, column); + } + + @Override + protected void customizeCellRenderer(@NotNull JTable table, + Object value, + boolean selected, + boolean hasFocus, + int row, + int column) { + append((String)value, SimpleTextAttributes.REGULAR_ATTRIBUTES); + } + }, null)); + + myTable = new JBTable(myTableModel, columnModel); + if (!showInstrumentationOptions) myTable.setTableHeader(null); + + if (showInstrumentationOptions) { + columnModel.getColumn(0).setHeaderValue(JavaPsiBundle.message("node.annotation.tooltip")); + + TableColumn checkColumn = new TableColumn(1, 100, new BooleanTableCellRenderer(), new BooleanTableCellEditor()); + columnModel.addColumn(checkColumn); + checkColumn.setHeaderValue(" Instrument "); + + TableCellRenderer headerRenderer = createHeaderRenderer(); + myTable.getTableHeader().setDefaultRenderer(headerRenderer); + checkColumn.setHeaderRenderer(headerRenderer); + checkColumn.sizeWidthToFit(); + } + + final ToolbarDecorator toolbarDecorator = ToolbarDecorator.createDecorator(myTable) + .setMoveUpAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton button) { + int selectedRow = myTable.getSelectedRow(); + if (selectedRow < 1) return; + @SuppressWarnings("unchecked") + List vector = myTableModel.getDataVector().get(selectedRow); + myTableModel.removeRow(selectedRow); + myTableModel.insertRow(selectedRow - 1, vector.toArray()); + myTable.setRowSelectionInterval(selectedRow - 1, selectedRow - 1); + } + }) + .setMoveDownAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton button) { + int selectedRow = myTable.getSelectedRow(); + if (selectedRow < 0 || selectedRow >= myTableModel.getRowCount() - 1) return; + @SuppressWarnings("unchecked") + List vector = myTableModel.getDataVector().get(selectedRow); + myTableModel.removeRow(selectedRow); + myTableModel.insertRow(selectedRow + 1, vector.toArray()); + myTable.setRowSelectionInterval(selectedRow + 1, selectedRow + 1); + } + }) + .setAddAction(b -> chooseAnnotation(model.getName())) + .setRemoveAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton anActionButton) { + String selectedValue = getSelectedAnnotation(); + if (selectedValue == null) return; + myCombo.removeItem(selectedValue); + + int rowIndex = -1; + for (int i = 0; i < myTableModel.getDataVector().size(); i++) { + if (myTableModel.getDataVector().get(i).contains(selectedValue)) { + rowIndex = i; + break; + } + } + if (rowIndex != -1) myTableModel.removeRow(rowIndex); + } + }) + .setRemoveActionUpdater(e -> !myDefaultAnnotations.contains(getSelectedAnnotation())); + + myTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + myTable.setRowSelectionAllowed(true); + myTable.setShowGrid(false); + + final var tablePanel = UI.PanelFactory + .panel(toolbarDecorator.createPanel()) + .withLabel(JavaBundle.message("nullable.notnull.annotations.panel.title", model.getName())) + .withComment(JavaBundle.message("nullable.notnull.annotations.panel.description")) + .moveLabelOnTop() + .resizeY(true) + .createPanel(); + tablePanel.setPreferredSize(new JBDimension(tablePanel.getPreferredSize().width, 200)); + + myComponent = new JPanel(new GridBagLayout()); + myComponent.putClientProperty(DslComponentProperty.VERTICAL_COMPONENT_GAP, VerticalComponentGap.BOTH); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.anchor = GridBagConstraints.WEST; + constraints.weightx = 1; + myComponent.add(new JLabel(JavaBundle.message("nullable.notnull.annotation.used.label")), constraints); + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.insets.bottom = 3; + constraints.gridy = 1; + myComponent.add(myCombo, constraints); + constraints.insets.bottom = 0; + constraints.gridy = 2; + constraints.fill = GridBagConstraints.BOTH; + constraints.weighty = 1; + myComponent.add(tablePanel, constraints); + } + + private @NotNull TableCellRenderer createHeaderRenderer() { + TableCellRenderer defaultRenderer = myTable.getTableHeader().getDefaultRenderer(); + + TableCellRenderer headerRenderer = new TableCellRenderer() { + @Override + public Component getTableCellRendererComponent(JTable table, + Object value, + boolean isSelected, + boolean hasFocus, + int row, + int column) { + Component component = defaultRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + if (component instanceof JComponent) { + ((JComponent)component) + .setToolTipText(column == 1 ? JavaBundle.message("nullable.notnull.annotations.runtime.instrumentation.tooltip") : null); + } + return component; + } + }; + return headerRenderer; + } + + private void loadAdvancedAnnotations(@NotNull AnnotationPanelModel model) { + // No project-specific annotations are possible for default project + if (myProject.isDefault()) return; + String loading = JavaBundle.message("loading.additional.annotations"); + myCombo.addItem(loading); + DumbService.getInstance(myProject).runWhenSmart(() -> { + ReadAction.nonBlocking(model::getAdvancedAnnotations) + .finishOnUiThread(ModalityState.any(), advancedAnnotations -> { + myCombo.removeItem(loading); + int count = myCombo.getItemCount(); + Object selectedItem = myCombo.getSelectedItem(); + List newItems = Stream.concat( + IntStream.range(0, count).mapToObj(myCombo::getItemAt), + advancedAnnotations.stream()).distinct().toList(); + myCombo.removeAllItems(); + newItems.forEach(myCombo::addItem); + myCombo.setSelectedItem(selectedItem); + }).submit(AppExecutorUtil.getAppExecutorService()); + }); + myCombo.addActionListener(new ActionListener() { + Object previous = myCombo.getSelectedItem(); + + @Override + public void actionPerformed(ActionEvent e) { + Object item = myCombo.getSelectedItem(); + if (item == loading) { + myCombo.setSelectedItem(previous); + } else { + previous = item; + } + } + }); + } + + private void addRow(String annotation, boolean checked) { + myTableModel.addRow(new Object[]{annotation, checked}); + } + + private Integer selectAnnotation(String annotation) { + for (int i = 0; i < myTable.getRowCount(); i++) { + if (annotation.equals(myTable.getValueAt(i, 0))) { + myTable.setRowSelectionInterval(i, i); + return i; + } + } + return null; + } + + private @NlsSafe String getSelectedAnnotation() { + int selectedRow = myTable.getSelectedRow(); + return selectedRow < 0 ? null : (String)myTable.getValueAt(selectedRow, 0); + } + + private void chooseAnnotation(@NlsSafe String title) { + final TreeClassChooser chooser = TreeClassChooserFactory.getInstance(myProject) + .createNoInnerClassesScopeChooser(JavaBundle.message("dialog.title.choose.annotation", title), GlobalSearchScope.allScope(myProject), new ClassFilter() { + @Override + public boolean isAccepted(PsiClass aClass) { + if (!aClass.isAnnotationType()) return false; + return true; + } + }, null); + chooser.showDialog(); + final PsiClass selected = chooser.getSelected(); + if (selected == null) { + return; + } + final String qualifiedName = selected.getQualifiedName(); + if (selectAnnotation(qualifiedName) == null) { + addRow(qualifiedName, false); + addAnnotationToCombo(qualifiedName); + Object added = selectAnnotation(qualifiedName); + assert added != null; + myTable.scrollRectToVisible(myTable.getCellRect((int)added, 0, true)); + } + } + + private void addAnnotationToCombo(@NlsSafe String annotation) { + int insertAt = 0; + for (; insertAt < myCombo.getItemCount(); insertAt += 1) { + if (myCombo.getItemAt(insertAt).compareTo(annotation) >= 0) break; + } + myCombo.insertItemAt(annotation, insertAt); + } + + public JComponent getComponent() { + return myComponent; + } + + String getDefaultAnnotation() { + return myCombo.getItem(); + } + + public String[] getAnnotations() { + int size = myTable.getRowCount(); + String[] result = new String[size]; + for (int i = 0; i < size; i++) { + result[i] = (String)myTable.getValueAt(i, 0); + } + return result; + } + + List getCheckedAnnotations() { + List result = new ArrayList<>(); + for (int i = 0; i < myTable.getRowCount(); i++) { + if (Boolean.TRUE.equals(myTable.getValueAt(i, 1))) { + result.add((String)myTable.getValueAt(i, 0)); + } + } + return result; + } +} \ No newline at end of file diff --git a/java/openapi/src/com/intellij/codeInsight/NullableNotNullDialog.java b/java/openapi/src/com/intellij/codeInsight/NullableNotNullDialog.java index 2b33e20ec342..d9aae14f6c27 100644 --- a/java/openapi/src/com/intellij/codeInsight/NullableNotNullDialog.java +++ b/java/openapi/src/com/intellij/codeInsight/NullableNotNullDialog.java @@ -14,8 +14,8 @@ import java.awt.event.ActionListener; public class NullableNotNullDialog extends DialogWrapper { private final Project myProject; - private final AnnotationsPanel myNullablePanel; - private final AnnotationsPanel myNotNullPanel; + private final NullableAnnotationsPanel myNullablePanel; + private final NullableAnnotationsPanel myNotNullPanel; private final boolean myShowInstrumentationOptions; public static final @NlsSafe String NULLABLE = "Nullable"; public static final @NlsSafe String NOT_NULL = "NotNull"; @@ -30,12 +30,12 @@ public class NullableNotNullDialog extends DialogWrapper { myShowInstrumentationOptions = showInstrumentationOptions; NullableNotNullManager manager = NullableNotNullManager.getInstance(myProject); - myNullablePanel = new AnnotationsPanel(project, + myNullablePanel = new NullableAnnotationsPanel(project, new NullabilityAnnotationPanelModel.NullableModel(manager), - false, true); - myNotNullPanel = new AnnotationsPanel(project, + false); + myNotNullPanel = new NullableAnnotationsPanel(project, new NullabilityAnnotationPanelModel.NotNullModel(manager), - showInstrumentationOptions, true); + showInstrumentationOptions); init(); setTitle(JavaBundle.message("nullable.notnull.configuration.dialog.title")); diff --git a/plugins/coverage/src/com/intellij/coverage/JavaCoverageOptions.kt b/plugins/coverage/src/com/intellij/coverage/JavaCoverageOptions.kt index 41d1a6209cc6..92218736321e 100644 --- a/plugins/coverage/src/com/intellij/coverage/JavaCoverageOptions.kt +++ b/plugins/coverage/src/com/intellij/coverage/JavaCoverageOptions.kt @@ -50,9 +50,8 @@ private class JavaCoverageOptions(private val project: Project) : UiDslUnnamedCo } row { val excludeAnnotationsPanel = object : AnnotationsPanel( - project, "Exclude", "", - coverageOptionsProvider.excludeAnnotationPatterns, - JavaCoverageOptionsProvider.defaultExcludeAnnotationPatterns, emptySet(), false, false + project, "Exclude", coverageOptionsProvider.excludeAnnotationPatterns, + JavaCoverageOptionsProvider.defaultExcludeAnnotationPatterns ) { override fun isAnnotationAccepted(annotation: PsiClass): Boolean { return annotation.containingClass == null