diff --git a/src/main/java/org/openrewrite/java/migrate/io/ReplaceSystemOutWithIOPrint.java b/src/main/java/org/openrewrite/java/migrate/io/ReplaceSystemOutWithIOPrint.java new file mode 100644 index 0000000000..d4c7a28d54 --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/io/ReplaceSystemOutWithIOPrint.java @@ -0,0 +1,89 @@ +/* + * Copyright 2025 the original author or authors. + *
+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *
+ * https://docs.moderne.io/licensing/moderne-source-available-license + *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.openrewrite.java.migrate.io;
+
+import org.openrewrite.ExecutionContext;
+import org.openrewrite.Preconditions;
+import org.openrewrite.Recipe;
+import org.openrewrite.TreeVisitor;
+import org.openrewrite.java.JavaIsoVisitor;
+import org.openrewrite.java.JavaTemplate;
+import org.openrewrite.java.MethodMatcher;
+import org.openrewrite.java.search.UsesMethod;
+import org.openrewrite.java.tree.Expression;
+import org.openrewrite.java.tree.J;
+import org.openrewrite.java.tree.TypeUtils;
+
+public class ReplaceSystemOutWithIOPrint extends Recipe {
+
+ @Override
+ public String getDisplayName() {
+ return "Migrate `System.out.print` to Java 25 IO utility class";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Replace `System.out.print()`, `System.out.println()` with `IO.print()` and `IO.println()`. " +
+ "Migrates to the new IO utility class introduced in Java 25.";
+ }
+
+ private static final MethodMatcher SYSTEM_OUT_PRINT = new MethodMatcher("java.io.PrintStream print(..)");
+ private static final MethodMatcher SYSTEM_OUT_PRINTLN = new MethodMatcher("java.io.PrintStream println(..)");
+
+ @Override
+ public TreeVisitor, ExecutionContext> getVisitor() {
+ return Preconditions.check(
+ Preconditions.or(
+ new UsesMethod<>(SYSTEM_OUT_PRINT),
+ new UsesMethod<>(SYSTEM_OUT_PRINTLN)
+ ),
+ new JavaIsoVisitor
+ * Licensed under the Moderne Source Available License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://docs.moderne.io/licensing/moderne-source-available-license
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.openrewrite.java.migrate.io;
+
+import org.junit.jupiter.api.Test;
+import org.openrewrite.DocumentExample;
+import org.openrewrite.java.JavaParser;
+import org.openrewrite.java.search.FindMissingTypes;
+import org.openrewrite.test.RecipeSpec;
+import org.openrewrite.test.RewriteTest;
+import org.openrewrite.test.TypeValidation;
+
+import static org.openrewrite.java.Assertions.java;
+import static org.openrewrite.java.Assertions.javaVersion;
+
+class ReplaceSystemOutWithIOPrintTest implements RewriteTest {
+
+ @Override
+ public void defaults(RecipeSpec spec) {
+ spec.recipe(new ReplaceSystemOutWithIOPrint())
+ .afterTypeValidationOptions(TypeValidation.all().allowMissingType(o -> {
+ assert o instanceof FindMissingTypes.MissingTypeResult;
+ FindMissingTypes.MissingTypeResult result = (FindMissingTypes.MissingTypeResult) o;
+ return result.getPrintedTree().contains("IO");
+ })) // TODO remove once tests run on Java 25+
+ .parser(JavaParser.fromJavaVersion())
+ .allSources(s -> s.markers(javaVersion(25)));
+ }
+
+ @DocumentExample
+ @Test
+ void replaceSystemOutPrint() {
+ rewriteRun(
+ java(
+ """
+ class Example {
+ void test() {
+ System.out.print("Hello");
+ }
+ }
+ """,
+ """
+ class Example {
+ void test() {
+ IO.print("Hello");
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void replaceSystemOutPrintln() {
+ rewriteRun(
+ java(
+ """
+ class Example {
+ void test() {
+ System.out.println("Hello");
+ }
+ }
+ """,
+ """
+ class Example {
+ void test() {
+ IO.println("Hello");
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void replaceSystemOutPrintlnWithStaticImport() {
+ rewriteRun(
+ java(
+ """
+ import static java.lang.System.out;
+
+ class Example {
+ void test() {
+ out.println("Hello");
+ }
+ }
+ """,
+ """
+ class Example {
+ void test() {
+ IO.println("Hello");
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void replaceSystemOutPrintWithVariable() {
+ rewriteRun(
+ java(
+ """
+ class Example {
+ void test() {
+ String message = "Hello World";
+ System.out.print(message);
+ }
+ }
+ """,
+ """
+ class Example {
+ void test() {
+ String message = "Hello World";
+ IO.print(message);
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void replaceSystemOutPrintlnEmpty() {
+ rewriteRun(
+ java(
+ """
+ class Example {
+ void test() {
+ System.out.println();
+ }
+ }
+ """,
+ """
+ class Example {
+ void test() {
+ IO.println();
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void replaceMultipleSystemOutCalls() {
+ rewriteRun(
+ java(
+ """
+ class Example {
+ void test() {
+ System.out.print("Hello");
+ System.out.println(" World");
+ System.out.print(42);
+ System.out.println();
+ }
+ }
+ """,
+ """
+ class Example {
+ void test() {
+ IO.print("Hello");
+ IO.println(" World");
+ IO.print(42);
+ IO.println();
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void handlesPrintWithComplexExpressions() {
+ rewriteRun(
+ java(
+ """
+ class Example {
+ void test() {
+ String name = "John";
+ int age = 30;
+ System.out.print("Name: " + name + ", Age: " + age);
+ System.out.println(String.format("Formatted: %s is %d years old", name, age));
+ }
+ }
+ """,
+ """
+ class Example {
+ void test() {
+ String name = "John";
+ int age = 30;
+ IO.print("Name: " + name + ", Age: " + age);
+ IO.println(String.format("Formatted: %s is %d years old", name, age));
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void doesNotReplaceSystemErrCalls() {
+ rewriteRun(
+ java(
+ """
+ class Example {
+ void test() {
+ System.err.print("Error message");
+ System.err.println("Error message");
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void doesNotReplaceOtherPrintStreams() {
+ rewriteRun(
+ java(
+ """
+ import java.io.PrintStream;
+
+ class Example {
+ void test() {
+ PrintStream ps = new PrintStream(System.out);
+ ps.print("Should not change");
+ ps.println("Should not change");
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void doesNotReplacePrintf() {
+ rewriteRun(
+ java(
+ """
+ class Example {
+ void test() {
+ System.out.printf("Hello%n");
+ }
+ }
+ """
+ )
+ );
+ }
+}