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

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

/*
 * Copyright 2023 the original author or authors.
 * 

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* https://www.apache.org/licenses/LICENSE-2.0 *

* 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.openrewrite.*; import org.openrewrite.internal.ListUtils; import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.JavaVisitor; import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.search.UsesMethod; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; import java.time.Duration; import java.util.ArrayList; import java.util.Collections; import java.util.List; import static org.openrewrite.Tree.randomId; public class ReplaceStringBuilderWithString extends Recipe { private static final MethodMatcher STRING_BUILDER_APPEND = new MethodMatcher("java.lang.StringBuilder append(..)"); private static final MethodMatcher STRING_BUILDER_TO_STRING = new MethodMatcher("java.lang.StringBuilder toString()"); @Override public String getDisplayName() { return "Replace `StringBuilder#append` with `String`"; } @Override public String getDescription() { return "Replace `StringBuilder.append()` with String if you are only concatenating a small number of strings " + "and the code is simple and easy to read, as the compiler can optimize simple string concatenation " + "expressions into a single String object, which can be more efficient than using StringBuilder."; } @Override public Duration getEstimatedEffortPerOccurrence() { return Duration.ofMinutes(2); } @Override public TreeVisitor getVisitor() { return Preconditions.check( Preconditions.and( new UsesMethod<>(STRING_BUILDER_APPEND), new UsesMethod<>(STRING_BUILDER_TO_STRING)), new StringBuilderToAppendVisitor() ); } private static class StringBuilderToAppendVisitor extends JavaVisitor { @Override public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation m = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); if (STRING_BUILDER_TO_STRING.matches(method)) { List methodCallsChain = new ArrayList<>(); List arguments = new ArrayList<>(); boolean isFlattenable = flatMethodInvocationChain(method, methodCallsChain, arguments); if (!isFlattenable || arguments.isEmpty()) { return m; } Collections.reverse(arguments); arguments = adjustExpressions(method, arguments); Expression additive = ChainStringBuilderAppendCalls.additiveExpression(arguments).withPrefix(method.getPrefix()); if (isAMethodSelect(method)) { additive = new J.Parentheses<>(randomId(), Space.EMPTY, Markers.EMPTY, JRightPadded.build(additive)); } return additive; } return m; } // Check if a method call is a "select" of another method call private boolean isAMethodSelect(J.MethodInvocation method) { Cursor parent = getCursor().getParent(2); // 2 means skip right padded cursor if (parent == null || !(parent.getValue() instanceof J.MethodInvocation)) { return false; } return ((J.MethodInvocation) parent.getValue()).getSelect() == method; } private J.Literal toStringLiteral(J.Literal input) { if (input.getType() == JavaType.Primitive.String) { return input; } String value = input.getValueSource(); return new J.Literal(randomId(), Space.EMPTY, Markers.EMPTY, value, "\"" + value + "\"", null, JavaType.Primitive.String); } private List adjustExpressions(J.MethodInvocation method, List arguments) { return ListUtils.map(arguments, (i, arg) -> { if (i == 0) { if (!TypeUtils.isString(arg.getType())) { if (arg instanceof J.Literal) { return toStringLiteral((J.Literal) arg); } else { return JavaTemplate.builder("String.valueOf(#{any()})").build() .apply(getCursor(), method.getCoordinates().replace(), arg) .withPrefix(arg.getPrefix()); } } } else if (!(arg instanceof J.Identifier || arg instanceof J.Literal || arg instanceof J.MethodInvocation)) { return new J.Parentheses<>(randomId(), Space.EMPTY, Markers.EMPTY, JRightPadded.build(arg)); } return arg; }); } /** * Return true if the method calls chain is like "new StringBuilder().append("A")....append("B");" * * @param method a StringBuilder.toString() method call * @param methodChain output methods chain * @param arguments output expression list to be chained by '+'. */ private boolean flatMethodInvocationChain(J.MethodInvocation method, List methodChain, List arguments) { Expression select = method.getSelect(); while (select != null) { methodChain.add(select); if (!(select instanceof J.MethodInvocation)) { break; } J.MethodInvocation selectMethod = (J.MethodInvocation) select; select = selectMethod.getSelect(); if (!STRING_BUILDER_APPEND.matches(selectMethod)) { return false; } List args = selectMethod.getArguments(); if (args.size() != 1) { return false; } else { arguments.add(args.get(0)); } } if (select instanceof J.NewClass && ((J.NewClass) select).getClazz() != null && TypeUtils.isOfClassType(((J.NewClass) select).getClazz().getType(), "java.lang.StringBuilder")) { J.NewClass nc = (J.NewClass) select; if (nc.getArguments().size() == 1 && TypeUtils.isString(nc.getArguments().get(0).getType())) { arguments.add(nc.getArguments().get(0)); } return true; } return false; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy