All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openrewrite.staticanalysis.ReplaceRedundantFormatWithPrintf Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2024 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.staticanalysis; import org.jspecify.annotations.Nullable; import org.openrewrite.ExecutionContext; import org.openrewrite.Preconditions; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; import org.openrewrite.internal.ListUtils; 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.JavaType; import java.util.List; public class ReplaceRedundantFormatWithPrintf extends Recipe { private static final MethodMatcher STRING_FORMAT_MATCHER_LOCALE = new MethodMatcher("java.lang.String format(java.util.Locale, java.lang.String, java.lang.Object[])"); private static final MethodMatcher STRING_FORMAT_MATCHER_NO_LOCALE = new MethodMatcher("java.lang.String format(java.lang.String, java.lang.Object[])"); private static final MethodMatcher PRINTSTREAM_PRINT_MATCHER = new MethodMatcher("java.io.PrintStream print(java.lang.String)", true); private static final MethodMatcher PRINTSTREAM_PRINTLN_MATCHER = new MethodMatcher("java.io.PrintStream println(java.lang.String)", true); @Override public String getDisplayName() { return "Replace redundant String format invocations that are wrapped with PrintStream operations"; } @Override public String getDescription() { return "Replaces `PrintStream.print(String.format(format, ...args))` with `PrintStream.printf(format, ...args)` (and for `println`, appends a newline to the format string)."; } @Override public TreeVisitor getVisitor() { return Preconditions.check(Preconditions.and( Preconditions.or( new UsesMethod<>(STRING_FORMAT_MATCHER_LOCALE), new UsesMethod<>(STRING_FORMAT_MATCHER_NO_LOCALE) ), Preconditions.or( new UsesMethod<>(PRINTSTREAM_PRINT_MATCHER), new UsesMethod<>(PRINTSTREAM_PRINTLN_MATCHER) ) ), new JavaIsoVisitor() { @Override public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { method = super.visitMethodInvocation(method, ctx); boolean needsNewline; if (PRINTSTREAM_PRINTLN_MATCHER.matches(method)) { needsNewline = true; } else if (PRINTSTREAM_PRINT_MATCHER.matches(method)) { needsNewline = false; } else { return method; } Expression arg = method.getArguments().get(0); if (!(arg instanceof J.MethodInvocation)) { return method; } J.MethodInvocation innerMethodInvocation = (J.MethodInvocation) arg; boolean hasLocaleArg; if (STRING_FORMAT_MATCHER_NO_LOCALE.matches(innerMethodInvocation)) { hasLocaleArg = false; } else if (STRING_FORMAT_MATCHER_LOCALE.matches(innerMethodInvocation)) { hasLocaleArg = true; } else { return method; } List originalFormatArgs = innerMethodInvocation.getArguments(); List printfArgs; if (needsNewline) { Expression formatStringExpression = originalFormatArgs.get(hasLocaleArg ? 1 : 0); if (!(formatStringExpression instanceof J.Literal)) { return method; } J.Literal formatStringLiteral = (J.Literal) formatStringExpression; Object formatStringValue = formatStringLiteral.getValue(); if (!(formatStringValue instanceof String)) { return method; } formatStringLiteral = appendToStringLiteral(formatStringLiteral); if (formatStringLiteral == null) { return method; } List formatStringArgs = originalFormatArgs.subList(hasLocaleArg ? 2 : 1, originalFormatArgs.size()); printfArgs = ListUtils.concat(formatStringLiteral, formatStringArgs); if (hasLocaleArg) { printfArgs = ListUtils.concat(originalFormatArgs.get(0), printfArgs); } } else { printfArgs = originalFormatArgs; } // need to build the JavaTemplate code dynamically due to varargs StringBuilder code = new StringBuilder(); code.append("printf("); for (int i = 0; i < originalFormatArgs.size(); i++) { JavaType argType = originalFormatArgs.get(i).getType(); if (i != 0) { code.append(", "); } code.append("#{any("); if (argType instanceof JavaType.GenericTypeVariable) { List bounds = ((JavaType.GenericTypeVariable) argType).getBounds(); if (bounds.isEmpty()) { code.append("Object"); } else { code.append(bounds.get(0)); } } else if (argType instanceof JavaType.Parameterized) { code.append(((JavaType.Parameterized) argType).getType()); } else { code.append(argType); } code.append(")}"); } code.append(")"); JavaTemplate template = JavaTemplate.builder(code.toString()).contextSensitive().build(); return maybeAutoFormat( method, template.apply(updateCursor(method), method.getCoordinates().replaceMethod(), printfArgs.toArray()), ctx ); } }); } private static J.@Nullable Literal appendToStringLiteral(J.Literal literal) { if (literal.getType() != JavaType.Primitive.String) { return null; } Object value = literal.getValue(); if (!(value instanceof String)) { return null; } String stringValue = (String) value; String newStringValue = stringValue + "%n"; String valueSource = literal.getValueSource(); if (valueSource != null && valueSource.startsWith("\"\"\"") && valueSource.endsWith("\"\"\"")) { // text block return literal .withValueSource(valueSource.substring(0, valueSource.length() - 3) + "%n" + "\"\"\"") .withValue(newStringValue); } else if (valueSource != null && valueSource.startsWith("\"") && valueSource.endsWith("\"")) { // regular string literal return literal .withValueSource(valueSource.substring(0, valueSource.length() - 1) + "%n" + "\"") .withValue(newStringValue); } else { return null; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy