Skip to content

Commit 28f454c

Browse files
authored
Merge branch 'main' into feature/733-move_superJDK25
2 parents b2161f3 + 5037eac commit 28f454c

File tree

12 files changed

+1695
-106
lines changed

12 files changed

+1695
-106
lines changed

.sdkmanrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# Enable auto-env through the sdkman_auto_env config
22
# Add key=value pairs of SDKs to use below
3-
java=21.0.5-tem
3+
java=21.0.7-tem

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ dependencies {
4646
runtimeOnly("org.openrewrite:rewrite-java-11")
4747
runtimeOnly("org.openrewrite:rewrite-java-17")
4848
runtimeOnly("org.openrewrite:rewrite-java-21")
49+
runtimeOnly("org.openrewrite:rewrite-java-25")
4950

5051
runtimeOnly("tech.picnic.error-prone-support:error-prone-contrib:latest.release:recipes")
5152

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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.jspecify.annotations.Nullable;
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.tree.Expression;
28+
import org.openrewrite.java.tree.J;
29+
import org.openrewrite.staticanalysis.SimplifyDurationCreationUnits;
30+
31+
public class MigrateProcessWaitForDuration extends Recipe {
32+
33+
private static final MethodMatcher PROCESS_WAIT_FOR_MATCHER = new MethodMatcher("java.lang.Process waitFor(long, java.util.concurrent.TimeUnit)");
34+
35+
@Override
36+
public String getDisplayName() {
37+
return "Use `Process#waitFor(Duration)`";
38+
}
39+
40+
@Override
41+
public String getDescription() {
42+
return "Use `Process#waitFor(Duration)` instead of `Process#waitFor(long, TimeUnit)` in Java 25 or higher.";
43+
}
44+
45+
@Override
46+
public TreeVisitor<?, ExecutionContext> getVisitor() {
47+
return Preconditions.check(new UsesJavaVersion<>(25), new JavaVisitor<ExecutionContext>() {
48+
@Override
49+
public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
50+
J.MethodInvocation mi = (J.MethodInvocation) super.visitMethodInvocation(method, ctx);
51+
52+
if (PROCESS_WAIT_FOR_MATCHER.matches(mi)) {
53+
Expression valueArg = mi.getArguments().get(0);
54+
Expression unitArg = mi.getArguments().get(1);
55+
String timeUnitName = getTimeUnitName(unitArg);
56+
String durationMethod = getDurationMethod(timeUnitName);
57+
58+
boolean isSimpleValue = valueArg instanceof J.Literal || valueArg instanceof J.Identifier;
59+
60+
maybeRemoveImport("java.util.concurrent.TimeUnit");
61+
maybeRemoveImport("java.util.concurrent.TimeUnit." + timeUnitName);
62+
maybeAddImport("java.time.Duration");
63+
maybeAddImport("java.time.temporal.ChronoUnit");
64+
65+
doAfterVisit(new SimplifyDurationCreationUnits().getVisitor());
66+
67+
if (isSimpleValue && "MICROSECONDS".equals(timeUnitName)) {
68+
return JavaTemplate.builder("Duration.of(#{any(long)}, ChronoUnit.MICROS)")
69+
.imports("java.time.Duration", "java.time.temporal.ChronoUnit")
70+
.build()
71+
.apply(getCursor(), mi.getCoordinates().replaceArguments(), valueArg);
72+
}
73+
if (isSimpleValue && durationMethod != null) {
74+
return JavaTemplate.builder("Duration." + durationMethod + "(#{any(long)})")
75+
.imports("java.time.Duration")
76+
.build()
77+
.apply(getCursor(), mi.getCoordinates().replaceArguments(), valueArg);
78+
}
79+
return JavaTemplate.builder("Duration.of(#{any(long)}, #{any(java.util.concurrent.TimeUnit)}.toChronoUnit())")
80+
.imports("java.time.Duration")
81+
.build()
82+
.apply(getCursor(), mi.getCoordinates().replaceArguments(), valueArg, unitArg);
83+
}
84+
return mi;
85+
}
86+
87+
private @Nullable String getTimeUnitName(Expression timeUnitArg) {
88+
if (timeUnitArg instanceof J.FieldAccess) {
89+
J.FieldAccess fa = (J.FieldAccess) timeUnitArg;
90+
return fa.getSimpleName();
91+
}
92+
if (timeUnitArg instanceof J.Identifier) {
93+
J.Identifier id = (J.Identifier) timeUnitArg;
94+
return id.getSimpleName();
95+
}
96+
return null;
97+
}
98+
99+
private @Nullable String getDurationMethod(@Nullable String timeUnitName) {
100+
if (timeUnitName == null) {
101+
return null;
102+
}
103+
switch (timeUnitName) {
104+
case "NANOSECONDS":
105+
return "ofNanos";
106+
case "MILLISECONDS":
107+
return "ofMillis";
108+
case "SECONDS":
109+
return "ofSeconds";
110+
case "MINUTES":
111+
return "ofMinutes";
112+
case "HOURS":
113+
return "ofHours";
114+
case "DAYS":
115+
return "ofDays";
116+
default:
117+
return null;
118+
}
119+
}
120+
});
121+
}
122+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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 lombok.EqualsAndHashCode;
19+
import lombok.Value;
20+
import org.openrewrite.ExecutionContext;
21+
import org.openrewrite.Preconditions;
22+
import org.openrewrite.Recipe;
23+
import org.openrewrite.TreeVisitor;
24+
import org.openrewrite.java.JavaIsoVisitor;
25+
import org.openrewrite.java.RenameVariable;
26+
import org.openrewrite.java.search.SemanticallyEqual;
27+
import org.openrewrite.java.search.UsesJavaVersion;
28+
import org.openrewrite.java.tree.J;
29+
import org.openrewrite.java.tree.Statement;
30+
import org.openrewrite.staticanalysis.VariableReferences;
31+
32+
import java.util.concurrent.atomic.AtomicBoolean;
33+
34+
@EqualsAndHashCode(callSuper = false)
35+
@Value
36+
public class ReplaceUnusedVariablesWithUnderscore extends Recipe {
37+
38+
private static final String UNDERSCORE = "_";
39+
40+
@Override
41+
public String getDisplayName() {
42+
return "Replace unused variables with underscore";
43+
}
44+
45+
@Override
46+
public String getDescription() {
47+
return "Replace unused variable declarations with underscore (_) for Java 22+. " +
48+
"This includes unused variables in enhanced for loops, catch blocks, " +
49+
"and lambda parameters where the variable is never referenced.";
50+
}
51+
52+
@Override
53+
public TreeVisitor<?, ExecutionContext> getVisitor() {
54+
return Preconditions.check(new UsesJavaVersion<>(25), new JavaIsoVisitor<ExecutionContext>() {
55+
@Override
56+
public J.ForEachLoop visitForEachLoop(J.ForEachLoop forLoop, ExecutionContext ctx) {
57+
J.ForEachLoop l = super.visitForEachLoop(forLoop, ctx);
58+
Statement variable = l.getControl().getVariable();
59+
if (variable instanceof J.VariableDeclarations) {
60+
for (J.VariableDeclarations.NamedVariable namedVariable : ((J.VariableDeclarations) variable).getVariables()) {
61+
renameVariableIfUnusedInContext(namedVariable, l.getBody());
62+
}
63+
}
64+
return l;
65+
}
66+
67+
@Override
68+
public J.Try.Catch visitCatch(J.Try.Catch _catch, ExecutionContext ctx) {
69+
J.Try.Catch c = super.visitCatch(_catch, ctx);
70+
for (J.VariableDeclarations.NamedVariable namedVariable : c.getParameter().getTree().getVariables()) {
71+
renameVariableIfUnusedInContext(namedVariable, c.getBody());
72+
}
73+
return c;
74+
}
75+
76+
@Override
77+
public J.Lambda visitLambda(J.Lambda lambda, ExecutionContext ctx) {
78+
J.Lambda l = super.visitLambda(lambda, ctx);
79+
for (J param : l.getParameters().getParameters()) {
80+
if (param instanceof J.VariableDeclarations) {
81+
for (J.VariableDeclarations.NamedVariable namedVariable : ((J.VariableDeclarations) param).getVariables()) {
82+
renameVariableIfUnusedInContext(namedVariable, l.getBody());
83+
}
84+
}
85+
}
86+
return l;
87+
}
88+
89+
private void renameVariableIfUnusedInContext(J.VariableDeclarations.NamedVariable variable, J context) {
90+
if (!UNDERSCORE.equals(variable.getName().getSimpleName()) &&
91+
VariableReferences.findRhsReferences(context, variable.getName()).isEmpty() &&
92+
!usedInModifyingUnary(variable.getName(), context)) {
93+
doAfterVisit(new RenameVariable<>(variable, UNDERSCORE));
94+
}
95+
}
96+
97+
private boolean usedInModifyingUnary(J.Identifier identifier, J context) {
98+
return new JavaIsoVisitor<AtomicBoolean>() {
99+
@Override
100+
public J.Unary visitUnary(J.Unary unary, AtomicBoolean atomicBoolean) {
101+
if (unary.getOperator().isModifying() &&
102+
SemanticallyEqual.areEqual(identifier, unary.getExpression())) {
103+
atomicBoolean.set(true);
104+
}
105+
return super.visitUnary(unary, atomicBoolean);
106+
}
107+
}.reduce(context, new AtomicBoolean(false)).get();
108+
}
109+
});
110+
}
111+
}

0 commit comments

Comments
 (0)