Skip to content

Commit bd73da6

Browse files
committed
Replace Collections.emptyXXX with Immutable Static Factory Methods
Closes #1044
1 parent 9e138a7 commit bd73da6

9 files changed

Lines changed: 527 additions & 18 deletions

File tree

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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.MethodMatcher;
22+
import org.openrewrite.java.tree.J;
23+
import org.openrewrite.java.tree.JavaType;
24+
import org.openrewrite.java.tree.JavaType.ShallowClass;
25+
import org.openrewrite.java.tree.Space;
26+
27+
import static java.util.Collections.emptyList;
28+
29+
public class MigrateCollectionsEmptyList extends Recipe {
30+
private static final MethodMatcher EMPTY_LIST = new MethodMatcher("java.util.Collections emptyList()");
31+
32+
@Getter
33+
final String displayName = "Prefer `List.of()`";
34+
35+
@Getter
36+
final String description = "Prefer `List.of()` instead of using `Collections.emptyList()` in Java 9 or higher.";
37+
38+
@Override
39+
public JavaIsoVisitor<ExecutionContext> getVisitor() {
40+
return 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+
50+
JavaType.Class classType = ShallowClass.build("java.util.List");
51+
JavaType.Method methodType = m.getMethodType().withName("of").withDeclaringType(classType);
52+
m = m.withName(m.getName().withSimpleName("of").withType(methodType));
53+
if (m.getSelect() instanceof J.Identifier) {
54+
return m.withSelect(((J.Identifier) m.getSelect()).withSimpleName("List").withType(classType));
55+
}
56+
return m.withSelect(new J.Identifier(
57+
Tree.randomId(), m.getPrefix(), m.getMarkers(), emptyList(), "List", classType, null))
58+
.withPrefix(Space.EMPTY);
59+
}
60+
61+
return m;
62+
}
63+
};
64+
}
65+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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.MethodMatcher;
22+
import org.openrewrite.java.tree.J;
23+
import org.openrewrite.java.tree.JavaType;
24+
import org.openrewrite.java.tree.JavaType.ShallowClass;
25+
import org.openrewrite.java.tree.Space;
26+
27+
import static java.util.Collections.emptyList;
28+
29+
public class MigrateCollectionsEmptyMap extends Recipe {
30+
private static final MethodMatcher EMPTY_MAP = new MethodMatcher("java.util.Collections emptyMap()");
31+
32+
@Getter
33+
final String displayName = "Prefer `Map.of()`";
34+
35+
@Getter
36+
final String description = "Prefer `Map.of()` instead of using `Collections.emptyMap()` in Java 9 or higher.";
37+
38+
@Override
39+
public JavaIsoVisitor<ExecutionContext> getVisitor() {
40+
return 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+
50+
JavaType.Class classType = ShallowClass.build("java.util.Map");
51+
JavaType.Method methodType = m.getMethodType().withName("of").withDeclaringType(classType);
52+
m = m.withName(m.getName().withSimpleName("of").withType(methodType));
53+
if (m.getSelect() instanceof J.Identifier) {
54+
return m.withSelect(((J.Identifier) m.getSelect()).withSimpleName("Map").withType(classType));
55+
}
56+
return m.withSelect(new J.Identifier(
57+
Tree.randomId(), m.getPrefix(), m.getMarkers(), emptyList(), "Map", classType, null))
58+
.withPrefix(Space.EMPTY);
59+
}
60+
61+
return m;
62+
}
63+
};
64+
}
65+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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.MethodMatcher;
22+
import org.openrewrite.java.tree.J;
23+
import org.openrewrite.java.tree.JavaType;
24+
import org.openrewrite.java.tree.JavaType.ShallowClass;
25+
import org.openrewrite.java.tree.Space;
26+
27+
import static java.util.Collections.emptyList;
28+
29+
public class MigrateCollectionsEmptySet extends Recipe {
30+
private static final MethodMatcher EMPTY_SET = new MethodMatcher("java.util.Collections emptySet()");
31+
32+
@Getter
33+
final String displayName = "Prefer `Set.of()`";
34+
35+
@Getter
36+
final String description = "Prefer `Set.of()` instead of using `Collections.emptySet()` in Java 9 or higher.";
37+
38+
@Override
39+
public JavaIsoVisitor<ExecutionContext> getVisitor() {
40+
return 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+
50+
JavaType.Class classType = ShallowClass.build("java.util.Set");
51+
JavaType.Method methodType = m.getMethodType().withName("of").withDeclaringType(classType);
52+
m = m.withName(m.getName().withSimpleName("of").withType(methodType));
53+
if (m.getSelect() instanceof J.Identifier) {
54+
return m.withSelect(((J.Identifier) m.getSelect()).withSimpleName("Set").withType(classType));
55+
}
56+
return m.withSelect(new J.Identifier(
57+
Tree.randomId(), m.getPrefix(), m.getMarkers(), emptyList(), "Set", classType, null))
58+
.withPrefix(Space.EMPTY);
59+
}
60+
61+
return m;
62+
}
63+
};
64+
}
65+
}

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

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8809,6 +8809,63 @@ examples:
88098809
language: java
88108810
---
88118811
type: specs.openrewrite.org/v1beta/example
8812+
recipeName: org.openrewrite.java.migrate.util.MigrateCollectionsEmptyList
8813+
examples:
8814+
- description: '`MigrateCollectionsEmptyListTest#emptyList`'
8815+
sources:
8816+
- before: |
8817+
import java.util.*;
8818+
8819+
class Test {
8820+
List<String> list = Collections.emptyList();
8821+
}
8822+
after: |
8823+
import java.util.List;
8824+
8825+
class Test {
8826+
List<String> list = List.of();
8827+
}
8828+
language: java
8829+
---
8830+
type: specs.openrewrite.org/v1beta/example
8831+
recipeName: org.openrewrite.java.migrate.util.MigrateCollectionsEmptySet
8832+
examples:
8833+
- description: '`MigrateCollectionsEmptySetTest#emptySet`'
8834+
sources:
8835+
- before: |
8836+
import java.util.*;
8837+
8838+
class Test {
8839+
Set<String> set = Collections.emptySet();
8840+
}
8841+
after: |
8842+
import java.util.Set;
8843+
8844+
class Test {
8845+
Set<String> set = Set.of();
8846+
}
8847+
language: java
8848+
---
8849+
type: specs.openrewrite.org/v1beta/example
8850+
recipeName: org.openrewrite.java.migrate.util.MigrateCollectionsEmptyMap
8851+
examples:
8852+
- description: '`MigrateCollectionsEmptyMapTest#emptyMap`'
8853+
sources:
8854+
- before: |
8855+
import java.util.*;
8856+
8857+
class Test {
8858+
Map<String, String> map = Collections.emptyMap();
8859+
}
8860+
after: |
8861+
import java.util.Map;
8862+
8863+
class Test {
8864+
Map<String, String> map = Map.of();
8865+
}
8866+
language: java
8867+
---
8868+
type: specs.openrewrite.org/v1beta/example
88128869
recipeName: org.openrewrite.java.migrate.util.MigrateCollectionsSingletonList
88138870
examples:
88148871
- description: '`MigrateCollectionsSingletonListTest#singletonList`'

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ 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.MigrateCollectionsEmptyList
26+
- org.openrewrite.java.migrate.util.MigrateCollectionsEmptyMap
27+
- org.openrewrite.java.migrate.util.MigrateCollectionsEmptySet
2528
- org.openrewrite.java.migrate.util.MigrateCollectionsSingletonList
2629
- org.openrewrite.java.migrate.util.MigrateCollectionsSingletonMap
2730
- org.openrewrite.java.migrate.util.MigrateCollectionsSingletonSet

0 commit comments

Comments
 (0)