Skip to content

Commit a579d9b

Browse files
committed
refactor: extract helper method in util class
1 parent a2b5986 commit a579d9b

File tree

3 files changed

+212
-31
lines changed

3 files changed

+212
-31
lines changed

src/main/java/org/openrewrite/java/migrate/lang/NullCheckAsSwitchCase.java

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@
3131
import java.time.Duration;
3232
import java.util.ArrayList;
3333
import java.util.List;
34-
import java.util.Objects;
3534
import java.util.Optional;
3635
import java.util.concurrent.atomic.AtomicReference;
3736

3837
import static java.util.Objects.requireNonNull;
3938
import static org.openrewrite.java.migrate.lang.NullCheck.Matcher.nullCheck;
39+
import static org.openrewrite.java.migrate.lang.SwitchUtils.coversAllPossibleValues;
4040

4141
@EqualsAndHashCode(callSuper = false)
4242
@Value
@@ -184,36 +184,6 @@ private J.Case createCaseStatement(J.Switch aSwitch, Statement whenNull, J.Case
184184

185185
return nullCase.withStatements(ListUtils.mapFirst(nullCase.getStatements(), s -> s == null ? null : s.withPrefix(currentFirstCaseIndentation)));
186186
}
187-
188-
private boolean coversAllPossibleValues(J.Switch switch_) {
189-
List<J> labels = new ArrayList<>();
190-
for (Statement statement : switch_.getCases().getStatements()) {
191-
for (J j : ((J.Case) statement).getCaseLabels()) {
192-
if (j instanceof J.Identifier && "default".equals(((J.Identifier) j).getSimpleName())) {
193-
return true;
194-
}
195-
labels.add(j);
196-
}
197-
}
198-
JavaType javaType = switch_.getSelector().getTree().getType();
199-
if (javaType instanceof JavaType.Class && ((JavaType.Class) javaType).getKind() == JavaType.FullyQualified.Kind.Enum) {
200-
// Every enum value must be present in the switch
201-
return ((JavaType.Class) javaType).getMembers().stream().allMatch(variable ->
202-
labels.stream().anyMatch(label -> {
203-
if (!(label instanceof TypeTree && TypeUtils.isOfType(((TypeTree) label).getType(), javaType))) {
204-
return false;
205-
}
206-
J.Identifier enumName = null;
207-
if (label instanceof J.Identifier) {
208-
enumName = (J.Identifier) label;
209-
} else if (label instanceof J.FieldAccess) {
210-
enumName = ((J.FieldAccess) label).getName();
211-
}
212-
return enumName != null && Objects.equals(variable.getName(), enumName.getSimpleName());
213-
}));
214-
}
215-
return false;
216-
}
217187
});
218188
}
219189
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2025 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.lang;
17+
18+
import org.openrewrite.java.tree.*;
19+
20+
import java.util.ArrayList;
21+
import java.util.List;
22+
import java.util.Objects;
23+
24+
public class SwitchUtils {
25+
/**
26+
* Checks if a switch statement covers all possible values of its selector.
27+
* This is typically used to determine if a switch statement is "exhaustive" as per the Java language specification.
28+
*
29+
* NOTE: Missing support for sealed classes/interfaces.
30+
*
31+
* @See <a href="https://docs.oracle.com/en/java/javase/21/language/switch-expressions-and-statements.html">Switch Expressions in Java 21</a>
32+
*
33+
* @param switch_ the switch statement to check
34+
* @return true if the switch covers all possible values, false otherwise
35+
*/
36+
public static boolean coversAllPossibleValues(J.Switch switch_) {
37+
List<J> labels = new ArrayList<>();
38+
for (Statement statement : switch_.getCases().getStatements()) {
39+
for (J j : ((J.Case) statement).getCaseLabels()) {
40+
if (j instanceof J.Identifier && "default".equals(((J.Identifier) j).getSimpleName())) {
41+
return true;
42+
}
43+
labels.add(j);
44+
}
45+
}
46+
JavaType javaType = switch_.getSelector().getTree().getType();
47+
if (javaType instanceof JavaType.Class && ((JavaType.Class) javaType).getKind() == JavaType.FullyQualified.Kind.Enum) {
48+
// Every enum value must be present in the switch
49+
return ((JavaType.Class) javaType).getMembers().stream().allMatch(variable ->
50+
labels.stream().anyMatch(label -> {
51+
if (!(label instanceof TypeTree && TypeUtils.isOfType(((TypeTree) label).getType(), javaType))) {
52+
return false;
53+
}
54+
J.Identifier enumName = null;
55+
if (label instanceof J.Identifier) {
56+
enumName = (J.Identifier) label;
57+
} else if (label instanceof J.FieldAccess) {
58+
enumName = ((J.FieldAccess) label).getName();
59+
}
60+
return enumName != null && Objects.equals(variable.getName(), enumName.getSimpleName());
61+
}));
62+
}
63+
return false;
64+
}
65+
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* Copyright 2025 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.lang;
17+
18+
import org.intellij.lang.annotations.Language;
19+
import org.junit.jupiter.api.Disabled;
20+
import org.junit.jupiter.api.Test;
21+
import org.openrewrite.ExecutionContext;
22+
import org.openrewrite.InMemoryExecutionContext;
23+
import org.openrewrite.TreeVisitor;
24+
import org.openrewrite.java.JavaParser;
25+
import org.openrewrite.java.tree.J;
26+
27+
import java.util.concurrent.atomic.AtomicReference;
28+
29+
import static org.junit.jupiter.api.Assertions.assertFalse;
30+
import static org.junit.jupiter.api.Assertions.assertTrue;
31+
32+
class SwitchUtilsTest {
33+
private static J.Switch getSwitchElement(@Language("java") String code) {
34+
JavaParser parser = JavaParser.fromJavaVersion().build();
35+
J.CompilationUnit cu = (J.CompilationUnit) parser.parse(code).findFirst().get();
36+
37+
AtomicReference<J.Switch> foundSwitch = new AtomicReference<>();
38+
new TreeVisitor<J, ExecutionContext>() {
39+
@Override
40+
public J preVisit(J tree, ExecutionContext executionContext) {
41+
if (foundSwitch.get() != null) {
42+
return tree;
43+
}
44+
if (tree instanceof J.Switch sw) {
45+
foundSwitch.set(sw);
46+
}
47+
return super.preVisit(tree, executionContext);
48+
}
49+
50+
51+
}.visit(cu, new InMemoryExecutionContext());
52+
53+
return foundSwitch.get();
54+
}
55+
56+
@Test
57+
void coversAllCasesAllEnums() {
58+
assertTrue(SwitchUtils.coversAllPossibleValues(getSwitchElement("""
59+
public class Test {
60+
void method(TrafficLight light) {
61+
switch (light) {
62+
case RED -> System.out.println("stop");
63+
case YELLOW -> System.out.println("caution");
64+
case GREEN -> System.out.println("go");
65+
}
66+
}
67+
enum TrafficLight { RED, YELLOW, GREEN }
68+
}
69+
""")));
70+
}
71+
72+
@Test
73+
void coversAllCasesMissingEnums() {
74+
assertFalse(SwitchUtils.coversAllPossibleValues(getSwitchElement("""
75+
public class Test {
76+
void method(TrafficLight light) {
77+
switch (light) {
78+
case RED -> System.out.println("stop");
79+
case YELLOW -> System.out.println("caution");
80+
}
81+
}
82+
enum TrafficLight { RED, YELLOW, GREEN }
83+
}
84+
""")));
85+
}
86+
87+
@Test
88+
void coversAllCasesMissingEnumsWithDefault() {
89+
assertTrue(SwitchUtils.coversAllPossibleValues(getSwitchElement("""
90+
public class Test {
91+
void method(TrafficLight light) {
92+
switch (light) {
93+
case RED -> System.out.println("stop");
94+
case YELLOW -> System.out.println("caution");
95+
default -> System.out.println("unknown");
96+
}
97+
}
98+
enum TrafficLight { RED, YELLOW, GREEN }
99+
}
100+
""")));
101+
}
102+
103+
@Test
104+
void coversAllCasesEnumOnlyDefault() {
105+
assertTrue(SwitchUtils.coversAllPossibleValues(getSwitchElement("""
106+
public class Test {
107+
void method(TrafficLight light) {
108+
switch (light) {
109+
default -> System.out.println("unknown");
110+
}
111+
}
112+
enum TrafficLight { RED, YELLOW, GREEN }
113+
}
114+
""")));
115+
}
116+
117+
@Test
118+
void coversAllCasesObjectOnlyDefault() {
119+
assertTrue(SwitchUtils.coversAllPossibleValues(getSwitchElement("""
120+
public class Test {
121+
void method(Object obj) {
122+
switch (obj) {
123+
default -> System.out.println("default");
124+
}
125+
}
126+
}
127+
""")));
128+
}
129+
130+
@Test
131+
@Disabled("Unsupported yet")
132+
void coversAllCasesAllSealedClasses() {
133+
assertTrue(SwitchUtils.coversAllPossibleValues(getSwitchElement("""
134+
public class Test {
135+
sealed abstract class Shape permits Circle, Square, Rectangle {}
136+
void method(Shape shape) {
137+
switch (shape) {
138+
case Circle c -> System.out.println("circle");
139+
case Square s -> System.out.println("square");
140+
case Rectangle r -> System.out.println("rectangle");
141+
}
142+
}
143+
}
144+
""")));
145+
}
146+
}

0 commit comments

Comments
 (0)