Skip to content

Commit 96d6dd9

Browse files
mkargtimtebeek
andauthored
Replace Collections.emptyXXX with Immutable Static Factory Methods (#1045)
* Replace Collections.emptyXXX with Immutable Static Factory Methods Closes #1044 * Add Java 9+ precondition and use JavaTemplate for Collections.emptyXXX recipes * Add all util recipes to JavaUtilAPIs and reference it from best practices * Fix ReplaceMathRandomWithThreadLocalRandom recipe class name --------- Co-authored-by: Tim te Beek <[email protected]>
1 parent 2a56971 commit 96d6dd9

10 files changed

Lines changed: 527 additions & 22 deletions

File tree

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2026 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.*;
20+
import org.openrewrite.java.JavaIsoVisitor;
21+
import org.openrewrite.java.JavaTemplate;
22+
import org.openrewrite.java.MethodMatcher;
23+
import org.openrewrite.java.search.UsesJavaVersion;
24+
import org.openrewrite.java.search.UsesMethod;
25+
import org.openrewrite.java.tree.J;
26+
27+
public class MigrateCollectionsEmptyList extends Recipe {
28+
private static final MethodMatcher EMPTY_LIST = new MethodMatcher("java.util.Collections emptyList()");
29+
30+
@Getter
31+
final String displayName = "Prefer `List.of()`";
32+
33+
@Getter
34+
final String description = "Prefer `List.of()` instead of using `Collections.emptyList()` in Java 9 or higher.";
35+
36+
@Override
37+
public TreeVisitor<?, ExecutionContext> getVisitor() {
38+
TreeVisitor<?, ExecutionContext> check = Preconditions.and(new UsesJavaVersion<>(9),
39+
new UsesMethod<>(EMPTY_LIST));
40+
return Preconditions.check(check, new JavaIsoVisitor<ExecutionContext>() {
41+
42+
@Override
43+
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
44+
J.MethodInvocation m = super.visitMethodInvocation(method, ctx);
45+
46+
if (EMPTY_LIST.matches(m)) {
47+
maybeRemoveImport("java.util.Collections");
48+
maybeAddImport("java.util.List");
49+
return JavaTemplate.builder("List.of()")
50+
.contextSensitive()
51+
.imports("java.util.List")
52+
.build()
53+
.apply(updateCursor(m), m.getCoordinates().replace());
54+
}
55+
56+
return m;
57+
}
58+
});
59+
}
60+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2026 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.*;
20+
import org.openrewrite.java.JavaIsoVisitor;
21+
import org.openrewrite.java.JavaTemplate;
22+
import org.openrewrite.java.MethodMatcher;
23+
import org.openrewrite.java.search.UsesJavaVersion;
24+
import org.openrewrite.java.search.UsesMethod;
25+
import org.openrewrite.java.tree.J;
26+
27+
public class MigrateCollectionsEmptyMap extends Recipe {
28+
private static final MethodMatcher EMPTY_MAP = new MethodMatcher("java.util.Collections emptyMap()");
29+
30+
@Getter
31+
final String displayName = "Prefer `Map.of()`";
32+
33+
@Getter
34+
final String description = "Prefer `Map.of()` instead of using `Collections.emptyMap()` in Java 9 or higher.";
35+
36+
@Override
37+
public TreeVisitor<?, ExecutionContext> getVisitor() {
38+
TreeVisitor<?, ExecutionContext> check = Preconditions.and(new UsesJavaVersion<>(9),
39+
new UsesMethod<>(EMPTY_MAP));
40+
return Preconditions.check(check, new JavaIsoVisitor<ExecutionContext>() {
41+
42+
@Override
43+
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
44+
J.MethodInvocation m = super.visitMethodInvocation(method, ctx);
45+
46+
if (EMPTY_MAP.matches(m)) {
47+
maybeRemoveImport("java.util.Collections");
48+
maybeAddImport("java.util.Map");
49+
return JavaTemplate.builder("Map.of()")
50+
.contextSensitive()
51+
.imports("java.util.Map")
52+
.build()
53+
.apply(updateCursor(m), m.getCoordinates().replace());
54+
}
55+
56+
return m;
57+
}
58+
});
59+
}
60+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2026 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.*;
20+
import org.openrewrite.java.JavaIsoVisitor;
21+
import org.openrewrite.java.JavaTemplate;
22+
import org.openrewrite.java.MethodMatcher;
23+
import org.openrewrite.java.search.UsesJavaVersion;
24+
import org.openrewrite.java.search.UsesMethod;
25+
import org.openrewrite.java.tree.J;
26+
27+
public class MigrateCollectionsEmptySet extends Recipe {
28+
private static final MethodMatcher EMPTY_SET = new MethodMatcher("java.util.Collections emptySet()");
29+
30+
@Getter
31+
final String displayName = "Prefer `Set.of()`";
32+
33+
@Getter
34+
final String description = "Prefer `Set.of()` instead of using `Collections.emptySet()` in Java 9 or higher.";
35+
36+
@Override
37+
public TreeVisitor<?, ExecutionContext> getVisitor() {
38+
TreeVisitor<?, ExecutionContext> check = Preconditions.and(new UsesJavaVersion<>(9),
39+
new UsesMethod<>(EMPTY_SET));
40+
return Preconditions.check(check, new JavaIsoVisitor<ExecutionContext>() {
41+
42+
@Override
43+
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
44+
J.MethodInvocation m = super.visitMethodInvocation(method, ctx);
45+
46+
if (EMPTY_SET.matches(m)) {
47+
maybeRemoveImport("java.util.Collections");
48+
maybeAddImport("java.util.Set");
49+
return JavaTemplate.builder("Set.of()")
50+
.contextSensitive()
51+
.imports("java.util.Set")
52+
.build()
53+
.apply(updateCursor(m), m.getCoordinates().replace());
54+
}
55+
56+
return m;
57+
}
58+
});
59+
}
60+
}

src/main/resources/META-INF/rewrite/examples.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8961,6 +8961,63 @@ examples:
89618961
language: java
89628962
---
89638963
type: specs.openrewrite.org/v1beta/example
8964+
recipeName: org.openrewrite.java.migrate.util.MigrateCollectionsEmptyList
8965+
examples:
8966+
- description: '`MigrateCollectionsEmptyListTest#emptyList`'
8967+
sources:
8968+
- before: |
8969+
import java.util.*;
8970+
8971+
class Test {
8972+
List<String> list = Collections.emptyList();
8973+
}
8974+
after: |
8975+
import java.util.List;
8976+
8977+
class Test {
8978+
List<String> list = List.of();
8979+
}
8980+
language: java
8981+
---
8982+
type: specs.openrewrite.org/v1beta/example
8983+
recipeName: org.openrewrite.java.migrate.util.MigrateCollectionsEmptySet
8984+
examples:
8985+
- description: '`MigrateCollectionsEmptySetTest#emptySet`'
8986+
sources:
8987+
- before: |
8988+
import java.util.*;
8989+
8990+
class Test {
8991+
Set<String> set = Collections.emptySet();
8992+
}
8993+
after: |
8994+
import java.util.Set;
8995+
8996+
class Test {
8997+
Set<String> set = Set.of();
8998+
}
8999+
language: java
9000+
---
9001+
type: specs.openrewrite.org/v1beta/example
9002+
recipeName: org.openrewrite.java.migrate.util.MigrateCollectionsEmptyMap
9003+
examples:
9004+
- description: '`MigrateCollectionsEmptyMapTest#emptyMap`'
9005+
sources:
9006+
- before: |
9007+
import java.util.*;
9008+
9009+
class Test {
9010+
Map<String, String> map = Collections.emptyMap();
9011+
}
9012+
after: |
9013+
import java.util.Map;
9014+
9015+
class Test {
9016+
Map<String, String> map = Map.of();
9017+
}
9018+
language: java
9019+
---
9020+
type: specs.openrewrite.org/v1beta/example
89649021
recipeName: org.openrewrite.java.migrate.util.MigrateCollectionsSingletonList
89659022
examples:
89669023
- description: '`MigrateCollectionsSingletonListTest#singletonList`'

src/main/resources/META-INF/rewrite/java-best-practices.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,7 @@ recipeList:
4848
- org.openrewrite.java.migrate.lang.var.UseVarForGenericMethodInvocations
4949
- org.openrewrite.java.migrate.lang.var.UseVarForPrimitive
5050
# Prefer modern Java collection factories and utilities
51-
- org.openrewrite.java.migrate.util.UseEnumSetOf
52-
- org.openrewrite.java.migrate.util.UseListOf
53-
- org.openrewrite.java.migrate.util.UseMapOf
54-
- org.openrewrite.java.migrate.util.UseSetOf
51+
- org.openrewrite.java.migrate.util.JavaUtilAPIs
5552
# Static analysis: bug prevention
5653
- org.openrewrite.staticanalysis.ReplaceWeekYearWithYear
5754
- org.openrewrite.staticanalysis.RemoveHashCodeCallsFromArrayInstances

src/main/resources/META-INF/rewrite/java-util-apis.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,26 @@ description: Certain java util APIs have been introduced and are favored over pr
2222
preconditions:
2323
- org.openrewrite.Singleton
2424
recipeList:
25+
- org.openrewrite.java.migrate.util.IteratorNext
26+
- org.openrewrite.java.migrate.util.ListFirstAndLast
27+
- org.openrewrite.java.migrate.util.MigrateCollectionsEmptyList
28+
- org.openrewrite.java.migrate.util.MigrateCollectionsEmptyMap
29+
- org.openrewrite.java.migrate.util.MigrateCollectionsEmptySet
2530
- org.openrewrite.java.migrate.util.MigrateCollectionsSingletonList
2631
- org.openrewrite.java.migrate.util.MigrateCollectionsSingletonMap
2732
- org.openrewrite.java.migrate.util.MigrateCollectionsSingletonSet
2833
- org.openrewrite.java.migrate.util.MigrateCollectionsUnmodifiableList
2934
- org.openrewrite.java.migrate.util.MigrateCollectionsUnmodifiableSet
35+
- org.openrewrite.java.migrate.util.MigrateStringReaderToReaderOf
36+
- org.openrewrite.java.migrate.util.OptionalNotEmptyToIsPresent
37+
- org.openrewrite.java.migrate.util.OptionalNotPresentToIsEmpty
38+
- org.openrewrite.java.migrate.util.OptionalStreamRecipe
39+
- org.openrewrite.java.migrate.util.RemoveFinalizerFromZip
40+
- org.openrewrite.java.migrate.util.ReplaceMathRandomWithThreadLocalRandomRecipe
3041
- org.openrewrite.java.migrate.util.ReplaceStreamCollectWithToList
42+
- org.openrewrite.java.migrate.util.StreamFindFirst
43+
- org.openrewrite.java.migrate.util.UseEnumSetOf
44+
- org.openrewrite.java.migrate.util.UseListOf
45+
- org.openrewrite.java.migrate.util.UseLocaleOf
3146
- org.openrewrite.java.migrate.util.UseMapOf
47+
- org.openrewrite.java.migrate.util.UseSetOf

0 commit comments

Comments
 (0)