Skip to content

Commit a885789

Browse files
authored
Merge branch 'main' into run-ci-on-java-25
2 parents 7350d5f + 72818ad commit a885789

File tree

6 files changed

+767
-0
lines changed

6 files changed

+767
-0
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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.io;
17+
18+
import org.openrewrite.ExecutionContext;
19+
import org.openrewrite.Preconditions;
20+
import org.openrewrite.Recipe;
21+
import org.openrewrite.TreeVisitor;
22+
import org.openrewrite.java.JavaIsoVisitor;
23+
import org.openrewrite.java.JavaTemplate;
24+
import org.openrewrite.java.MethodMatcher;
25+
import org.openrewrite.java.search.UsesMethod;
26+
import org.openrewrite.java.tree.Expression;
27+
import org.openrewrite.java.tree.J;
28+
import org.openrewrite.java.tree.TypeUtils;
29+
30+
public class ReplaceSystemOutWithIOPrint extends Recipe {
31+
32+
@Override
33+
public String getDisplayName() {
34+
return "Migrate `System.out.print` to Java 25 IO utility class";
35+
}
36+
37+
@Override
38+
public String getDescription() {
39+
return "Replace `System.out.print()`, `System.out.println()` with `IO.print()` and `IO.println()`. " +
40+
"Migrates to the new IO utility class introduced in Java 25.";
41+
}
42+
43+
private static final MethodMatcher SYSTEM_OUT_PRINT = new MethodMatcher("java.io.PrintStream print(..)");
44+
private static final MethodMatcher SYSTEM_OUT_PRINTLN = new MethodMatcher("java.io.PrintStream println(..)");
45+
46+
@Override
47+
public TreeVisitor<?, ExecutionContext> getVisitor() {
48+
return Preconditions.check(
49+
Preconditions.or(
50+
new UsesMethod<>(SYSTEM_OUT_PRINT),
51+
new UsesMethod<>(SYSTEM_OUT_PRINTLN)
52+
),
53+
new JavaIsoVisitor<ExecutionContext>() {
54+
@Override
55+
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
56+
J.MethodInvocation m = super.visitMethodInvocation(method, ctx);
57+
if (!isSystemOutMethod(m)) {
58+
return m;
59+
}
60+
String methodName = m.getName().getSimpleName();
61+
return m.getArguments().isEmpty() ?
62+
JavaTemplate.builder("IO.#{}()").build()
63+
.apply(getCursor(), m.getCoordinates().replace(), methodName) :
64+
JavaTemplate.builder("IO.#{}(#{any()})").build()
65+
.apply(getCursor(), m.getCoordinates().replace(), methodName, m.getArguments().get(0));
66+
}
67+
68+
private boolean isSystemOutMethod(J.MethodInvocation mi) {
69+
if (SYSTEM_OUT_PRINT.matches(mi) || SYSTEM_OUT_PRINTLN.matches(mi)) {
70+
Expression expression = mi.getSelect();
71+
if (expression instanceof J.FieldAccess) {
72+
return isSystemOut(((J.FieldAccess) expression).getName());
73+
}
74+
if (expression instanceof J.Identifier) {
75+
maybeRemoveImport("java.lang.System.out");
76+
return isSystemOut((J.Identifier) expression);
77+
}
78+
}
79+
return false;
80+
}
81+
82+
private boolean isSystemOut(J.Identifier identifier) {
83+
return "out".equals(identifier.getSimpleName()) &&
84+
identifier.getFieldType() != null &&
85+
TypeUtils.isAssignableTo("java.lang.System", identifier.getFieldType().getOwner());
86+
}
87+
});
88+
}
89+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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.ExecutionContext;
19+
import org.openrewrite.Preconditions;
20+
import org.openrewrite.Recipe;
21+
import org.openrewrite.TreeVisitor;
22+
import org.openrewrite.java.JavaIsoVisitor;
23+
import org.openrewrite.java.search.UsesJavaVersion;
24+
import org.openrewrite.java.tree.J;
25+
import org.openrewrite.java.tree.JavaType;
26+
import org.openrewrite.java.tree.TypeUtils;
27+
import org.openrewrite.staticanalysis.VariableReferences;
28+
29+
import static java.util.Collections.emptyList;
30+
31+
public class MigrateMainMethodToInstanceMain extends Recipe {
32+
@Override
33+
public String getDisplayName() {
34+
return "Migrate `public static void main(String[] args)` to instance `void main()`";
35+
}
36+
37+
@Override
38+
public String getDescription() {
39+
return "Migrate `public static void main(String[] args)` method to instance `void main()` method when the `args` parameter is unused, as supported by JEP 512 in Java 25+.";
40+
}
41+
42+
@Override
43+
public TreeVisitor<?, ExecutionContext> getVisitor() {
44+
return Preconditions.check(new UsesJavaVersion<>(25), new JavaIsoVisitor<ExecutionContext>() {
45+
@Override
46+
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) {
47+
J.MethodDeclaration md = super.visitMethodDeclaration(method, ctx);
48+
49+
// Check if this is a main method: public static void main(String[] args)
50+
if (!"main".equals(md.getSimpleName()) ||
51+
md.getReturnTypeExpression() == null ||
52+
md.getReturnTypeExpression().getType() != JavaType.Primitive.Void ||
53+
!md.hasModifier(J.Modifier.Type.Public) ||
54+
!md.hasModifier(J.Modifier.Type.Static) ||
55+
md.getParameters().size() != 1 ||
56+
!(md.getParameters().get(0) instanceof J.VariableDeclarations) ||
57+
md.getBody() == null) {
58+
return md;
59+
}
60+
61+
// Check if parameter is String[] type
62+
J.VariableDeclarations param = (J.VariableDeclarations) md.getParameters().get(0);
63+
JavaType paramType = param.getType();
64+
if (!TypeUtils.isOfClassType(paramType, "java.lang.String") || !(paramType instanceof JavaType.Array)) {
65+
return md;
66+
}
67+
68+
// Remove the parameter if unused
69+
J.Identifier variableName = param.getVariables().get(0).getName();
70+
if (VariableReferences.findRhsReferences(md.getBody(), variableName).isEmpty()) {
71+
md = md.withParameters(emptyList());
72+
}
73+
return md.withReturnTypeExpression(md.getReturnTypeExpression().withPrefix(md.getModifiers().get(0).getPrefix()))
74+
.withModifiers(emptyList());
75+
}
76+
});
77+
}
78+
79+
}

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3896,6 +3896,25 @@ examples:
38963896
language: java
38973897
---
38983898
type: specs.openrewrite.org/v1beta/example
3899+
recipeName: org.openrewrite.java.migrate.io.ReplaceSystemOutWithIOPrint
3900+
examples:
3901+
- description: ''
3902+
sources:
3903+
- before: |
3904+
class Example {
3905+
void test() {
3906+
System.out.print("Hello");
3907+
}
3908+
}
3909+
after: |
3910+
class Example {
3911+
void test() {
3912+
IO.print("Hello");
3913+
}
3914+
}
3915+
language: java
3916+
---
3917+
type: specs.openrewrite.org/v1beta/example
38993918
recipeName: org.openrewrite.java.migrate.jacoco.UpgradeJaCoCo
39003919
examples:
39013920
- description: ''
@@ -7479,6 +7498,30 @@ examples:
74797498
language: java
74807499
---
74817500
type: specs.openrewrite.org/v1beta/example
7501+
recipeName: org.openrewrite.java.migrate.util.MigrateStringReaderToReaderOf
7502+
examples:
7503+
- description: ''
7504+
sources:
7505+
- before: |
7506+
import java.io.Reader;
7507+
import java.io.StringReader;
7508+
7509+
class Test {
7510+
void test(String content) {
7511+
Reader reader = new StringReader(content);
7512+
}
7513+
}
7514+
after: |
7515+
import java.io.Reader;
7516+
7517+
class Test {
7518+
void test(String content) {
7519+
Reader reader = Reader.of(content);
7520+
}
7521+
}
7522+
language: java
7523+
---
7524+
type: specs.openrewrite.org/v1beta/example
74827525
recipeName: org.openrewrite.java.migrate.util.OptionalStreamRecipe
74837526
examples:
74847527
- description: ''

src/main/resources/META-INF/rewrite/java-version-25.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ recipeList:
3030
version: 25
3131
- org.openrewrite.github.SetupJavaUpgradeJavaVersion:
3232
minimumJavaMajorVersion: 25
33+
- org.openrewrite.java.migrate.io.ReplaceSystemOutWithIOPrint
34+
- org.openrewrite.java.migrate.lang.MigrateMainMethodToInstanceMain
3335
- org.openrewrite.java.migrate.lang.MigrateProcessWaitForDuration
3436
- org.openrewrite.java.migrate.lang.ReplaceUnusedVariablesWithUnderscore
3537
- org.openrewrite.java.migrate.util.MigrateInflaterDeflaterToClose

0 commit comments

Comments
 (0)