String.repeat() method (available since Java 11).
-Example:
+Reports loops that can be replaced with a singleStringBuilder.repeat() method (available since Java 21) or
+String.repeat() method (available since Java 11).
+Example (Java 11):
void append(StringBuilder sb, int count, Object obj) {
for (int i = 0; i < count; i++) {
diff --git a/java/java-tests/testData/inspection/stringBuilderRepeat/afterRepeat.java b/java/java-tests/testData/inspection/stringBuilderRepeat/afterRepeat.java
new file mode 100644
index 000000000000..09ab97f6c6f0
--- /dev/null
+++ b/java/java-tests/testData/inspection/stringBuilderRepeat/afterRepeat.java
@@ -0,0 +1,10 @@
+// "Replace with 'StringBuilder.repeat()'" "true"
+class Test {
+ String hundredSpaces() {
+ StringBuilder sb = new StringBuilder();
+ // comment before for-loop
+ // comment before append
+ sb.repeat(" ", 100);
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/stringBuilderRepeat/afterRepeatDiff.java b/java/java-tests/testData/inspection/stringBuilderRepeat/afterRepeatDiff.java
new file mode 100644
index 000000000000..488a330565f6
--- /dev/null
+++ b/java/java-tests/testData/inspection/stringBuilderRepeat/afterRepeatDiff.java
@@ -0,0 +1,8 @@
+// "Replace with 'StringBuilder.repeat()'" "true"
+class Test {
+ String spaces(int a, int b, int c, int d) {
+ StringBuilder sb = new StringBuilder();
+ sb.repeat(" ", Math.max(0, c - d - (a - b)));
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/stringBuilderRepeat/afterRepeatDiffKnown.java b/java/java-tests/testData/inspection/stringBuilderRepeat/afterRepeatDiffKnown.java
new file mode 100644
index 000000000000..c6ae1b4e0c96
--- /dev/null
+++ b/java/java-tests/testData/inspection/stringBuilderRepeat/afterRepeatDiffKnown.java
@@ -0,0 +1,8 @@
+// "Replace with 'StringBuilder.repeat()'" "true"
+class Test {
+ String testRepeat(String s, StringBuilder sb, int digits) {
+ if ((s.length() < digits) && (sb.length() > 0)) {
+ sb.repeat("0", digits - s.length());
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/stringBuilderRepeat/afterRepeatField.java b/java/java-tests/testData/inspection/stringBuilderRepeat/afterRepeatField.java
new file mode 100644
index 000000000000..3c3df75756c8
--- /dev/null
+++ b/java/java-tests/testData/inspection/stringBuilderRepeat/afterRepeatField.java
@@ -0,0 +1,11 @@
+// "Replace with 'StringBuilder.repeat()'" "true"
+class Test {
+ public int pendingSpaces;
+
+ String testRepeat(StringBuilder buffer) {
+ if (pendingSpaces > 0) {
+ buffer.repeat(" ", pendingSpaces);
+ pendingSpaces = 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/stringBuilderRepeat/afterRepeatInclusive.java b/java/java-tests/testData/inspection/stringBuilderRepeat/afterRepeatInclusive.java
new file mode 100644
index 000000000000..eae1e8d1e3c2
--- /dev/null
+++ b/java/java-tests/testData/inspection/stringBuilderRepeat/afterRepeatInclusive.java
@@ -0,0 +1,8 @@
+// "Replace with 'StringBuilder.repeat()'" "true"
+class Test {
+ String spaces(int c, int d) {
+ StringBuilder sb = new StringBuilder();
+ sb.repeat("123456", Math.max(0, c - d));
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/stringBuilderRepeat/afterRepeatNonConstant.java b/java/java-tests/testData/inspection/stringBuilderRepeat/afterRepeatNonConstant.java
new file mode 100644
index 000000000000..119f55c686fc
--- /dev/null
+++ b/java/java-tests/testData/inspection/stringBuilderRepeat/afterRepeatNonConstant.java
@@ -0,0 +1,8 @@
+// "Replace with 'StringBuilder.repeat()'" "true"
+class Test {
+ String hundredTimes(String s) {
+ StringBuilder sb = new StringBuilder();
+ sb.repeat(String.valueOf(s), 100);
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/stringBuilderRepeat/afterRepeatNonConstantNullChecked.java b/java/java-tests/testData/inspection/stringBuilderRepeat/afterRepeatNonConstantNullChecked.java
new file mode 100644
index 000000000000..db07583c49c9
--- /dev/null
+++ b/java/java-tests/testData/inspection/stringBuilderRepeat/afterRepeatNonConstantNullChecked.java
@@ -0,0 +1,11 @@
+// "Replace with 'StringBuilder.repeat()'" "true"
+import java.util.Objects;
+
+class Test {
+ String hundredTimes(String s) {
+ Objects.requireNonNull(s);
+ StringBuilder sb = new StringBuilder();
+ sb.repeat(s, 100);
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/stringBuilderRepeat/afterStringBuffer.java b/java/java-tests/testData/inspection/stringBuilderRepeat/afterStringBuffer.java
new file mode 100644
index 000000000000..4e1aec00d727
--- /dev/null
+++ b/java/java-tests/testData/inspection/stringBuilderRepeat/afterStringBuffer.java
@@ -0,0 +1,8 @@
+// "Replace with 'StringBuffer.repeat()'" "true"
+class Test {
+ String hundredSpaces() {
+ StringBuffer sb = new StringBuffer();
+ sb.repeat(" ", 100);
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/stringBuilderRepeat/beforeBrokenChar.java b/java/java-tests/testData/inspection/stringBuilderRepeat/beforeBrokenChar.java
new file mode 100644
index 000000000000..07c22fa1e42b
--- /dev/null
+++ b/java/java-tests/testData/inspection/stringBuilderRepeat/beforeBrokenChar.java
@@ -0,0 +1,10 @@
+// "Replace with 'StringBuilder.repeat()'" "false"
+class Test {
+ String hundredSpaces() {
+ StringBuilder sb = new StringBuilder();
+ for(int i=0; i<100; i++) {
+ sb.append('*
+ }
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/stringBuilderRepeat/beforeBrokenParenthesizedString.java b/java/java-tests/testData/inspection/stringBuilderRepeat/beforeBrokenParenthesizedString.java
new file mode 100644
index 000000000000..990f4dd14aaf
--- /dev/null
+++ b/java/java-tests/testData/inspection/stringBuilderRepeat/beforeBrokenParenthesizedString.java
@@ -0,0 +1,10 @@
+// "Replace with 'StringBuilder.repeat()'" "false"
+class Test {
+ String hundredSpaces() {
+ StringBuilder sb = new StringBuilder();
+ for(int i=0; i<100; i++) {
+ sb.append(("*
+ }
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/stringBuilderRepeat/beforeBrokenString.java b/java/java-tests/testData/inspection/stringBuilderRepeat/beforeBrokenString.java
new file mode 100644
index 000000000000..1ab4bb1da5db
--- /dev/null
+++ b/java/java-tests/testData/inspection/stringBuilderRepeat/beforeBrokenString.java
@@ -0,0 +1,10 @@
+// "Replace with 'StringBuilder.repeat()'" "false"
+class Test {
+ String hundredSpaces() {
+ StringBuilder sb = new StringBuilder();
+ for(int i=0; i<100; i++) {
+ sb.append("a" + "*
+ }
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/stringBuilderRepeat/beforeRepeat.java b/java/java-tests/testData/inspection/stringBuilderRepeat/beforeRepeat.java
new file mode 100644
index 000000000000..6d2c007a5dd9
--- /dev/null
+++ b/java/java-tests/testData/inspection/stringBuilderRepeat/beforeRepeat.java
@@ -0,0 +1,12 @@
+// "Replace with 'StringBuilder.repeat()'" "true"
+class Test {
+ String hundredSpaces() {
+ StringBuilder sb = new StringBuilder();
+ // comment before for-loop
+ for(int i=0; i<100; i++) {
+ // comment before append
+ sb.append(" ");
+ }
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/stringBuilderRepeat/beforeRepeatDiff.java b/java/java-tests/testData/inspection/stringBuilderRepeat/beforeRepeatDiff.java
new file mode 100644
index 000000000000..0b5141146a51
--- /dev/null
+++ b/java/java-tests/testData/inspection/stringBuilderRepeat/beforeRepeatDiff.java
@@ -0,0 +1,10 @@
+// "Replace with 'StringBuilder.repeat()'" "true"
+class Test {
+ String spaces(int a, int b, int c, int d) {
+ StringBuilder sb = new StringBuilder();
+ for(int i=a-b; i 0)) {
+ for (int i=s.length(); i < digits; i++) {
+ sb.append('0');
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/stringBuilderRepeat/beforeRepeatField.java b/java/java-tests/testData/inspection/stringBuilderRepeat/beforeRepeatField.java
new file mode 100644
index 000000000000..814f49a6e4d2
--- /dev/null
+++ b/java/java-tests/testData/inspection/stringBuilderRepeat/beforeRepeatField.java
@@ -0,0 +1,12 @@
+// "Replace with 'StringBuilder.repeat()'" "true"
+class Test {
+ public int pendingSpaces;
+
+ String testRepeat(StringBuilder buffer) {
+ if (pendingSpaces > 0) {
+ for (int sp = 0; sp < pendingSpaces; sp++)
+ buffer.append(' ');
+ pendingSpaces = 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/stringBuilderRepeat/beforeRepeatInclusive.java b/java/java-tests/testData/inspection/stringBuilderRepeat/beforeRepeatInclusive.java
new file mode 100644
index 000000000000..41588092bfbe
--- /dev/null
+++ b/java/java-tests/testData/inspection/stringBuilderRepeat/beforeRepeatInclusive.java
@@ -0,0 +1,10 @@
+// "Replace with 'StringBuilder.repeat()'" "true"
+class Test {
+ String spaces(int c, int d) {
+ StringBuilder sb = new StringBuilder();
+ for(int i=1; i<=c-d; i++) {
+ sb.append(123_456);
+ }
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/stringBuilderRepeat/beforeRepeatNonConstant.java b/java/java-tests/testData/inspection/stringBuilderRepeat/beforeRepeatNonConstant.java
new file mode 100644
index 000000000000..7e1c98cbebe4
--- /dev/null
+++ b/java/java-tests/testData/inspection/stringBuilderRepeat/beforeRepeatNonConstant.java
@@ -0,0 +1,10 @@
+// "Replace with 'StringBuilder.repeat()'" "true"
+class Test {
+ String hundredTimes(String s) {
+ StringBuilder sb = new StringBuilder();
+ for(int i=0; i<100; i++) {
+ sb.append(s);
+ }
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/stringBuilderRepeat/beforeRepeatNonConstantNullChecked.java b/java/java-tests/testData/inspection/stringBuilderRepeat/beforeRepeatNonConstantNullChecked.java
new file mode 100644
index 000000000000..d554eb7432c0
--- /dev/null
+++ b/java/java-tests/testData/inspection/stringBuilderRepeat/beforeRepeatNonConstantNullChecked.java
@@ -0,0 +1,13 @@
+// "Replace with 'StringBuilder.repeat()'" "true"
+import java.util.Objects;
+
+class Test {
+ String hundredTimes(String s) {
+ Objects.requireNonNull(s);
+ StringBuilder sb = new StringBuilder();
+ for(int i=0; i<100; i++) {
+ sb.append(s);
+ }
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/stringBuilderRepeat/beforeRepeatSideEffect.java b/java/java-tests/testData/inspection/stringBuilderRepeat/beforeRepeatSideEffect.java
new file mode 100644
index 000000000000..d419d99e537a
--- /dev/null
+++ b/java/java-tests/testData/inspection/stringBuilderRepeat/beforeRepeatSideEffect.java
@@ -0,0 +1,10 @@
+// "Replace with 'StringBuilder.repeat()'" "false"
+class Test {
+ String hundredNumbers() {
+ StringBuilder sb = new StringBuilder();
+ for(int i=0; i<100; i++) {
+ sb.append(Math.random());
+ }
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/stringBuilderRepeat/beforeStringBuffer.java b/java/java-tests/testData/inspection/stringBuilderRepeat/beforeStringBuffer.java
new file mode 100644
index 000000000000..a6c4ff8a8de3
--- /dev/null
+++ b/java/java-tests/testData/inspection/stringBuilderRepeat/beforeStringBuffer.java
@@ -0,0 +1,10 @@
+// "Replace with 'StringBuffer.repeat()'" "true"
+class Test {
+ String hundredSpaces() {
+ StringBuffer sb = new StringBuffer();
+ for(int i=0; i<100; i++) {
+ sb.append(" ");
+ }
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/stringRepeat/beforeBrokenChar.java b/java/java-tests/testData/inspection/stringRepeat/beforeBrokenChar.java
new file mode 100644
index 000000000000..39924642f9cb
--- /dev/null
+++ b/java/java-tests/testData/inspection/stringRepeat/beforeBrokenChar.java
@@ -0,0 +1,10 @@
+// "Replace with 'String.repeat()'" "false"
+class Test {
+ String hundredSpaces() {
+ StringBuilder sb = new StringBuilder();
+ for(int i=0; i<100; i++) {
+ sb.append('*
+ }
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/stringRepeat/beforeBrokenString.java b/java/java-tests/testData/inspection/stringRepeat/beforeBrokenString.java
new file mode 100644
index 000000000000..ef308170474d
--- /dev/null
+++ b/java/java-tests/testData/inspection/stringRepeat/beforeBrokenString.java
@@ -0,0 +1,10 @@
+// "Replace with 'String.repeat()'" "false"
+class Test {
+ String hundredSpaces() {
+ StringBuilder sb = new StringBuilder();
+ for(int i=0; i<100; i++) {
+ sb.append("a" + "*
+ }
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/StringBuilderRepeatCanBeUsedInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/StringBuilderRepeatCanBeUsedInspectionTest.java
new file mode 100644
index 000000000000..4db3d3c3c587
--- /dev/null
+++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/StringBuilderRepeatCanBeUsedInspectionTest.java
@@ -0,0 +1,28 @@
+// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package com.intellij.java.codeInspection;
+
+import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
+import com.intellij.codeInspection.LocalInspectionTool;
+import com.intellij.codeInspection.StringRepeatCanBeUsedInspection;
+import com.intellij.testFramework.LightProjectDescriptor;
+import org.jetbrains.annotations.NotNull;
+
+import static com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase.JAVA_21;
+
+public class StringBuilderRepeatCanBeUsedInspectionTest extends LightQuickFixParameterizedTestCase {
+ @Override
+ protected LocalInspectionTool @NotNull [] configureLocalInspectionTools() {
+ return new LocalInspectionTool[]{new StringRepeatCanBeUsedInspection()};
+ }
+
+ @NotNull
+ @Override
+ protected LightProjectDescriptor getProjectDescriptor() {
+ return JAVA_21;
+ }
+
+ @Override
+ protected String getBasePath() {
+ return "/inspection/stringBuilderRepeat";
+ }
+}
diff --git a/java/openapi/resources/messages/JavaBundle.properties b/java/openapi/resources/messages/JavaBundle.properties
index afbcfd29353a..375d5fa31bfd 100644
--- a/java/openapi/resources/messages/JavaBundle.properties
+++ b/java/openapi/resources/messages/JavaBundle.properties
@@ -531,6 +531,7 @@ inspection.message.can.be.replaced.with.files.readstring=Can be replaced with 'F
inspection.message.can.be.replaced.with.optional.of.nullable=Can be replaced with Optional.ofNullable()
inspection.message.can.be.replaced.with.single.expression.in.functional.style=Can be replaced with single expression in functional style
inspection.message.can.be.replaced.with.string.repeat=Can be replaced with 'String.repeat()'
+inspection.message.can.be.replaced.with.builder.repeat=Can be replaced with ''{0}''
inspection.message.lambda.parameter.type.is.redundant=Lambda parameter type is redundant
inspection.message.pseudo.functional.style.code=Pseudo functional style code
inspection.message.redundant.default.parameter.value.assignment=Redundant default parameter value assignment
@@ -1410,7 +1411,7 @@ inspection.explicit.array.filling.display.name=Explicit array filling
inspection.java.8.collection.remove.if.display.name=Loop can be replaced with 'Collection.removeIf()'
inspection.java.8.list.replace.all.display.name=Loop can be replaced with 'List.replaceAll()'
inspection.java.8.map.api.display.name=Simplifiable 'Map' operations
-inspection.string.repeat.can.be.used.display.name=String.repeat() can be used
+inspection.string.repeat.can.be.used.display.name='repeat()' method can be used
inspection.read.write.string.can.be.used.display.name='Files.readString()' or 'Files.writeString()' can be used
inspection.java.9.collection.factory.display.name=Immutable collection creation can be replaced with collection factory call
inspection.explicit.argument.can.be.lambda.display.name=Explicit argument can be lambda