diff --git a/rewrite-java-test/build.gradle.kts b/rewrite-java-test/build.gradle.kts index c349744c5f0..9ec9a8271fb 100644 --- a/rewrite-java-test/build.gradle.kts +++ b/rewrite-java-test/build.gradle.kts @@ -9,10 +9,20 @@ dependencies { testImplementation("io.github.classgraph:classgraph:latest.release") testImplementation("org.junit-pioneer:junit-pioneer:2.0.0") + testRuntimeOnly(project(":rewrite-java-17")) testRuntimeOnly("junit:junit:4.13.2") { because("Used for RemoveUnneededAssertionTest") } + testRuntimeOnly("com.google.code.findbugs:jsr305:3.0.2") { + because("Used for StandardizeNullabilityAnnotationsTest, UseJavaxNullabilityAnnotations and UseOpenRewriteNullabilityAnnotations") + } + testRuntimeOnly("org.springframework:spring-core:6.0.7") { + because("Used for StandardizeNullabilityAnnotationsTest and UseSpringNullabilityAnnotations") + } + testRuntimeOnly("jakarta.annotation:jakarta.annotation-api:2.1.1") { + because("Used for UseJakartaNullabilityAnnotations") + } testRuntimeOnly("org.apache.hbase:hbase-shaded-client:2.4.11") testRuntimeOnly("com.google.guava:guava:latest.release") testRuntimeOnly("org.mapstruct:mapstruct:latest.release") diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/nullability/StandardizeNullabilityAnnotationsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/nullability/StandardizeNullabilityAnnotationsTest.java new file mode 100644 index 00000000000..f817558714e --- /dev/null +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/nullability/StandardizeNullabilityAnnotationsTest.java @@ -0,0 +1,516 @@ +/* + * Copyright 2023 the original author or authors. + *
+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *
+ * https://www.apache.org/licenses/LICENSE-2.0 + *
+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.nullability; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.openrewrite.internal.lang.NonNull; +import org.openrewrite.internal.lang.NonNullApi; +import org.openrewrite.internal.lang.NonNullFields; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import java.util.List; + +import static org.openrewrite.java.Assertions.java; + +class StandardizeNullabilityAnnotationsTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.parser(JavaParser.fromJavaVersion().classpath("rewrite-core", "jsr305", "spring-core")); + } + + @Test + void removesImportIfPossible() { + rewriteRun(spec -> spec.recipe(new StandardizeNullabilityAnnotations(List.of(Nullable.class.getName(), NonNull.class.getName()))), java(""" + package org.openrewrite.internal.lang; + + import javax.annotation.Nonnull; + + class Test { + @Nonnull + String variable = ""; + } + """, + + """ + package org.openrewrite.internal.lang; + + class Test { + @NonNull + String variable = ""; + } + """)); + } + + @Test + void addsImportIfNecessary() { + rewriteRun(spec -> spec.recipe(new StandardizeNullabilityAnnotations(List.of("javax.annotation.Nullable", "javax.annotation.Nonnull"))), java(""" + package org.openrewrite.internal.lang; + + class Test { + @NonNull + String variable = ""; + } + """, + + """ + package org.openrewrite.internal.lang; + + import javax.annotation.Nonnull; + + class Test { + @Nonnull + String variable = ""; + } + """)); + } + + @Test + void doesNotAddImportIfUnnecessary() { + rewriteRun(spec -> spec.recipe(new StandardizeNullabilityAnnotations(List.of(Nullable.class.getName(), NonNull.class.getName()))), java(""" + package org.openrewrite.internal.lang; + + import javax.annotation.Nonnull; + + class Test { + @Nonnull + String variable = ""; + } + """, + + """ + package org.openrewrite.internal.lang; + + class Test { + @NonNull + String variable = ""; + } + """)); + } + + @Test + void unchangedWhenNoNullabilityAnnotationWasUsed() { + rewriteRun(spec -> spec.recipe(new StandardizeNullabilityAnnotations(List.of(Nullable.class.getName(), NonNull.class.getName()))), java(""" + package org.openrewrite.java; + + class Test { + String variable = ""; + } + """)); + } + + @Test + void unchangedWhenOnlyTheConfiguredNullabilityAnnotationsWereUsed() { + rewriteRun(spec -> spec.recipe(new StandardizeNullabilityAnnotations(List.of(Nullable.class.getName(), NonNull.class.getName()))), java(""" + package org.openrewrite.java; + + import org.openrewrite.internal.lang.NonNull; + import org.openrewrite.internal.lang.Nullable; + + class Test { + @NonNull + String nonNullVariable = ""; + + @Nullable + String nullableVariable; + } + """)); + } + + @Test + void replacesAllAnnotationsIfDifferentNonConfiguredAnnotationWereUsed() { + rewriteRun(spec -> spec.recipe(new StandardizeNullabilityAnnotations(List.of(Nullable.class.getName(), NonNull.class.getName()))), java(""" + package org.openrewrite.java; + + import javax.annotation.Nonnull; + import org.springframework.lang.Nullable; + + class Test { + @Nonnull + String nonNullVariable = ""; + + @Nullable + String nullableVariable; + } + """, """ + package org.openrewrite.java; + + import org.openrewrite.internal.lang.NonNull; + import org.openrewrite.internal.lang.Nullable; + + class Test { + @NonNull + String nonNullVariable = ""; + + @Nullable + String nullableVariable; + } + """)); + } + + @Test + void shouldReplaceAnnotationsOnPackage() { + rewriteRun(spec -> spec.recipe(new StandardizeNullabilityAnnotations(List.of(NonNullApi.class.getName()))), java(""" + @NonNullApi + package org.openrewrite.java; + + import org.springframework.lang.NonNullApi; + """, """ + @NonNullApi + package org.openrewrite.java; + + import org.openrewrite.internal.lang.NonNullApi; + """)); + } + + @Test + void shouldReplaceLeadingAnnotationsOnClass() { + rewriteRun(spec -> spec.recipe(new StandardizeNullabilityAnnotations(List.of(Nullable.class.getName(), NonNull.class.getName()))), java(""" + package org.openrewrite.java; + + import javax.annotation.Nonnull; + + @Nonnull + public class Test { + } + """, """ + package org.openrewrite.java; + + import org.openrewrite.internal.lang.NonNull; + + @NonNull + public class Test { + } + """)); + } + + @Test + @Disabled("annotation on kind not yet supported") + void shouldReplaceAnnotationsOnClass() { + rewriteRun(spec -> spec.recipe(new StandardizeNullabilityAnnotations(List.of(Nullable.class.getName(), NonNull.class.getName()))), java(""" + package org.openrewrite.java; + + import javax.annotation.Nonnull; + + public @Nonnull class Test { + } + """, """ + package org.openrewrite.java; + + import org.openrewrite.internal.lang.NonNull; + + public @NonNull class Test { + } + """)); + } + + @Test + @Disabled("annotations on modifiers not yet support") + void shouldReplaceAnnotationsBetweenModifiersOnClass() { + rewriteRun(spec -> spec.recipe(new StandardizeNullabilityAnnotations(List.of(Nullable.class.getName(), NonNull.class.getName()))), java(""" + package org.openrewrite.java; + + import javax.annotation.Nonnull; + + public @Nonnull final class Test { + } + """, """ + package org.openrewrite.java; + + import org.openrewrite.internal.lang.NonNull; + + public @NonNull final class Test { + } + """)); + } + + @Test + void shouldReplaceAnnotationsOnMethod() { + rewriteRun(spec -> spec.recipe(new StandardizeNullabilityAnnotations(List.of(Nullable.class.getName(), NonNull.class.getName()))), java(""" + package org.openrewrite.java; + + import javax.annotation.Nonnull; + + class Test { + @Nonnull + public String getString() { + return ""; + } + } + """, """ + package org.openrewrite.java; + + import org.openrewrite.internal.lang.NonNull; + + class Test { + @NonNull + public String getString() { + return ""; + } + } + """)); + } + + @Test + @Disabled("annotations on modifiers not yet support") + void shouldReplaceAnnotationsBetweenModifiersOnMethod() { + rewriteRun(spec -> spec.recipe(new StandardizeNullabilityAnnotations(List.of(Nullable.class.getName(), NonNull.class.getName()))), java(""" + package org.openrewrite.java; + + import javax.annotation.Nonnull; + + class Test { + public @Nonnull static String getString() { + return ""; + } + } + """, """ + package org.openrewrite.java; + + import org.openrewrite.internal.lang.NonNull; + + class Test { + public @NonNull static String getString() { + return ""; + } + } + """)); + } + + @Test + void shouldReplaceAnnotationsOnReturnType() { + rewriteRun(spec -> spec.recipe(new StandardizeNullabilityAnnotations(List.of(Nullable.class.getName(), NonNull.class.getName()))), java(""" + package org.openrewrite.java; + + import javax.annotation.Nonnull; + + class Test { + public @Nonnull String getString() { + return ""; + } + } + """, """ + package org.openrewrite.java; + + import org.openrewrite.internal.lang.NonNull; + + class Test { + public @NonNull String getString() { + return ""; + } + } + """)); + } + + @Test + void shouldReplaceAnnotationsOnParameter() { + rewriteRun(spec -> spec.recipe(new StandardizeNullabilityAnnotations(List.of(Nullable.class.getName(), NonNull.class.getName()))), java(""" + package org.openrewrite.java; + + import javax.annotation.Nonnull; + + class Test { + public String getString(@Nonnull String parameter) { + return parameter; + } + } + """, """ + package org.openrewrite.java; + + import org.openrewrite.internal.lang.NonNull; + + class Test { + public String getString(@NonNull String parameter) { + return parameter; + } + } + """)); + } + + @Test + void shouldReplaceAnnotationsOnField() { + rewriteRun(spec -> spec.recipe(new StandardizeNullabilityAnnotations(List.of(Nullable.class.getName(), NonNull.class.getName()))), java(""" + package org.openrewrite.java; + + import javax.annotation.Nonnull; + + class Test { + @Nonnull + String nonNullVariable = ""; + } + """, """ + package org.openrewrite.java; + + import org.openrewrite.internal.lang.NonNull; + + class Test { + @NonNull + String nonNullVariable = ""; + } + """)); + } + + @Test + @Disabled("annotations on modifiers not yet support") + void shouldReplaceAnnotationsBetweenModifiersOnField() { + rewriteRun(spec -> spec.recipe(new StandardizeNullabilityAnnotations(List.of(Nullable.class.getName(), NonNull.class.getName()))), java(""" + package org.openrewrite.java; + + import javax.annotation.Nonnull; + + class Test { + public @Nonnull final String nonNullVariable = ""; + } + """, """ + package org.openrewrite.java; + + import org.openrewrite.internal.lang.NonNull; + + class Test { + public @NonNull final String nonNullVariable = ""; + } + """)); + } + + @Test + void shouldReplaceAnnotationsOnLocalField() { + rewriteRun(spec -> spec.recipe(new StandardizeNullabilityAnnotations(List.of(Nullable.class.getName(), NonNull.class.getName()))), java(""" + package org.openrewrite.java; + + import javax.annotation.Nonnull; + + class Test { + public String getString() { + @Nonnull + String parameter = ""; + return parameter; + } + } + """, """ + package org.openrewrite.java; + + import org.openrewrite.internal.lang.NonNull; + + class Test { + public String getString() { + @NonNull + String parameter = ""; + return parameter; + } + } + """)); + } + + @Test + @Disabled("annotations on modifiers not yet support") + void shouldReplaceAnnotationsBeforeModifierOnLocalField() { + rewriteRun(spec -> spec.recipe(new StandardizeNullabilityAnnotations(List.of(Nullable.class.getName(), NonNull.class.getName()))), java(""" + package org.openrewrite.java; + + import javax.annotation.Nonnull; + + class Test { + public String getString() { + @Nonnull final String parameter = ""; + return parameter; + } + } + """, """ + package org.openrewrite.java; + + import org.openrewrite.internal.lang.NonNull; + + class Test { + public String getString() { + @NonNull final String parameter = ""; + return parameter; + } + } + """)); + } + + @Test + void shouldReplaceTwoAnnotationsWithOne() { + rewriteRun(spec -> spec.recipe(new StandardizeNullabilityAnnotations(List.of("javax.annotation.Nonnull"))), java(""" + package org.openrewrite.java; + + import org.openrewrite.internal.lang.NonNullApi; + import org.openrewrite.internal.lang.NonNullFields; + + @NonNullApi + @NonNullFields + class Test { + } + """, """ + package org.openrewrite.java; + + import javax.annotation.Nonnull; + + @Nonnull + class Test { + } + """)); + } + + @Test + void shouldReplaceOneAnnotationsWithTwo() { + rewriteRun(spec -> spec.recipe(new StandardizeNullabilityAnnotations(List.of(NonNullApi.class.getName(), NonNullFields.class.getName()))), java(""" + package org.openrewrite.java; + + import javax.annotation.Nonnull; + + @Nonnull + class Test { + } + """, """ + package org.openrewrite.java; + + import org.openrewrite.internal.lang.NonNullApi; + import org.openrewrite.internal.lang.NonNullFields; + + @NonNullApi + @NonNullFields + class Test { + } + """)); + } + + @Test + void shouldReplaceUsingFqnWhenCollidingImportExistsToNotBreakCode() { + rewriteRun(spec -> spec.recipe(new StandardizeNullabilityAnnotations(List.of(Nullable.class.getName(), NonNull.class.getName()))), java(""" + package org.openrewrite.java; + + import javax.annotation.Nonnull; + import org.springframework.lang.NonNull; + + @Nonnull + class Test { + } + """, """ + package org.openrewrite.java; + + import org.springframework.lang.NonNull; + + @org.openrewrite.internal.lang.NonNull + class Test { + } + """)); + } +} diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/nullability/UseJakartaNullabilityAnnotationsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/nullability/UseJakartaNullabilityAnnotationsTest.java new file mode 100644 index 00000000000..15231eabea0 --- /dev/null +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/nullability/UseJakartaNullabilityAnnotationsTest.java @@ -0,0 +1,82 @@ +/* + * Copyright 2023 the original author or authors. + *
+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *
+ * https://www.apache.org/licenses/LICENSE-2.0 + *
+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.nullability; + +import org.junit.jupiter.api.Test; +import org.openrewrite.config.Environment; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class UseJakartaNullabilityAnnotationsTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.parser(JavaParser.fromJavaVersion().classpath("jsr305", "jakarta.annotation-api")) + .recipe(Environment.builder().scanRuntimeClasspath("org.openrewrite.java").build().activateRecipes("org.openrewrite.java.nullability.UseJakartaNullabilityAnnotations")); + } + + @Test + void replaceJavaxWithJakartaAnnotationsNonNull() { + rewriteRun(java(""" + package org.openrewrite.java; + + import javax.annotation.Nonnull; + + class Test { + @Nonnull + String variable = ""; + } + """, + """ + package org.openrewrite.java; + + import jakarta.annotation.Nonnull; + + class Test { + @Nonnull + String variable = ""; + } + """)); + } + + @Test + void replaceJavaxWithJakartaAnnotationsNullable() { + rewriteRun(java(""" + package org.openrewrite.java; + + import javax.annotation.Nullable; + + class Test { + @Nullable + String variable; + } + """, + + """ + package org.openrewrite.java; + + import jakarta.annotation.Nullable; + + class Test { + @Nullable + String variable; + } + """)); + } +} diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/nullability/UseJavaxNullabilityAnnotationsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/nullability/UseJavaxNullabilityAnnotationsTest.java new file mode 100644 index 00000000000..7bb2fa5644f --- /dev/null +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/nullability/UseJavaxNullabilityAnnotationsTest.java @@ -0,0 +1,83 @@ +/* + * Copyright 2023 the original author or authors. + *
+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *
+ * https://www.apache.org/licenses/LICENSE-2.0 + *
+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.nullability; + +import org.junit.jupiter.api.Test; +import org.openrewrite.config.Environment; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class UseJavaxNullabilityAnnotationsTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.parser(JavaParser.fromJavaVersion().classpath("rewrite-core", "jsr305")) + .recipe(Environment.builder().scanRuntimeClasspath("org.openrewrite.java").build().activateRecipes("org.openrewrite.java.nullability.UseJavaxNullabilityAnnotations")); + } + + @Test + void replaceOpenRewriteWithJavaxAnnotationsNonNull() { + rewriteRun(java(""" + package org.openrewrite.java; + + import org.openrewrite.internal.lang.NonNull; + + class Test { + @NonNull + String variable = ""; + } + """, + + """ + package org.openrewrite.java; + + import javax.annotation.Nonnull; + + class Test { + @Nonnull + String variable = ""; + } + """)); + } + + @Test + void replaceOpenRewriteWithJavaxAnnotationsNullable() { + rewriteRun(java(""" + package org.openrewrite.java; + + import org.openrewrite.internal.lang.Nullable; + + class Test { + @Nullable + String variable; + } + """, + + """ + package org.openrewrite.java; + + import javax.annotation.Nullable; + + class Test { + @Nullable + String variable; + } + """)); + } +} diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/nullability/UseOpenRewriteNullabilityAnnotationsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/nullability/UseOpenRewriteNullabilityAnnotationsTest.java new file mode 100644 index 00000000000..17a247b84d6 --- /dev/null +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/nullability/UseOpenRewriteNullabilityAnnotationsTest.java @@ -0,0 +1,118 @@ +/* + * Copyright 2023 the original author or authors. + *
+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *
+ * https://www.apache.org/licenses/LICENSE-2.0 + *
+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.nullability; + +import org.junit.jupiter.api.Test; +import org.openrewrite.config.Environment; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class UseOpenRewriteNullabilityAnnotationsTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.parser(JavaParser.fromJavaVersion().classpath("rewrite-core", "jsr305")).recipe(Environment.builder().scanRuntimeClasspath("org.openrewrite.java").build().activateRecipes("org.openrewrite.java.nullability.UseOpenRewriteNullabilityAnnotations")); + } + + @Test + void replaceJavaxWithOpenRewriteAnnotationsNonNull() { + rewriteRun(java(""" + package org.openrewrite.java; + + import javax.annotation.Nonnull; + + class Test { + @Nonnull + String variable = ""; + } + """, """ + package org.openrewrite.java; + + import org.openrewrite.internal.lang.NonNull; + + class Test { + @NonNull + String variable = ""; + } + """)); + } + + @Test + void replaceJavaxWithOpenRewriteAnnotationsNullable() { + rewriteRun(java(""" + package org.openrewrite.java; + + import javax.annotation.Nullable; + + class Test { + @Nullable + String variable; + } + """, + + """ + package org.openrewrite.java; + + import org.openrewrite.internal.lang.Nullable; + + class Test { + @Nullable + String variable; + } + """)); + } + + @Test + void replaceJavaxWithOpenRewriteAnnotationNonNullClass() { + rewriteRun(java(""" + package org.openrewrite.java; + + import javax.annotation.Nonnull; + + @Nonnull + class Test { + } + """, """ + package org.openrewrite.java; + + import org.openrewrite.internal.lang.NonNull; + + @NonNull + class Test { + } + """)); + } + + @Test + void replaceJavaxWithOpenRewriteAnnotationsNonNullPackage() { + rewriteRun(java(""" + @Nonnull + package org.openrewrite.java; + + import javax.annotation.Nonnull; + """, """ + @NonNullApi + @NonNullFields + package org.openrewrite.java; + + import org.openrewrite.internal.lang.NonNullApi; + import org.openrewrite.internal.lang.NonNullFields; + """)); + } +} diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/nullability/UseSpringNullabilityAnnotationsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/nullability/UseSpringNullabilityAnnotationsTest.java new file mode 100644 index 00000000000..3274fabbbfc --- /dev/null +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/nullability/UseSpringNullabilityAnnotationsTest.java @@ -0,0 +1,113 @@ +/* + * Copyright 2023 the original author or authors. + *
+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *
+ * https://www.apache.org/licenses/LICENSE-2.0 + *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.openrewrite.java.nullability;
+
+import org.junit.jupiter.api.Test;
+import org.openrewrite.config.Environment;
+import org.openrewrite.java.JavaParser;
+import org.openrewrite.test.RecipeSpec;
+import org.openrewrite.test.RewriteTest;
+
+import static org.openrewrite.java.Assertions.java;
+
+class UseSpringNullabilityAnnotationsTest implements RewriteTest {
+
+ @Override
+ public void defaults(RecipeSpec spec) {
+ spec.parser(JavaParser.fromJavaVersion().classpath("jsr305", "spring-core"))
+ .recipe(Environment.builder().scanRuntimeClasspath("org.openrewrite.java").build().activateRecipes("org.openrewrite.java.nullability.UseSpringNullabilityAnnotations"));
+ }
+
+ @Test
+ void replaceJavaxWithSpringAnnotationsNonNull() {
+ rewriteRun(java("""
+ package org.openrewrite.java;
+
+ import javax.annotation.Nonnull;
+
+ class Test {
+ @Nonnull
+ String variable = "";
+ }
+ """,
+ """
+ package org.openrewrite.java;
+
+ import org.springframework.lang.NonNull;
+
+ class Test {
+ @NonNull
+ String variable = "";
+ }
+ """));
+ }
+
+ @Test
+ void replaceJavaxWithSpringAnnotationsNullable() {
+ rewriteRun(java("""
+ package org.openrewrite.java;
+
+ import javax.annotation.Nullable;
+
+ class Test {
+ @Nullable
+ String variable;
+ }
+ """,
+
+ """
+ package org.openrewrite.java;
+
+ import org.springframework.lang.Nullable;
+
+ class Test {
+ @Nullable
+ String variable;
+ }
+ """));
+ }
+
+ @Test
+ void shouldNotReplaceJavaxWithSpringAnnotationsNonNullClass() {
+ rewriteRun(java("""
+ package org.openrewrite.java;
+
+ import javax.annotation.Nonnull;
+
+ @Nonnull
+ class Test {
+ }
+ """
+ ));
+ }
+
+ @Test
+ void replaceJavaxWithSpringAnnotationsNonNullPackage() {
+ rewriteRun(java("""
+ @Nonnull
+ package org.openrewrite.java;
+
+ import javax.annotation.Nonnull;
+ """, """
+ @NonNullApi
+ @NonNullFields
+ package org.openrewrite.java;
+
+ import org.springframework.lang.NonNullApi;
+ import org.springframework.lang.NonNullFields;
+ """));
+ }
+}
diff --git a/rewrite-java/src/main/java/org/openrewrite/java/format/WrappingAndBracesVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/format/WrappingAndBracesVisitor.java
index c6f9d3bacdb..1a946c81cb9 100644
--- a/rewrite-java/src/main/java/org/openrewrite/java/format/WrappingAndBracesVisitor.java
+++ b/rewrite-java/src/main/java/org/openrewrite/java/format/WrappingAndBracesVisitor.java
@@ -148,6 +148,12 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, P
return j;
}
+ @Override
+ public J.Package visitPackage(J.Package pkg, P p) {
+ J.Package j = super.visitPackage(pkg, p);
+ return j.withAnnotations(withNewlines(j.getAnnotations()));
+ }
+
private List
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.openrewrite.java.nullability;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.EnumSet;
+import java.util.Set;
+
+@Getter
+@AllArgsConstructor
+public enum KnownNullabilityAnnotations implements NullabilityAnnotation {
+
+ ANDROID_SUPPORT_NULLABLE("android.support.annotation.Nullable", Nullability.NULLABLE, EnumSet.of(Target.METHOD, Target.PARAMETER, Target.FIELD, Target.LOCAL_FIELD, Target.PACKAGE), EnumSet.allOf(Scope.class)),
+ ANDROID_SUPPORT_NON_NULL("android.support.annotation.NonNull", Nullability.NONNULL, EnumSet.of(Target.METHOD, Target.PARAMETER, Target.FIELD, Target.LOCAL_FIELD, Target.PACKAGE), EnumSet.allOf(Scope.class)),
+
+ ANDROIDX_SUPPORT_NULLABLE("androidx.annotation.Nullable", Nullability.NULLABLE, EnumSet.of(Target.METHOD, Target.PARAMETER, Target.FIELD, Target.LOCAL_FIELD, Target.PACKAGE), EnumSet.allOf(Scope.class)),
+ ANDROIDX_SUPPORT_NON_NULL("androidx.annotation.NonNull", Nullability.NONNULL, EnumSet.of(Target.METHOD, Target.PARAMETER, Target.FIELD, Target.LOCAL_FIELD, Target.PACKAGE), EnumSet.allOf(Scope.class)),
+
+ CHECKER_FRAMEWORK_NULLABLE("org.checkerframework.checker.nullness.qual.Nullable", Nullability.NULLABLE, EnumSet.of(Target.TYPE_USE), EnumSet.allOf(Scope.class)),
+ CHECKER_FRAMEWORK_NON_NULL("org.checkerframework.checker.nullness.qual.NonNull", Nullability.NONNULL, EnumSet.of(Target.TYPE_USE), EnumSet.allOf(Scope.class)),
+
+ ECLIPSE_JDT_NULLABLE("org.eclipse.jdt.annotation.Nullable", Nullability.NULLABLE, EnumSet.of(Target.TYPE_USE), EnumSet.allOf(Scope.class)),
+ ECLIPSE_JDT_NON_NULL("org.eclipse.jdt.annotation.NonNull", Nullability.NONNULL, EnumSet.of(Target.TYPE_USE), EnumSet.allOf(Scope.class)),
+ /*
+ * The following annotation is commented out, because scopes it applies to are dynamically configurable with parameters.
+ * To support this, we would need to convert the static scopes attribute to a provider function which takes the parsed annotation to work with its argument.
+ * ECLIPSE_JDT_NON_NULL_BY_DEFAULT("org.eclipse.jdt.annotation.NonNullByDefault", Nullability.NONNULL, EnumSet.allOf(Target.class), annotation -> ...),
+ */
+
+ FINDBUGS_CHECK_FOR_NULL("edu.umd.cs.findbugs.annotations.CheckForNull", Nullability.NULLABLE, EnumSet.of(Target.FIELD, Target.METHOD, Target.PARAMETER, Target.LOCAL_FIELD), EnumSet.allOf(Scope.class)),
+ FINDBUGS_NULLABLE("edu.umd.cs.findbugs.annotations.Nullable", Nullability.NULLABLE, EnumSet.of(Target.FIELD, Target.METHOD, Target.PARAMETER, Target.LOCAL_FIELD), EnumSet.allOf(Scope.class)),
+ FINDBUGS_POSSIBLY_NULL("edu.umd.cs.findbugs.annotations.PossiblyNull", Nullability.NULLABLE, EnumSet.of(Target.FIELD, Target.METHOD, Target.PARAMETER, Target.LOCAL_FIELD), EnumSet.allOf(Scope.class)),
+ FINDBUGS_NON_NULL("edu.umd.cs.findbugs.annotations.NonNull", Nullability.NONNULL, EnumSet.of(Target.FIELD, Target.METHOD, Target.PARAMETER, Target.LOCAL_FIELD), EnumSet.allOf(Scope.class)),
+ FINDBUGS_UNKNOWN_NULLNESS("edu.umd.cs.findbugs.annotations.UnknownNullness", Nullability.UNKNOWN, EnumSet.of(Target.FIELD, Target.METHOD, Target.PARAMETER, Target.LOCAL_FIELD), EnumSet.allOf(Scope.class)),
+
+ JAKARTA_NULLABLE("jakarta.annotation.Nullable", Nullability.NULLABLE, EnumSet.allOf(Target.class), EnumSet.allOf(Scope.class)),
+ JAKARTA_NON_NULL("jakarta.annotation.Nonnull", Nullability.NONNULL, EnumSet.allOf(Target.class), EnumSet.allOf(Scope.class)),
+
+ JAVAX_NULLABLE("javax.annotation.Nullable", Nullability.NULLABLE, EnumSet.allOf(Target.class), EnumSet.allOf(Scope.class)),
+ JAVAX_NON_NULL("javax.annotation.Nonnull", Nullability.NONNULL, EnumSet.allOf(Target.class), EnumSet.allOf(Scope.class)),
+
+ JETBRAINS_NULLABLE("org.jetbrains.annotations.Nullable", Nullability.NULLABLE, EnumSet.of(Target.METHOD, Target.FIELD, Target.PARAMETER, Target.LOCAL_FIELD, Target.TYPE_USE), EnumSet.allOf(Scope.class)),
+ JETBRAINS_NON_NULL("org.jetbrains.annotations.NotNull", Nullability.NONNULL, EnumSet.of(Target.METHOD, Target.FIELD, Target.PARAMETER, Target.LOCAL_FIELD, Target.TYPE_USE), EnumSet.allOf(Scope.class)),
+ JETBRAINS_UNKNOWN_NULLABILITY("org.jetbrains.annotations.UnknownNullability", Nullability.UNKNOWN, EnumSet.of(Target.TYPE_USE), EnumSet.allOf(Scope.class)),
+
+ JML_SPECS_NULLABLE("org.jmlspecs.annotation.Nullable", Nullability.NULLABLE, EnumSet.of(Target.TYPE_USE, Target.FIELD, Target.METHOD, Target.LOCAL_FIELD, Target.PARAMETER), EnumSet.allOf(Scope.class)),
+ JML_SPECS_NULLABLE_BY_DEFAULT("org.jmlspecs.annotation.NullableByDefault", Nullability.NULLABLE, EnumSet.allOf(Target.class), EnumSet.allOf(Scope.class)),
+ JML_SPECS_NON_NULL("org.jmlspecs.annotation.NonNull", Nullability.NONNULL, EnumSet.of(Target.TYPE_USE, Target.FIELD, Target.METHOD, Target.LOCAL_FIELD, Target.PARAMETER), EnumSet.allOf(Scope.class)),
+ JML_SPECS_NON_NULL_BY_DEFAULT("org.jmlspecs.annotation.NonNullByDefault", Nullability.NONNULL, EnumSet.allOf(Target.class), EnumSet.allOf(Scope.class)),
+
+ JSPECIFY_NULLABLE("org.jspecify.annotations.Nullable", Nullability.NULLABLE, EnumSet.of(Target.TYPE_USE), EnumSet.allOf(Scope.class)),
+ JSPECIFY_NON_NULL("org.jspecify.annotations.NonNull", Nullability.NONNULL, EnumSet.of(Target.TYPE_USE), EnumSet.allOf(Scope.class)),
+ JSPECIFY_NULL_MARKED("org.jspecify.annotations.NullMarked", Nullability.NONNULL, EnumSet.of(Target.MODULE, Target.PACKAGE, Target.TYPE, Target.METHOD), EnumSet.allOf(Scope.class)),
+ JSPECIFY_NULL_UNMARKED("org.jspecify.annotations.NullUnmarked", Nullability.UNKNOWN, EnumSet.of(Target.PACKAGE, Target.TYPE, Target.METHOD), EnumSet.allOf(Scope.class)),
+
+ LOMBOK_NON_NULL("lombok.NonNull", Nullability.NONNULL, EnumSet.of(Target.FIELD, Target.METHOD, Target.PARAMETER, Target.LOCAL_FIELD, Target.TYPE_USE), EnumSet.allOf(Scope.class)),
+
+ NETBEANS_CHECK_FOR_NULL("org.netbeans.api.annotations.common.CheckForNull", Nullability.NULLABLE, EnumSet.of(Target.METHOD), EnumSet.of(Scope.METHOD)),
+ NETBEANS_NULL_ALLOWED("org.netbeans.api.annotations.common.NullAllowed", Nullability.NULLABLE, EnumSet.of(Target.FIELD, Target.PARAMETER, Target.LOCAL_FIELD), EnumSet.of(Scope.FIELD, Scope.PARAMETER)),
+ NETBEANS_NON_NULL("org.netbeans.api.annotations.common.NonNull", Nullability.NONNULL, EnumSet.of(Target.FIELD, Target.METHOD, Target.PARAMETER, Target.LOCAL_FIELD), EnumSet.allOf(Scope.class)),
+ NETBEANS_NULL_UNKNOWN("org.netbeans.api.annotations.common.NullUnknown", Nullability.UNKNOWN, EnumSet.of(Target.FIELD, Target.METHOD, Target.PARAMETER, Target.LOCAL_FIELD), EnumSet.allOf(Scope.class)),
+
+ OPEN_REWRITE_NULLABLE("org.openrewrite.internal.lang.Nullable", Nullability.NULLABLE, EnumSet.of(Target.METHOD, Target.PARAMETER, Target.FIELD, Target.TYPE, Target.TYPE_USE), EnumSet.allOf(Scope.class)),
+ OPEN_REWRITE_NULL_FIELDS("org.openrewrite.internal.lang.NullFields", Nullability.NULLABLE, EnumSet.of(Target.PACKAGE, Target.TYPE), EnumSet.of(Scope.FIELD)),
+ OPEN_REWRITE_NON_NULL("org.openrewrite.internal.lang.NonNull", Nullability.NONNULL, EnumSet.of(Target.METHOD, Target.PARAMETER, Target.FIELD, Target.TYPE, Target.TYPE_USE), EnumSet.allOf(Scope.class)),
+ OPEN_REWRITE_NON_NULL_FIELDS("org.openrewrite.internal.lang.NonNullFields", Nullability.NONNULL, EnumSet.of(Target.PACKAGE, Target.TYPE), EnumSet.of(Scope.FIELD)),
+ OPEN_REWRITE_NON_NULL_API("org.openrewrite.internal.lang.NonNullApi", Nullability.NONNULL, EnumSet.of(Target.PACKAGE, Target.TYPE), EnumSet.of(Scope.METHOD, Scope.PARAMETER)),
+
+ SPRING_NULLABLE("org.springframework.lang.Nullable", Nullability.NULLABLE, EnumSet.of(Target.METHOD, Target.PARAMETER, Target.FIELD), EnumSet.allOf(Scope.class)),
+ SPRING_NON_NULL("org.springframework.lang.NonNull", Nullability.NONNULL, EnumSet.of(Target.METHOD, Target.PARAMETER, Target.FIELD), EnumSet.allOf(Scope.class)),
+ SPRING_NON_NULL_FIELDS("org.springframework.lang.NonNullFields", Nullability.NONNULL, EnumSet.of(Target.PACKAGE), EnumSet.of(Scope.FIELD)),
+ SPRING_NON_NULL_API("org.springframework.lang.NonNullApi", Nullability.NONNULL, EnumSet.of(Target.PACKAGE), EnumSet.of(Scope.METHOD, Scope.PARAMETER))
+ ;
+
+ private final String fqn;
+ private final Nullability nullability;
+ private final Set
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.openrewrite.java.nullability;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.lang.annotation.ElementType;
+import java.util.Set;
+
+public interface NullabilityAnnotation {
+
+ enum Nullability {
+ NULLABLE,
+ NONNULL,
+ UNKNOWN;
+ }
+
+ enum Target {
+ TYPE,
+ PACKAGE,
+ MODULE,
+ TYPE_USE,
+ METHOD,
+ PARAMETER,
+ FIELD,
+ LOCAL_FIELD;
+ }
+
+ enum Scope {
+ FIELD,
+ METHOD,
+ PARAMETER;
+ }
+
+ /**
+ * Fully qualified name of this annotation
+ */
+ String getFqn();
+
+ default String getSimpleName() {
+ return StringUtils.substringAfterLast(getFqn(), ".");
+ }
+
+ /**
+ * Whether this annotation indicates whether an element can be null or not.
+ */
+ Nullability getNullability();
+
+ /**
+ * Defines on what elements this nullability annotation ca be used.
+ * @see ElementType
+ */
+ Set
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.openrewrite.java.nullability;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import lombok.EqualsAndHashCode;
+import lombok.Value;
+import org.openrewrite.*;
+import org.openrewrite.internal.lang.Nullable;
+import org.openrewrite.java.cleanup.ShortenFullyQualifiedTypeReferences;
+
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+
+@Value
+@EqualsAndHashCode(callSuper = false)
+public class StandardizeNullabilityAnnotations extends Recipe {
+
+ @Option(displayName = "Nullability annotations to use",
+ description = "All other nullability annotations will be replaced with these.",
+ example = "javax.annotation.Nullable")
+ List
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.openrewrite.java.nullability;
+
+import lombok.AllArgsConstructor;
+import lombok.Value;
+import org.openrewrite.Cursor;
+import org.openrewrite.ExecutionContext;
+import org.openrewrite.internal.ListUtils;
+import org.openrewrite.java.JavaIsoVisitor;
+import org.openrewrite.java.JavaParser;
+import org.openrewrite.java.JavaTemplate;
+import org.openrewrite.java.tree.J;
+import org.openrewrite.java.tree.JavaType;
+import org.openrewrite.java.tree.Space;
+import org.openrewrite.java.tree.TypeUtils;
+
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+class StandardizeNullabilityAnnotationsVisitor extends JavaIsoVisitor
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@NonNullApi
+@NonNullFields
+package org.openrewrite.java.nullability;
+
+import org.openrewrite.internal.lang.NonNullApi;
+import org.openrewrite.internal.lang.NonNullFields;
\ No newline at end of file
diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/CoordinateBuilder.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/CoordinateBuilder.java
index b8edc7ae5fd..0d88648c9f2 100644
--- a/rewrite-java/src/main/java/org/openrewrite/java/tree/CoordinateBuilder.java
+++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/CoordinateBuilder.java
@@ -55,6 +55,7 @@ public JavaCoordinates before() {
return before(Space.Location.STATEMENT_PREFIX);
}
+ @Override
public JavaCoordinates replace() {
return replace(Space.Location.STATEMENT_PREFIX);
}
@@ -77,11 +78,23 @@ public JavaCoordinates replace() {
}
}
+ public static class AnnotatedType extends Expression {
+
+ AnnotatedType(J.AnnotatedType tree) {
+ super(tree);
+ }
+
+ public JavaCoordinates addAnnotation(Comparator
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+---
+type: specs.openrewrite.org/v1beta/recipe
+name: org.openrewrite.java.nullability.UseJavaxNullabilityAnnotations
+displayName: Use javax nullability annotations
+description: Replaces other known nullability annotations with Javax annotations where possible.
+tags:
+ - nullability
+recipeList:
+ - org.openrewrite.java.nullability.StandardizeNullabilityAnnotations:
+ nullabilityAnnotationsFqn:
+ - javax.annotation.Nullable
+ - javax.annotation.Nonnull
+---
+type: specs.openrewrite.org/v1beta/recipe
+name: org.openrewrite.java.nullability.UseJakartaNullabilityAnnotations
+displayName: Use Jakarta nullability annotations
+description: Replaces other known nullability annotations with Jakarta annotations where possible.
+tags:
+ - nullability
+recipeList:
+ - org.openrewrite.java.nullability.StandardizeNullabilityAnnotations:
+ nullabilityAnnotationsFqn:
+ - jakarta.annotation.Nullable
+ - jakarta.annotation.Nonnull
+---
+type: specs.openrewrite.org/v1beta/recipe
+name: org.openrewrite.java.nullability.UseSpringNullabilityAnnotations
+displayName: Use spring nullability annotations
+description: Replaces other known nullability annotations with Spring annotations where possible.
+tags:
+ - nullability
+recipeList:
+ - org.openrewrite.java.nullability.StandardizeNullabilityAnnotations:
+ nullabilityAnnotationsFqn:
+ - org.springframework.lang.Nullable
+ - org.springframework.lang.NonNull
+ - org.springframework.lang.NonNullFields
+ - org.springframework.lang.NonNullApi
+---
+type: specs.openrewrite.org/v1beta/recipe
+name: org.openrewrite.java.nullability.UseOpenRewriteNullabilityAnnotations
+displayName: Use OpenRewrite nullability annotations
+description: Replaces other known nullability annotations with OpenRewrite annotations where possible.
+tags:
+ - nullability
+recipeList:
+ - org.openrewrite.java.nullability.StandardizeNullabilityAnnotations:
+ nullabilityAnnotationsFqn:
+ - org.openrewrite.internal.lang.Nullable
+ - org.openrewrite.internal.lang.NullFields
+ - org.openrewrite.internal.lang.NonNull
+ - org.openrewrite.internal.lang.NonNullFields
+ - org.openrewrite.internal.lang.NonNullApi