Skip to content

Commit 3e07f69

Browse files
authored
Add UseListOf and UseSetOf recipes (#974)
Add recipes to convert double-brace initialization of ArrayList and HashSet to List.of() and Set.of() respectively for Java 10+. These recipes complement the existing UseMapOf recipe. Example transformation for UseListOf: new ArrayList<>() {{ add("a"); add("b"); }} becomes List.of("a", "b") Example transformation for UseSetOf: new HashSet<>() {{ add("a"); add("b"); }} becomes Set.of("a", "b") Fixes: moderneinc/customer-requests#1544
1 parent 49c0978 commit 3e07f69

5 files changed

Lines changed: 642 additions & 0 deletions

File tree

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
* <p>
4+
* Licensed under the Moderne Source Available License (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://docs.moderne.io/licensing/moderne-source-available-license
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.java.migrate.util;
17+
18+
import lombok.Getter;
19+
import org.openrewrite.ExecutionContext;
20+
import org.openrewrite.Preconditions;
21+
import org.openrewrite.Recipe;
22+
import org.openrewrite.TreeVisitor;
23+
import org.openrewrite.java.JavaTemplate;
24+
import org.openrewrite.java.JavaVisitor;
25+
import org.openrewrite.java.MethodMatcher;
26+
import org.openrewrite.java.search.UsesJavaVersion;
27+
import org.openrewrite.java.search.UsesMethod;
28+
import org.openrewrite.java.tree.Expression;
29+
import org.openrewrite.java.tree.J;
30+
import org.openrewrite.java.tree.Statement;
31+
32+
import java.util.ArrayList;
33+
import java.util.List;
34+
import java.util.StringJoiner;
35+
36+
public class UseListOf extends Recipe {
37+
private static final MethodMatcher NEW_ARRAY_LIST = new MethodMatcher("java.util.ArrayList <constructor>()", true);
38+
private static final MethodMatcher LIST_ADD = new MethodMatcher("java.util.List add(..)", true);
39+
40+
@Getter
41+
final String displayName = "Prefer `List.of(..)`";
42+
43+
@Getter
44+
final String description = "Prefer `List.of(..)` instead of using `java.util.List#add(..)` in anonymous ArrayList initializers in Java 10 or higher. " +
45+
"This recipe will not modify code where the List is later mutated since `List.of` returns an immutable list.";
46+
47+
@Override
48+
public TreeVisitor<?, ExecutionContext> getVisitor() {
49+
return Preconditions.check(
50+
Preconditions.and(
51+
new UsesJavaVersion<>(10),
52+
new UsesMethod<>(NEW_ARRAY_LIST)),
53+
new JavaVisitor<ExecutionContext>() {
54+
@Override
55+
public J visitNewClass(J.NewClass newClass, ExecutionContext ctx) {
56+
J.NewClass n = (J.NewClass) super.visitNewClass(newClass, ctx);
57+
J.Block body = n.getBody();
58+
if (NEW_ARRAY_LIST.matches(n) && body != null && body.getStatements().size() == 1) {
59+
Statement statement = body.getStatements().get(0);
60+
if (statement instanceof J.Block) {
61+
List<Expression> args = new ArrayList<>();
62+
StringJoiner listOf = new StringJoiner(", ", "List.of(", ")");
63+
for (Statement stat : ((J.Block) statement).getStatements()) {
64+
if (!(stat instanceof J.MethodInvocation) || !LIST_ADD.matches((Expression) stat)) {
65+
return n;
66+
}
67+
J.MethodInvocation add = (J.MethodInvocation) stat;
68+
// List.add() takes only one argument
69+
if (add.getArguments().size() != 1) {
70+
return n;
71+
}
72+
args.add(add.getArguments().get(0));
73+
listOf.add("#{any()}");
74+
}
75+
76+
maybeRemoveImport("java.util.ArrayList");
77+
maybeAddImport("java.util.List");
78+
return JavaTemplate.builder(listOf.toString())
79+
.contextSensitive()
80+
.imports("java.util.List")
81+
.build()
82+
.apply(updateCursor(n), n.getCoordinates().replace(), args.toArray());
83+
}
84+
}
85+
86+
return n;
87+
}
88+
});
89+
}
90+
91+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
* <p>
4+
* Licensed under the Moderne Source Available License (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://docs.moderne.io/licensing/moderne-source-available-license
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.java.migrate.util;
17+
18+
import lombok.Getter;
19+
import org.openrewrite.ExecutionContext;
20+
import org.openrewrite.Preconditions;
21+
import org.openrewrite.Recipe;
22+
import org.openrewrite.TreeVisitor;
23+
import org.openrewrite.java.JavaTemplate;
24+
import org.openrewrite.java.JavaVisitor;
25+
import org.openrewrite.java.MethodMatcher;
26+
import org.openrewrite.java.search.UsesJavaVersion;
27+
import org.openrewrite.java.search.UsesMethod;
28+
import org.openrewrite.java.tree.Expression;
29+
import org.openrewrite.java.tree.J;
30+
import org.openrewrite.java.tree.Statement;
31+
32+
import java.util.ArrayList;
33+
import java.util.List;
34+
import java.util.StringJoiner;
35+
36+
public class UseSetOf extends Recipe {
37+
private static final MethodMatcher NEW_HASH_SET = new MethodMatcher("java.util.HashSet <constructor>()", true);
38+
private static final MethodMatcher SET_ADD = new MethodMatcher("java.util.Set add(..)", true);
39+
40+
@Getter
41+
final String displayName = "Prefer `Set.of(..)`";
42+
43+
@Getter
44+
final String description = "Prefer `Set.of(..)` instead of using `java.util.Set#add(..)` in anonymous HashSet initializers in Java 10 or higher. " +
45+
"This recipe will not modify code where the Set is later mutated since `Set.of` returns an immutable set.";
46+
47+
@Override
48+
public TreeVisitor<?, ExecutionContext> getVisitor() {
49+
return Preconditions.check(
50+
Preconditions.and(
51+
new UsesJavaVersion<>(10),
52+
new UsesMethod<>(NEW_HASH_SET)),
53+
new JavaVisitor<ExecutionContext>() {
54+
@Override
55+
public J visitNewClass(J.NewClass newClass, ExecutionContext ctx) {
56+
J.NewClass n = (J.NewClass) super.visitNewClass(newClass, ctx);
57+
J.Block body = n.getBody();
58+
if (NEW_HASH_SET.matches(n) && body != null && body.getStatements().size() == 1) {
59+
Statement statement = body.getStatements().get(0);
60+
if (statement instanceof J.Block) {
61+
List<Expression> args = new ArrayList<>();
62+
StringJoiner setOf = new StringJoiner(", ", "Set.of(", ")");
63+
for (Statement stat : ((J.Block) statement).getStatements()) {
64+
if (!(stat instanceof J.MethodInvocation) || !SET_ADD.matches((Expression) stat)) {
65+
return n;
66+
}
67+
J.MethodInvocation add = (J.MethodInvocation) stat;
68+
// Set.add() takes only one argument
69+
if (add.getArguments().size() != 1) {
70+
return n;
71+
}
72+
args.add(add.getArguments().get(0));
73+
setOf.add("#{any()}");
74+
}
75+
76+
maybeRemoveImport("java.util.HashSet");
77+
maybeAddImport("java.util.Set");
78+
return JavaTemplate.builder(setOf.toString())
79+
.contextSensitive()
80+
.imports("java.util.Set")
81+
.build()
82+
.apply(updateCursor(n), n.getCoordinates().replace(), args.toArray());
83+
}
84+
}
85+
86+
return n;
87+
}
88+
});
89+
}
90+
91+
}

src/main/resources/META-INF/rewrite/recipes.csv

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,8 @@ maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.u
418418
maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.util.UseEnumSetOf,Prefer `EnumSet of(..)`,Prefer `EnumSet of(..)` instead of using `Set of(..)` when the arguments are enums in Java 9 or higher.,1,,`java.util` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,"[{""name"":""convertEmptySet"",""type"":""Boolean"",""displayName"":""Convert empty `Set.of()` to `EnumSet.noneOf()`"",""description"":""When true, converts `Set.of()` with no arguments to `EnumSet.noneOf()`. Default true."",""example"":""true""}]",
419419
maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.util.UseLocaleOf,Prefer `Locale.of(..)` over `new Locale(..)`,Prefer `Locale.of(..)` over `new Locale(..)` in Java 19 or higher.,1,,`java.util` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,
420420
maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.util.UseMapOf,Prefer `Map.of(..)`,Prefer `Map.of(..)` instead of using `java.util.Map#put(..)` in Java 10 or higher.,1,,`java.util` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,
421+
maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.util.UseListOf,Prefer `List.of(..)`,Prefer `List.of(..)` instead of using `java.util.List#add(..)` in anonymous ArrayList initializers in Java 10 or higher. This recipe will not modify code where the List is later mutated since `List.of` returns an immutable list.,1,,`java.util` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,
422+
maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.util.UseSetOf,Prefer `Set.of(..)`,Prefer `Set.of(..)` instead of using `java.util.Set#add(..)` in anonymous HashSet initializers in Java 10 or higher. This recipe will not modify code where the Set is later mutated since `Set.of` returns an immutable set.,1,,`java.util` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,
421423
maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.util.JavaUtilAPIs,Use modernized `java.util` APIs,Certain java util APIs have been introduced and are favored over previous APIs.,15,,`java.util` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,
422424
maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.util.SequencedCollection,Adopt `SequencedCollection`,"Replace older code patterns with `SequencedCollection` methods, as per https://openjdk.org/jeps/431.",13,,`java.util` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,
423425
maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.util.MigrateInflaterDeflaterToClose,Replace `Inflater` and `Deflater` `end()` calls with `close()`,"Replace `end()` method calls with `close()` method calls for `Inflater` and `Deflater` classes in Java 25+, as they now implement AutoCloseable.",7,,`java.util` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,

0 commit comments

Comments
 (0)