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

org.openrewrite.staticanalysis.ChainStringBuilderAppendCalls 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.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaParser; import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.search.UsesMethod; import org.openrewrite.java.tree.*; import java.time.Duration; import java.util.ArrayList; import java.util.List; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; public class ChainStringBuilderAppendCalls extends Recipe { private static final MethodMatcher STRING_BUILDER_APPEND = new MethodMatcher("java.lang.StringBuilder append(String)"); @SuppressWarnings("ALL") // Stop NoMutableStaticFieldsInRecipes from suggesting to remove this mutable static field private static J.Binary additiveBinaryTemplate = null; @Override public String getDisplayName() { return "Chain `StringBuilder.append()` calls"; } @Override public String getDescription() { return "String concatenation within calls to `StringBuilder.append()` causes unnecessary memory allocation. Except for concatenations of String literals, which are joined together at compile time. Replaces inefficient concatenations with chained calls to `StringBuilder.append()`."; } @Override public @Nullable Duration getEstimatedEffortPerOccurrence() { return Duration.ofMinutes(2); } @Override public TreeVisitor getVisitor() { return Preconditions.check(new UsesMethod<>(STRING_BUILDER_APPEND), Repeat.repeatUntilStable(new JavaIsoVisitor() { @Override public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation m = super.visitMethodInvocation(method, ctx); if (STRING_BUILDER_APPEND.matches(m)) { List arguments = m.getArguments(); if (arguments.size() != 1) { return m; } List flattenExpressions = new ArrayList<>(); boolean flattenable = flatAdditiveExpressions(arguments.get(0).unwrap(), flattenExpressions); if (!flattenable) { return m; } if (flattenExpressions.stream().allMatch(J.Literal.class::isInstance)) { return m; } // group expressions List groups = new ArrayList<>(); List group = new ArrayList<>(); boolean appendToString = false; for (Expression exp : flattenExpressions) { if (appendToString) { if (exp instanceof J.Literal && (((J.Literal) exp).getType() == JavaType.Primitive.String) ) { group.add(exp); } else { addToGroups(group, groups); groups.add(exp); } } else { if (exp instanceof J.Literal && (((J.Literal) exp).getType() == JavaType.Primitive.String)) { addToGroups(group, groups); appendToString = true; } else if ((exp instanceof J.Identifier || exp instanceof J.MethodInvocation) && exp.getType() != null) { JavaType.FullyQualified fullyQualified = TypeUtils.asFullyQualified(exp.getType()); if (fullyQualified != null && fullyQualified.getFullyQualifiedName().equals("java.lang.String")) { addToGroups(group, groups); appendToString = true; } } group.add(exp); } } addToGroups(group, groups); J.MethodInvocation chainedMethods = m.withArguments(singletonList(groups.get(0))); for (int i = 1; i < groups.size(); i++) { chainedMethods = chainedMethods.withSelect(chainedMethods) .withArguments(singletonList(groups.get(i).unwrap())) .withPrefix(Space.EMPTY); } return chainedMethods; } return m; } })); } /** * Concat two literals to an expression with '+' and surrounded with single space. */ public static J.Binary concatAdditionBinary(Expression left, Expression right) { J.Binary b = getAdditiveBinaryTemplate(); return b.withPrefix(b.getLeft().getPrefix()) .withLeft(left) .withRight(right.withPrefix(Space.build(" " + right.getPrefix().getWhitespace(), emptyList()))); } /** * Concat expressions to an expression with '+' connected. */ public static @Nullable Expression additiveExpression(Expression... expressions) { Expression expression = null; for (Expression element : expressions) { if (element != null) { expression = (expression == null) ? element : concatAdditionBinary(expression, element); } } return expression; } public static @Nullable Expression additiveExpression(List expressions) { return additiveExpression(expressions.toArray(new Expression[0])); } public static J.Binary getAdditiveBinaryTemplate() { if (additiveBinaryTemplate == null) { //noinspection OptionalGetWithoutIsPresent J.CompilationUnit cu = JavaParser.fromJavaVersion() .build() .parse("class A { String s = \"A\" + \"B\";}") .map(J.CompilationUnit.class::cast) .findFirst() .get(); additiveBinaryTemplate = (J.Binary) ((J.VariableDeclarations) cu.getClasses().get(0) .getBody() .getStatements().get(0)) .getVariables().get(0) .getInitializer(); assert additiveBinaryTemplate != null; } return additiveBinaryTemplate; } /** * Concat an additive expression in a group and add to groups */ private static void addToGroups(List group, List groups) { if (!group.isEmpty()) { groups.add(additiveExpression(group)); group.clear(); } } public static boolean flatAdditiveExpressions(Expression expression, List expressionList) { if (expression instanceof J.Binary) { J.Binary b = (J.Binary) expression; if (b.getOperator() != J.Binary.Type.Addition) { return false; } return flatAdditiveExpressions(b.getLeft(), expressionList) && flatAdditiveExpressions(b.getRight(), expressionList); } else if (expression instanceof J.Literal || expression instanceof J.Identifier || expression instanceof J.MethodInvocation || expression instanceof J.Parentheses) { expressionList.add(expression.withPrefix(Space.EMPTY)); return true; } return false; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy