From f1c66dfca985549cf5e37d4e261141842c9a69ef Mon Sep 17 00:00:00 2001 From: Yifeng Jin Date: Fri, 8 Dec 2023 16:58:21 +0800 Subject: [PATCH] CastArraysAsListToList Recipe (#366) * Add CastArraysAsListToList Recipe * Minor polish: UsesMethod, update display name, unused imports --------- Co-authored-by: Tim te Beek --- .../java/migrate/CastArraysAsListToList.java | 95 +++++++++++++++++++ .../META-INF/rewrite/java-version-11.yml | 1 + .../migrate/CastArraysAsListToListTest.java | 81 ++++++++++++++++ 3 files changed, 177 insertions(+) create mode 100644 src/main/java/org/openrewrite/java/migrate/CastArraysAsListToList.java create mode 100644 src/test/java/org/openrewrite/java/migrate/CastArraysAsListToListTest.java diff --git a/src/main/java/org/openrewrite/java/migrate/CastArraysAsListToList.java b/src/main/java/org/openrewrite/java/migrate/CastArraysAsListToList.java new file mode 100644 index 0000000000..19966900a7 --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/CastArraysAsListToList.java @@ -0,0 +1,95 @@ +/* + * 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.migrate; + +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.UsesMethod; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; + +public class CastArraysAsListToList extends Recipe { + + @Override + public String getDisplayName() { + return "Remove explicit casts on `Arrays.asList(..).toArray()`"; + } + + @Override + public String getDescription() { + //language=markdown + return "Convert code like `(Integer[]) Arrays.asList(1, 2, 3).toArray()` to `Arrays.asList(1, 2, 3).toArray(new Integer[0])`."; + } + + private static final MethodMatcher ARRAYS_AS_LIST = new MethodMatcher("java.util.Arrays asList(..)", false); + private static final MethodMatcher LIST_TO_ARRAY = new MethodMatcher("java.util.List toArray()", true); + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check( + Preconditions.and(new UsesMethod<>(ARRAYS_AS_LIST), new UsesMethod<>(LIST_TO_ARRAY)), + new CastArraysAsListToListVisitor()); + } + + private static class CastArraysAsListToListVisitor extends JavaVisitor { + @Override + public J visitTypeCast(J.TypeCast typeCast, ExecutionContext executionContext) { + J j = super.visitTypeCast(typeCast, executionContext); + if (!(j instanceof J.TypeCast)) { + return j; + } + typeCast = (J.TypeCast) j; + + boolean matches = typeCast.getClazz().getTree() instanceof J.ArrayType + && (typeCast.getType() instanceof JavaType.Class || typeCast.getType() instanceof JavaType.Parameterized) + && ((JavaType.FullyQualified) typeCast.getType()).getOwningClass() == null // does not support inner class now + && LIST_TO_ARRAY.matches(typeCast.getExpression()) + && typeCast.getExpression() instanceof J.MethodInvocation + && ARRAYS_AS_LIST.matches(((J.MethodInvocation) typeCast.getExpression()).getSelect()); + if (!matches) { + return typeCast; + } + + String fullyQualifiedName = ((JavaType.FullyQualified) typeCast.getType()).getFullyQualifiedName(); + int dimensionSize = ((J.ArrayType) typeCast.getClazz().getTree()).getDimensions().size(); + + if (fullyQualifiedName.equals("java.lang.Object") && dimensionSize == 1) { + // we don't need to fix this case because toArray() does return Object[] type + return typeCast; + } + + // we don't add generic type name here because generic array creation is not allowed + StringBuilder newArrayString = new StringBuilder(); + String className = fullyQualifiedName.substring(fullyQualifiedName.lastIndexOf(".") + 1); + newArrayString.append(className); + newArrayString.append("[0]"); + for (int i = 0; i < dimensionSize - 1; i++) { + newArrayString.append("[]"); + } + + JavaTemplate t = JavaTemplate + .builder("#{any(java.util.List)}.toArray(new " + newArrayString + ")") + .imports(fullyQualifiedName) + .build(); + return t.apply(updateCursor(typeCast), typeCast.getCoordinates().replace(), ((J.MethodInvocation) typeCast.getExpression()).getSelect()); + } + } +} diff --git a/src/main/resources/META-INF/rewrite/java-version-11.yml b/src/main/resources/META-INF/rewrite/java-version-11.yml index 87cd4877d5..6877dcb12f 100644 --- a/src/main/resources/META-INF/rewrite/java-version-11.yml +++ b/src/main/resources/META-INF/rewrite/java-version-11.yml @@ -32,6 +32,7 @@ tags: recipeList: - org.openrewrite.java.migrate.UpgradeToJava8 - org.openrewrite.java.migrate.UseJavaUtilBase64 + - org.openrewrite.java.migrate.CastArraysAsListToList # Add an explicit JAXB/JAX-WS runtime and upgrade the dependencies to Jakarta EE 8 - org.openrewrite.java.migrate.javax.AddJaxbDependencies - org.openrewrite.java.migrate.javax.AddJaxwsDependencies diff --git a/src/test/java/org/openrewrite/java/migrate/CastArraysAsListToListTest.java b/src/test/java/org/openrewrite/java/migrate/CastArraysAsListToListTest.java new file mode 100644 index 0000000000..6fc175cc11 --- /dev/null +++ b/src/test/java/org/openrewrite/java/migrate/CastArraysAsListToListTest.java @@ -0,0 +1,81 @@ +/* + * 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.migrate; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class CastArraysAsListToListTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new CastArraysAsListToList()); + } + + @Test + void positiveCases() { + rewriteRun( + //language=java + java( + """ + import java.util.Arrays; + + class Foo { + void bar() { + Integer[] array1 = (Integer[]) Arrays.asList(1, 2, 3).toArray(); + Integer[][] array2 = (Integer[][]) Arrays.asList(new Integer[]{1}, new Integer[]{2}).toArray(); + Object[][] array3 = (Object[][]) Arrays.asList(new Object[]{}, new Object[]{}).toArray(); + } + } + """, + """ + import java.util.Arrays; + + class Foo { + void bar() { + Integer[] array1 = Arrays.asList(1, 2, 3).toArray(new Integer[0]); + Integer[][] array2 = Arrays.asList(new Integer[]{1}, new Integer[]{2}).toArray(new Integer[0][]); + Object[][] array3 = Arrays.asList(new Object[]{}, new Object[]{}).toArray(new Object[0][]); + } + } + """ + ) + ); + } + + @Test + void negativeCases() { + rewriteRun( + //language=java + java( + """ + import java.util.Arrays; + import java.util.Collections; + + class Foo { + void bar() { + Object[] array1 = (Object[]) Arrays.asList("a","b").toArray(); + Integer[] array2 = (Integer[]) Collections.singletonList(1).toArray(); + Integer[] array3 = Arrays.asList(1, 2, 3).toArray(new Integer[0]); + } + } + """ + ) + ); + } +}