diff --git a/src/main/java/org/openrewrite/java/migrate/guava/NoGuavaPredicate.java b/src/main/java/org/openrewrite/java/migrate/guava/NoGuavaPredicate.java new file mode 100644 index 0000000000..b9db662b39 --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/guava/NoGuavaPredicate.java @@ -0,0 +1,73 @@ +/* + * 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.ChangeType; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaSourceFile; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.marker.SearchResult; + +public class NoGuavaPredicate extends Recipe { + @Override + public String getDisplayName() { + return "Change Guava's `Predicate` into `java.util.function.Predicate` where possible"; + } + + @Override + public String getDescription() { + return "Change the type only where no methods are used that explicitly require a Guava `Predicate`."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check( + Preconditions.not(new UsesPredicateMethod<>()), + new ChangeType( + "com.google.common.base.Predicate", + "java.util.function.Predicate", + false) + .getVisitor() + ); + } + + private static class UsesPredicateMethod

extends JavaIsoVisitor

{ + private static final MethodMatcher PREDICATE_METHOD_MATCHER = new MethodMatcher("*..* *(.., com.google.common.base.Predicate)"); + private static final MethodMatcher NOT_MATCHER = new MethodMatcher("*..* not(com.google.common.base.Predicate)"); + + @Override + public J preVisit(J tree, P p) { + stopAfterPreVisit(); + if (tree instanceof JavaSourceFile) { + JavaSourceFile cu = (JavaSourceFile) tree; + for (JavaType.Method type : cu.getTypesInUse().getUsedMethods()) { + if (PREDICATE_METHOD_MATCHER.matches(type) && + // Make an exception for `not` methods; those can be safely converted + !NOT_MATCHER.matches(type)) { + return SearchResult.found(cu); + } + } + } + return tree; + } + } +} diff --git a/src/main/resources/META-INF/rewrite/no-guava.yml b/src/main/resources/META-INF/rewrite/no-guava.yml index 036114b02c..8684f621a2 100644 --- a/src/main/resources/META-INF/rewrite/no-guava.yml +++ b/src/main/resources/META-INF/rewrite/no-guava.yml @@ -195,9 +195,7 @@ recipeList: methodPattern: com.google.common.base.Predicate apply(..) newMethodName: test matchOverrides: true - - org.openrewrite.java.ChangeType: - oldFullyQualifiedTypeName: com.google.common.base.Predicate - newFullyQualifiedTypeName: java.util.function.Predicate + - org.openrewrite.java.migrate.guava.NoGuavaPredicate --- type: specs.openrewrite.org/v1beta/recipe diff --git a/src/test/java/org/openrewrite/java/migrate/guava/NoGuavaPredicateTest.java b/src/test/java/org/openrewrite/java/migrate/guava/NoGuavaPredicateTest.java new file mode 100644 index 0000000000..c359d3e586 --- /dev/null +++ b/src/test/java/org/openrewrite/java/migrate/guava/NoGuavaPredicateTest.java @@ -0,0 +1,304 @@ +/* + * 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.InMemoryExecutionContext; +import org.openrewrite.Issue; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class NoGuavaPredicateTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec + .recipe(new NoGuavaPredicate()) + .parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(), "guava")); + } + + @DocumentExample + @Test + void changeGuavaPredicateToJavaUtilPredicate() { + rewriteRun( + //language=java + java( + """ + import com.google.common.base.Predicate; + + class Test { + Predicate predicate = input -> input != null && !input.isEmpty(); + } + """, + """ + import java.util.function.Predicate; + + class Test { + Predicate predicate = input -> input != null && !input.isEmpty(); + } + """ + ) + ); + } + + @Test + void changeWhenMethodOnlyReturnsGuavaPredicate() { + // Recipe only blocks when Predicate is used as a parameter, not return type + rewriteRun( + //language=java + java( + """ + import com.google.common.base.Predicate; + + class Test { + Predicate predicate = input -> input != null; + + public Predicate createPredicate() { + return s -> s.isEmpty(); + } + } + """, + """ + import java.util.function.Predicate; + + class Test { + Predicate predicate = input -> input != null; + + public Predicate createPredicate() { + return s -> s.isEmpty(); + } + } + """ + ) + ); + } + + @Test + void changePredicatesNot() { + rewriteRun( + spec -> spec.recipeFromResource( + "/META-INF/rewrite/no-guava.yml", + "org.openrewrite.java.migrate.guava.PreferJavaUtilPredicate"), + //language=java + java( + """ + import com.google.common.base.Predicate; + import com.google.common.base.Predicates; + + class A { + Predicate notEmptyPredicate() { + Predicate isEmpty = String::isEmpty; + return Predicates.not(isEmpty); + } + } + """, + """ + import java.util.function.Predicate; + + class A { + Predicate notEmptyPredicate() { + Predicate isEmpty = String::isEmpty; + return Predicate.not(isEmpty); + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-migrate-java/issues/897") + @Test + void doNotChangeWhenUsingSetsFilter() { + // Sets.filter requires Guava Predicate as last parameter + rewriteRun( + //language=java + java( + """ + import com.google.common.base.Predicate; + import com.google.common.collect.Sets; + import java.util.Set; + + class Test { + Predicate notEmpty = s -> !s.isEmpty(); + + public Set filterSet(Set input) { + return Sets.filter(input, notEmpty); + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-migrate-java/issues/883") + @Test + void doNotChangeWhenUsingIterablesFilter() { + // Iterables.filter requires Guava Predicate as last parameter + rewriteRun( + //language=java + java( + """ + import com.google.common.base.Predicate; + import com.google.common.collect.Iterables; + import java.util.List; + + class Test { + Predicate isPositive = n -> n > 0; + + public Iterable filterPositive(List numbers) { + return Iterables.filter(numbers, isPositive); + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-migrate-java/issues/899") + @Test + void doNotChangeWhenUsingCollectionsFilter() { + // Collections2.filter requires Guava Predicate as last parameter + rewriteRun( + //language=java + java( + """ + import com.google.common.base.Predicate; + import com.google.common.collect.Collections2; + import java.util.Collection; + + class Test { + Predicate notEmpty = s -> !s.isEmpty(); + + public Collection filterCollection(Collection input) { + return Collections2.filter(input, notEmpty); + } + } + """ + ) + ); + } + + @Test + void doNotChangeWhenUsingIteratorsFilter() { + // Iterators.filter requires Guava Predicate as last parameter + rewriteRun( + //language=java + java( + """ + import com.google.common.base.Predicate; + import com.google.common.collect.Iterators; + + import java.util.Iterator; + + class Test { + Predicate isPositive = n -> n > 0; + + public Iterator filterPositive(Iterator numbers) { + return Iterators.filter(numbers, isPositive); + } + } + """ + ) + ); + } + + @Test + void doNotChangeWhenUsingIterablesAny() { + // Iterables.any requires Guava Predicate as last parameter + rewriteRun( + //language=java + java( + """ + import com.google.common.base.Predicate; + import com.google.common.collect.Iterables; + import java.util.List; + + class Test { + public boolean any(List input, Predicate aPredicate) { + return Iterables.any(input, aPredicate); + } + } + """ + ) + ); + } + + @Test + void doNotChangeWhenUsingMapsFilterEntries() { + // Maps.filterEntries requires Guava Predicate as last parameter + rewriteRun( + //language=java + java( + """ + import com.google.common.base.Predicate; + import com.google.common.collect.Maps; + import java.util.Map; + + class Test { + public Map filterMap(Map input, Predicate> aPredicate) { + return Maps.filterEntries(input, aPredicate); + } + } + """ + ) + ); + } + + @Test + void doNotChangeWhenUsingMapsFilterValues() { + // Maps.filterValues requires Guava Predicate as last parameter + rewriteRun( + //language=java + java( + """ + import com.google.common.base.Predicate; + import com.google.common.collect.Maps; + import java.util.Map; + + class Test { + public Map filterMap(Map input, Predicate aPredicate) { + return Maps.filterValues(input, aPredicate); + } + } + """ + ) + ); + } + + @Test + void doNotChangeWhenUsingMapsFilterKeys() { + // Maps.filterKeys requires Guava Predicate as last parameter + rewriteRun( + //language=java + java( + """ + import com.google.common.base.Predicate; + import com.google.common.collect.Maps; + import java.util.Map; + + class Test { + public Map filterMap(Map input, Predicate aPredicate) { + return Maps.filterKeys(input, aPredicate); + } + } + """ + ) + ); + } +} diff --git a/src/test/java/org/openrewrite/java/migrate/guava/PreferJavaUtilPredicateTest.java b/src/test/java/org/openrewrite/java/migrate/guava/PreferJavaUtilPredicateTest.java index 0c2ff6afaa..083d9838f0 100644 --- a/src/test/java/org/openrewrite/java/migrate/guava/PreferJavaUtilPredicateTest.java +++ b/src/test/java/org/openrewrite/java/migrate/guava/PreferJavaUtilPredicateTest.java @@ -27,7 +27,9 @@ class PreferJavaUtilPredicateTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - spec.recipeFromResources("org.openrewrite.java.migrate.guava.PreferJavaUtilPredicate") + spec.recipeFromResource( + "/META-INF/rewrite/no-guava.yml", + "org.openrewrite.java.migrate.guava.PreferJavaUtilPredicate") .parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(), "guava")); }