diff --git a/build.gradle.kts b/build.gradle.kts index af572b74a..367aabb7f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -35,6 +35,8 @@ dependencies { runtimeOnly("org.openrewrite:rewrite-java-17") runtimeOnly("org.openrewrite:rewrite-java-21") + runtimeOnly("tech.picnic.error-prone-support:error-prone-contrib:latest.release:recipes") + testImplementation("org.junit.jupiter:junit-jupiter-api:latest.release") testImplementation("org.junit.jupiter:junit-jupiter-params:latest.release") testImplementation("org.junit-pioneer:junit-pioneer:2.0.0") diff --git a/src/main/java/org/openrewrite/java/migrate/guava/NoMapsAndSetsWithExpectedSize.java b/src/main/java/org/openrewrite/java/migrate/guava/NoMapsAndSetsWithExpectedSize.java new file mode 100644 index 000000000..76398631d --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/guava/NoMapsAndSetsWithExpectedSize.java @@ -0,0 +1,101 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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.migrate.guava; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.Preconditions; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.search.UsesJavaVersion; +import org.openrewrite.java.search.UsesMethod; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaCoordinates; + +public class NoMapsAndSetsWithExpectedSize extends Recipe { + + private static final MethodMatcher NEW_HASHMAP = new MethodMatcher("com.google.common.collect.Maps newHashMapWithExpectedSize(int)", false); + private static final MethodMatcher NEW_LINKED_HASHMAP = new MethodMatcher("com.google.common.collect.Maps newLinkedHashMapWithExpectedSize(int)", false); + private static final MethodMatcher NEW_HASHSET = new MethodMatcher("com.google.common.collect.Sets newHashSetWithExpectedSize(int)", false); + private static final MethodMatcher NEW_LINKED_HASHSET = new MethodMatcher("com.google.common.collect.Sets newLinkedHashSetWithExpectedSize(int)", false); + + @Override + public String getDisplayName() { + return "Prefer JDK methods for Maps and Sets of an expected size"; + } + + @Override + public String getDescription() { + return "Prefer Java 19+ methods to create Maps and Sets of an expected size instead of using Guava methods."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check( + Preconditions.and( + new UsesJavaVersion<>(19), + Preconditions.or( + new UsesMethod<>(NEW_HASHMAP), + new UsesMethod<>(NEW_LINKED_HASHMAP), + new UsesMethod<>(NEW_HASHSET), + new UsesMethod<>(NEW_LINKED_HASHSET) + ) + ), + new JavaVisitor() { + @Override + public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation j = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); + if (NEW_HASHMAP.matches(j)) { + maybeRemoveImport("com.google.common.collect.Maps"); + maybeAddImport("java.util.HashMap"); + JavaCoordinates coordinates = j.getCoordinates().replace(); + return JavaTemplate.builder("new HashMap<>(#{any()})") + .imports("java.util.HashMap") + .build() + .apply(getCursor(), coordinates, j.getArguments().toArray()); + } else if (NEW_LINKED_HASHMAP.matches(j)) { + maybeRemoveImport("com.google.common.collect.Maps"); + maybeAddImport("java.util.LinkedHashMap"); + JavaCoordinates coordinates = j.getCoordinates().replace(); + return JavaTemplate.builder("new LinkedHashMap<>(#{any()})") + .imports("java.util.LinkedHashMap") + .build() + .apply(getCursor(), coordinates, j.getArguments().toArray()); + } else if (NEW_HASHSET.matches(j)) { + maybeRemoveImport("com.google.common.collect.Sets"); + maybeAddImport("java.util.HashSet"); + JavaCoordinates coordinates = j.getCoordinates().replace(); + return JavaTemplate.builder("new HashSet<>(#{any()})") + .imports("java.util.HashSet") + .build() + .apply(getCursor(), coordinates, j.getArguments().toArray()); + } else if (NEW_LINKED_HASHSET.matches(j)) { + maybeRemoveImport("com.google.common.collect.Sets"); + maybeAddImport("java.util.LinkedHashSet"); + JavaCoordinates coordinates = j.getCoordinates().replace(); + return JavaTemplate.builder("new LinkedHashSet<>(#{any()})") + .imports("java.util.LinkedHashSet") + .build() + .apply(getCursor(), coordinates, j.getArguments().toArray()); + } + return j; + } + } + ); + } +} diff --git a/src/main/resources/META-INF/rewrite/no-guava.yml b/src/main/resources/META-INF/rewrite/no-guava.yml index 82f7f90a3..56e204bf7 100644 --- a/src/main/resources/META-INF/rewrite/no-guava.yml +++ b/src/main/resources/META-INF/rewrite/no-guava.yml @@ -25,6 +25,8 @@ description: >- tags: - guava recipeList: + - org.openrewrite.java.migrate.guava.NoGuavaJava11 + - org.openrewrite.java.migrate.guava.NoGuavaJava21 - org.openrewrite.java.migrate.guava.NoGuavaCreateTempDir - org.openrewrite.java.migrate.guava.NoGuavaDirectExecutor - org.openrewrite.java.migrate.guava.NoGuavaListsNewArrayList @@ -62,6 +64,8 @@ recipeList: - org.openrewrite.java.migrate.guava.PreferMathMultiplyExact - org.openrewrite.java.migrate.guava.NoGuavaAtomicsNewReference + - tech.picnic.errorprone.refasterrules.InputStreamRulesRecipes + --- type: specs.openrewrite.org/v1beta/recipe name: org.openrewrite.java.migrate.guava.NoGuavaJava11 @@ -73,8 +77,10 @@ description: >- tags: - guava - java11 +preconditions: + - org.openrewrite.java.search.HasJavaVersion: + version: "[11,)" recipeList: - - org.openrewrite.java.migrate.guava.NoGuava - org.openrewrite.java.migrate.guava.NoGuavaImmutableListOf - org.openrewrite.java.migrate.guava.NoGuavaImmutableMapOf - org.openrewrite.java.migrate.guava.NoGuavaImmutableSetOf @@ -95,9 +101,13 @@ description: >- tags: - guava - java21 +preconditions: + - org.openrewrite.java.search.HasJavaVersion: + version: "[21,)" recipeList: - - org.openrewrite.java.migrate.guava.NoGuavaJava11 + - org.openrewrite.java.migrate.guava.NoMapsAndSetsWithExpectedSize - org.openrewrite.java.migrate.guava.PreferMathClamp + --- type: specs.openrewrite.org/v1beta/recipe name: org.openrewrite.java.migrate.guava.PreferJavaNioCharsetStandardCharsets diff --git a/src/test/java/org/openrewrite/java/migrate/guava/NoGuavaJava21Test.java b/src/test/java/org/openrewrite/java/migrate/guava/NoGuavaJava21Test.java index f59f1a57d..8eed3f18d 100644 --- a/src/test/java/org/openrewrite/java/migrate/guava/NoGuavaJava21Test.java +++ b/src/test/java/org/openrewrite/java/migrate/guava/NoGuavaJava21Test.java @@ -17,23 +17,17 @@ import org.junit.jupiter.api.Test; import org.openrewrite.Issue; -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; -import static org.openrewrite.java.Assertions.version; +import static org.openrewrite.java.Assertions.*; class NoGuavaJava21Test implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - spec.recipe( - Environment.builder() - .scanRuntimeClasspath("org.openrewrite.java.migrate.guava") - .build() - .activateRecipes("org.openrewrite.java.migrate.guava.NoGuavaJava21") - ) + spec + .recipeFromResource("/META-INF/rewrite/no-guava.yml", "org.openrewrite.java.migrate.guava.NoGuava") .parser(JavaParser.fromJavaVersion().classpath("guava")); } @@ -122,32 +116,30 @@ public float testMethod() { @Test void noGuavaImmutableOfException() { rewriteRun( - version( - //language=java - java( - """ - import com.google.common.collect.ImmutableSet; - import com.google.common.collect.ImmutableMap; + //language=java + java( + """ + import com.google.common.collect.ImmutableSet; + import com.google.common.collect.ImmutableMap; - class A { - public Object getMap() { - return ImmutableMap.of("key", ImmutableSet.of("value1", "value2")); - } - } - """, - """ - import com.google.common.collect.ImmutableSet; + class A { + public Object getMap() { + return ImmutableMap.of("key", ImmutableSet.of("value1", "value2")); + } + } + """, + """ + import com.google.common.collect.ImmutableSet; - import java.util.Map; + import java.util.Map; - class A { - public Object getMap() { - return Map.of("key", ImmutableSet.of("value1", "value2")); - } - } - """ - ), - 21 + class A { + public Object getMap() { + return Map.of("key", ImmutableSet.of("value1", "value2")); + } + } + """, + spec -> spec.markers(javaVersion(21)) ) ); } diff --git a/src/test/java/org/openrewrite/java/migrate/guava/PreferJavaUtilObjectsTest.java b/src/test/java/org/openrewrite/java/migrate/guava/NoGuavaRefasterTest.java similarity index 85% rename from src/test/java/org/openrewrite/java/migrate/guava/PreferJavaUtilObjectsTest.java rename to src/test/java/org/openrewrite/java/migrate/guava/NoGuavaRefasterTest.java index f3df9d5b3..7ebd43113 100644 --- a/src/test/java/org/openrewrite/java/migrate/guava/PreferJavaUtilObjectsTest.java +++ b/src/test/java/org/openrewrite/java/migrate/guava/NoGuavaRefasterTest.java @@ -23,7 +23,7 @@ import static org.openrewrite.java.Assertions.java; -class PreferJavaUtilObjectsTest implements RewriteTest { +class NoGuavaRefasterTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { spec.recipe(new NoGuavaRefasterRecipes()) @@ -184,31 +184,4 @@ Object foo(Object obj) { ) ); } - - @Test - void moreObjectsFirstNonNullToObjectsRequireNonNullElse() { - rewriteRun(spec -> spec.recipeFromResource("/META-INF/rewrite/no-guava.yml", "org.openrewrite.java.migrate.guava.NoGuavaJava11"), - //language=java - java( - """ - import com.google.common.base.MoreObjects; - - class A { - Object foo(Object obj) { - return MoreObjects.firstNonNull(obj, "default"); - } - } - """, - """ - import java.util.Objects; - - class A { - Object foo(Object obj) { - return Objects.requireNonNullElse(obj, "default"); - } - } - """ - ) - ); - } } diff --git a/src/test/java/org/openrewrite/java/migrate/guava/NoGuavaTest.java b/src/test/java/org/openrewrite/java/migrate/guava/NoGuavaTest.java index 5701794a8..f53de64aa 100644 --- a/src/test/java/org/openrewrite/java/migrate/guava/NoGuavaTest.java +++ b/src/test/java/org/openrewrite/java/migrate/guava/NoGuavaTest.java @@ -16,26 +16,52 @@ package org.openrewrite.java.migrate.guava; import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; import org.openrewrite.Issue; -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; +import static org.openrewrite.java.Assertions.javaVersion; class NoGuavaTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - spec.recipe( - Environment.builder() - .scanRuntimeClasspath("org.openrewrite.java.migrate.guava") - .build() - .activateRecipes("org.openrewrite.java.migrate.guava.NoGuava") - ) + spec + .recipeFromResource("/META-INF/rewrite/no-guava.yml", "org.openrewrite.java.migrate.guava.NoGuava") .parser(JavaParser.fromJavaVersion().classpath("guava")); } + @DocumentExample + @Test + void moreObjectsFirstNonNullToObjectsRequireNonNullElse() { + rewriteRun( + //language=java + java( + """ + import com.google.common.base.MoreObjects; + + class A { + Object foo(Object obj) { + return MoreObjects.firstNonNull(obj, "default"); + } + } + """, + """ + import java.util.Objects; + + class A { + Object foo(Object obj) { + return Objects.requireNonNullElse(obj, "default"); + } + } + """, + spec -> spec.markers(javaVersion(11)) + ) + ); + } + @Test @Issue("https://github.com/openrewrite/rewrite-migrate-java/issues/39#issuecomment-910673213") void preferJavaUtilObjectsHashCode() { diff --git a/src/test/java/org/openrewrite/java/migrate/guava/NoMapsAndSetsWithExpectedSizeTest.java b/src/test/java/org/openrewrite/java/migrate/guava/NoMapsAndSetsWithExpectedSizeTest.java new file mode 100644 index 000000000..aad926d65 --- /dev/null +++ b/src/test/java/org/openrewrite/java/migrate/guava/NoMapsAndSetsWithExpectedSizeTest.java @@ -0,0 +1,69 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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.migrate.guava; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.java.Assertions.javaVersion; + +class NoMapsAndSetsWithExpectedSizeTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new NoMapsAndSetsWithExpectedSize()); + } + + @DocumentExample + @Test + void noMapSetWithExpectedSize() { + rewriteRun( + //language=java + java( + """ + import com.google.common.collect.Maps; + import com.google.common.collect.Sets; + import java.util.Map; + import java.util.Set; + + class A { + void method() { + Map a = Maps.newHashMapWithExpectedSize(1); + Map b = Maps.newLinkedHashMapWithExpectedSize(1); + Set c = Sets.newHashSetWithExpectedSize(1); + Set d = Sets.newLinkedHashSetWithExpectedSize(1); + } + } + """, + """ + import java.util.*; + + class A { + void method() { + Map a = new HashMap<>(1); + Map b = new LinkedHashMap<>(1); + Set c = new HashSet<>(1); + Set d = new LinkedHashSet<>(1); + } + } + """, + spec -> spec.markers(javaVersion(21)) + ) + ); + } +}