Skip to content

Commit

Permalink
Recipes to replace Guava Immutable{Set|List|Map}.copyOf() with Java…
Browse files Browse the repository at this point in the history
… `{Set|List|Map}.copyOf()`

Fixes #584

Add recipes to replace Guava `ImmutableSet.copyOf()`, `ImmutableList.copyOf()`, and `ImmutableMap.copyOf()` calls with Java `Set.copyOf()`, `List.copyOf()`, and `Map.copyOf()` calls.

* Add `AbstractNoGuavaImmutableCopyOf` class to provide a base for the new recipes.
* Add `NoGuavaImmutableSetCopyOf`, `NoGuavaImmutableListCopyOf`, and `NoGuavaImmutableMapCopyOf` classes extending `AbstractNoGuavaImmutableCopyOf`.
* Update `no-guava.yml` to include the new recipes.
* Add test classes `NoGuavaImmutableSetCopyOfTest`, `NoGuavaImmutableListCopyOfTest`, and `NoGuavaImmutableMapCopyOfTest` with test cases for the new recipes.

---

For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/openrewrite/rewrite-migrate-java/issues/584?shareId=XXXX-XXXX-XXXX-XXXX).
  • Loading branch information
knutwannheden committed Oct 20, 2024
1 parent 88e8ff2 commit 2276964
Show file tree
Hide file tree
Showing 9 changed files with 1,661 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"java.compile.nullAnalysis.mode": "automatic"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.jspecify.annotations.Nullable;
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.UsesType;
import org.openrewrite.java.tree.*;

import java.time.Duration;

abstract class AbstractNoGuavaImmutableCopyOf extends Recipe {

private final String guavaType;
private final String javaType;

AbstractNoGuavaImmutableCopyOf(String guavaType, String javaType) {
this.guavaType = guavaType;
this.javaType = javaType;
}

private String getShortType(String fullyQualifiedType) {
return fullyQualifiedType.substring(javaType.lastIndexOf(".") + 1);
}

@Override
public String getDisplayName() {
return "Prefer `" + getShortType(javaType) + ".copyOf(..)` in Java 10 or higher";
}

@Override
public String getDescription() {
return "Replaces `" + getShortType(guavaType) + ".copyOf(..)` if the returned type is immediately down-cast.";
}

@Override
public Duration getEstimatedEffortPerOccurrence() {
return Duration.ofMinutes(10);
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
TreeVisitor<?, ExecutionContext> check = Preconditions.and(new UsesJavaVersion<>(10),
new UsesType<>(guavaType, false));
final MethodMatcher IMMUTABLE_MATCHER = new MethodMatcher(guavaType + " copyOf(..)");
return Preconditions.check(check, new JavaVisitor<ExecutionContext>() {
@Override
public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
if (IMMUTABLE_MATCHER.matches(method) && isParentTypeDownCast(method)) {
maybeRemoveImport(guavaType);
maybeAddImport(javaType);

J.MethodInvocation templated = JavaTemplate.builder(getShortType(javaType) + ".copyOf(#{any()})")
.imports(javaType)
.build()
.apply(getCursor(),
method.getCoordinates().replace(),
method.getArguments().get(0));
return templated.getPadding().withArguments(method.getPadding().getArguments());
}
return super.visitMethodInvocation(method, ctx);
}

private boolean isParentTypeDownCast(MethodCall immutableMethod) {
J parent = getCursor().dropParentUntil(J.class::isInstance).getValue();
boolean isParentTypeDownCast = false;
if (parent instanceof J.VariableDeclarations.NamedVariable) {
isParentTypeDownCast = isParentTypeMatched(((J.VariableDeclarations.NamedVariable) parent).getType());
} else if (parent instanceof J.Assignment) {
J.Assignment a = (J.Assignment) parent;
if (a.getVariable() instanceof J.Identifier && ((J.Identifier) a.getVariable()).getFieldType() != null) {
isParentTypeDownCast = isParentTypeMatched(((J.Identifier) a.getVariable()).getFieldType().getType());
} else if (a.getVariable() instanceof J.FieldAccess) {
isParentTypeDownCast = isParentTypeMatched(a.getVariable().getType());
}
} else if (parent instanceof J.Return) {
// Does not currently support returns in lambda expressions.
J j = getCursor().dropParentUntil(is -> is instanceof J.MethodDeclaration || is instanceof J.CompilationUnit).getValue();
if (j instanceof J.MethodDeclaration) {
TypeTree returnType = ((J.MethodDeclaration) j).getReturnTypeExpression();
if (returnType != null) {
isParentTypeDownCast = isParentTypeMatched(returnType.getType());
}
}
} else if (parent instanceof J.MethodInvocation) {
J.MethodInvocation m = (J.MethodInvocation) parent;
int index = m.getArguments().indexOf(immutableMethod);
if (m.getMethodType() != null && index != -1) {
isParentTypeDownCast = isParentTypeMatched(m.getMethodType().getParameterTypes().get(index));
}
} else if (parent instanceof J.NewClass) {
J.NewClass c = (J.NewClass) parent;
int index = 0;
if (c.getConstructorType() != null) {
for (Expression argument : c.getArguments()) {
if (IMMUTABLE_MATCHER.matches(argument)) {
break;
}
index++;
}
if (c.getConstructorType() != null) {
isParentTypeDownCast = isParentTypeMatched(c.getConstructorType().getParameterTypes().get(index));
}
}
} else if (parent instanceof J.NewArray) {
J.NewArray a = (J.NewArray) parent;
JavaType arrayType = a.getType();
while (arrayType instanceof JavaType.Array) {
arrayType = ((JavaType.Array) arrayType).getElemType();
}

isParentTypeDownCast = isParentTypeMatched(arrayType);
}
return isParentTypeDownCast;
}

private boolean isParentTypeMatched(@Nullable JavaType type) {
JavaType.FullyQualified fq = TypeUtils.asFullyQualified(type);
return TypeUtils.isOfClassType(fq, javaType) ||
TypeUtils.isOfClassType(fq, "java.lang.Object");
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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;

public class NoGuavaImmutableListCopyOf extends AbstractNoGuavaImmutableCopyOf {
public NoGuavaImmutableListCopyOf() {
super("com.google.common.collect.ImmutableList", "java.util.List");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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;

public class NoGuavaImmutableMapCopyOf extends AbstractNoGuavaImmutableCopyOf {
public NoGuavaImmutableMapCopyOf() {
super("com.google.common.collect.ImmutableMap", "java.util.Map");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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;

public class NoGuavaImmutableSetCopyOf extends AbstractNoGuavaImmutableCopyOf {
public NoGuavaImmutableSetCopyOf() {
super("com.google.common.collect.ImmutableSet", "java.util.Set");
}
}
3 changes: 3 additions & 0 deletions src/main/resources/META-INF/rewrite/no-guava.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ recipeList:
- org.openrewrite.java.migrate.guava.NoGuavaImmutableListOf
- org.openrewrite.java.migrate.guava.NoGuavaImmutableMapOf
- org.openrewrite.java.migrate.guava.NoGuavaImmutableSetOf
- org.openrewrite.java.migrate.guava.NoGuavaImmutableSetCopyOf
- org.openrewrite.java.migrate.guava.NoGuavaImmutableListCopyOf
- org.openrewrite.java.migrate.guava.NoGuavaImmutableMapCopyOf
- org.openrewrite.java.migrate.guava.PreferJavaUtilObjectsRequireNonNullElse
- org.openrewrite.java.dependencies.UpgradeDependencyVersion:
groupId: io.springfox
Expand Down
Loading

0 comments on commit 2276964

Please sign in to comment.