diff --git a/java/java-impl/src/com/intellij/codeInsight/generation/OverrideImplementsAnnotationsHandler.java b/java/java-impl/src/com/intellij/codeInsight/generation/OverrideImplementsAnnotationsHandler.java index ebbbc7157ace..6e71d3bff133 100644 --- a/java/java-impl/src/com/intellij/codeInsight/generation/OverrideImplementsAnnotationsHandler.java +++ b/java/java-impl/src/com/intellij/codeInsight/generation/OverrideImplementsAnnotationsHandler.java @@ -11,9 +11,14 @@ import com.intellij.psi.JavaPsiFacade; import com.intellij.psi.PsiAnnotation; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiJavaCodeReferenceElement; +import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiModifierList; import com.intellij.psi.PsiModifierListOwner; import com.intellij.psi.PsiNameValuePair; +import com.intellij.psi.PsiPackage; +import com.intellij.psi.PsiTypeElement; +import com.intellij.psi.PsiVariable; import com.intellij.psi.codeStyle.JavaCodeStyleSettings; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.util.ArrayUtil; @@ -59,7 +64,7 @@ public interface OverrideImplementsAnnotationsHandler extends OverrideImplements if (moduleScope != null && facade.findClass(annotation, moduleScope) == null) continue; int flags = CHECK_EXTERNAL | CHECK_TYPE; - if (AnnotationUtil.isAnnotated(source, annotation, flags) && !AnnotationUtil.isAnnotated(target, annotation, flags)) { + if (AnnotationUtil.isAnnotated(source, annotation, flags) && !hasAnnotation(target, annotation)) { each.transferToTarget(annotation, source, target); } } @@ -70,6 +75,78 @@ public interface OverrideImplementsAnnotationsHandler extends OverrideImplements } } + private static boolean hasAnnotation(@NotNull PsiModifierListOwner target, @NotNull String annotation) { + if (AnnotationUtil.isAnnotated(target, annotation, CHECK_EXTERNAL | CHECK_TYPE)) return true; + // Handle case when the annotation present in the source modifier list + // has been 'hidden' after the package qualifier when generating target. + // For example for: + // source = @NotNull Map.Entry param + // target = java.util.@org.jetbrains.annotations.NotNull Map.Entry + // AnnotationUtil.hasAnnotation(.., ..@NotNull) returns true for source but false for the target + PsiTypeElement typeElement = getTypeElement(target); + if (typeElement == null) return false; + return hasAnnotationOnFirstNonPackageSubElement(typeElement, annotation); + } + + private static @Nullable PsiTypeElement getTypeElement(@NotNull PsiModifierListOwner target) { + if (target instanceof PsiMethod) return ((PsiMethod)target).getReturnTypeElement(); + if (target instanceof PsiVariable) return ((PsiVariable)target).getTypeElement(); + return null; + } + + /** + * Checks if a specified annotation is present at the innermost component reference element of the given type element + * after skipping the initial package qualifier. + * + *
Examples: + *
{@code
+ * Returns true for annotation "org.jetbrains.annotations.NotNull"
+ * when typeElement represents: java.lang.@NotNull String
+ *
+ * Returns true for annotation "org.jetbrains.annotations.NotNull"
+ * when typeElement represents: @NotNull String
+ *
+ * Returns false for annotation "org.jetbrains.annotations.NotNull"
+ * when typeElement represents: @NotNull java.lang.String
+ *
+ * Returns true for annotation "org.jetbrains.annotations.Nullable"
+ * when typeElement represents: java.util.@Nullable List
+ * }
+ *
+ * @param typeElement The {@link PsiTypeElement} to inspect. This represents a type in Java source code,
+ * such as a method return type or a parameter type.
+ * @param annotation The fully qualified name of the annotation to search for.
+ * @return true if the annotation is found after the package qualifier in the type reference;
+ * false otherwise, or if the type element has no innermost component reference.
+ */
+ private static boolean hasAnnotationOnFirstNonPackageSubElement(PsiTypeElement typeElement, @NotNull String annotation) {
+ PsiJavaCodeReferenceElement reference = typeElement.getInnermostComponentReferenceElement();
+ if (reference == null) return false;
+ var leftMostResolvedQualifier = getLeftmostNonPackageElement(reference);
+ if (leftMostResolvedQualifier == null) return false;
+ return hasDirectAnnotation(leftMostResolvedQualifier, annotation);
+ }
+
+ private static @Nullable PsiElement getLeftmostNonPackageElement(@NotNull PsiJavaCodeReferenceElement reference) {
+ PsiJavaCodeReferenceElement qualifier =
+ reference.getQualifier() instanceof PsiJavaCodeReferenceElement referenceElement ? referenceElement : null;
+ if (qualifier != null) {
+ PsiElement result = getLeftmostNonPackageElement(qualifier);
+ if (result != null) return result;
+ }
+ PsiElement resolved = reference.resolve();
+ return resolved == null || resolved instanceof PsiPackage ? null : reference;
+ }
+
+ private static boolean hasDirectAnnotation(@NotNull PsiElement element, @NotNull String annotation) {
+ for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) {
+ if (child instanceof PsiAnnotation psiAnnotation && psiAnnotation.hasQualifiedName(annotation)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
default void transferToTarget(String annotation, PsiModifierListOwner source, PsiModifierListOwner target) {
PsiModifierList modifierList = target.getModifierList();
assert modifierList != null : target;
diff --git a/java/java-tests/testData/codeInsight/overrideImplement/nullability/afterNestedTypeParameter.java b/java/java-tests/testData/codeInsight/overrideImplement/nullability/afterNestedTypeParameter.java
new file mode 100644
index 000000000000..c5df1d2f0825
--- /dev/null
+++ b/java/java-tests/testData/codeInsight/overrideImplement/nullability/afterNestedTypeParameter.java
@@ -0,0 +1,21 @@
+package mypackage;
+
+import org.jetbrains.annotations.NotNull;
+
+class Outer {
+ static class Middle {
+ static class Nested {
+ }
+ }
+}
+
+interface Parent {
+ void test(@NotNull Outer.Middle.Nested nested);
+}
+
+class Child implements Parent {
+ @Override
+ public void test(@NotNull Outer.Middle.Nested nested) {
+
+ }
+}
diff --git a/java/java-tests/testData/codeInsight/overrideImplement/nullability/beforeNestedTypeParameter.java b/java/java-tests/testData/codeInsight/overrideImplement/nullability/beforeNestedTypeParameter.java
new file mode 100644
index 000000000000..c8f685d4bf51
--- /dev/null
+++ b/java/java-tests/testData/codeInsight/overrideImplement/nullability/beforeNestedTypeParameter.java
@@ -0,0 +1,18 @@
+package mypackage;
+
+import org.jetbrains.annotations.NotNull;
+
+class Outer {
+ static class Middle {
+ static class Nested {
+ }
+ }
+}
+
+interface Parent {
+ void test(@NotNull Outer.Middle.Nested nested);
+}
+
+class Child implements Parent {
+