Backport String.repeat(int)

Test: tools/test.py --dex_vm all --no-internal -v *Backport*Test*
Test: tools/test.py --no-internal -v *GenerateBackportMethods*
Change-Id: I20cc8116d8333ac4b54dc62122067c159688dc54
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index bdc98f0..985195b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -1417,6 +1417,17 @@
       addProvider(
           new MethodGenerator(
               method, BackportedMethods::CharacterMethods_toStringCodepoint, "toStringCodepoint"));
+
+      // String
+      type = factory.stringType;
+
+      // String String.repeat(int)
+      name = factory.createString("repeat");
+      proto = factory.createProto(factory.stringType, factory.intType);
+      method = factory.createMethod(type, proto, name);
+      addProvider(
+          new StatifyingMethodGenerator(
+              method, BackportedMethods::StringMethods_repeat, "repeat", type));
     }
 
     private void initializeJava9OptionalMethodProviders(DexItemFactory factory) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index 8440fc2..ce1d21f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -5960,4 +5960,156 @@
         ImmutableList.of(),
         ImmutableList.of());
   }
+
+  public static CfCode StringMethods_repeat(InternalOptions options, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
+    CfLabel label6 = new CfLabel();
+    CfLabel label7 = new CfLabel();
+    CfLabel label8 = new CfLabel();
+    CfLabel label9 = new CfLabel();
+    CfLabel label10 = new CfLabel();
+    CfLabel label11 = new CfLabel();
+    CfLabel label12 = new CfLabel();
+    CfLabel label13 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        4,
+        5,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.INT, 1),
+            new CfIf(If.Type.GE, ValueType.INT, label2),
+            label1,
+            new CfNew(options.itemFactory.createType("Ljava/lang/IllegalArgumentException;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfNew(options.itemFactory.createType("Ljava/lang/StringBuilder;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfInvoke(
+                183,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/StringBuilder;"),
+                    options.itemFactory.createProto(options.itemFactory.createType("V")),
+                    options.itemFactory.createString("<init>")),
+                false),
+            new CfConstString(options.itemFactory.createString("count is negative: ")),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/StringBuilder;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("Ljava/lang/StringBuilder;"),
+                        options.itemFactory.createType("Ljava/lang/String;")),
+                    options.itemFactory.createString("append")),
+                false),
+            new CfLoad(ValueType.INT, 1),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/StringBuilder;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("Ljava/lang/StringBuilder;"),
+                        options.itemFactory.createType("I")),
+                    options.itemFactory.createString("append")),
+                false),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/StringBuilder;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("Ljava/lang/String;")),
+                    options.itemFactory.createString("toString")),
+                false),
+            new CfInvoke(
+                183,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/IllegalArgumentException;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("V"),
+                        options.itemFactory.createType("Ljava/lang/String;")),
+                    options.itemFactory.createString("<init>")),
+                false),
+            new CfThrow(),
+            label2,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/String;"),
+                    options.itemFactory.createProto(options.itemFactory.createType("I")),
+                    options.itemFactory.createString("length")),
+                false),
+            new CfStore(ValueType.INT, 2),
+            label3,
+            new CfLoad(ValueType.INT, 1),
+            new CfIf(If.Type.EQ, ValueType.INT, label4),
+            new CfLoad(ValueType.INT, 2),
+            new CfIf(If.Type.NE, ValueType.INT, label5),
+            label4,
+            new CfConstString(options.itemFactory.createString("")),
+            new CfReturn(ValueType.OBJECT),
+            label5,
+            new CfLoad(ValueType.INT, 1),
+            new CfConstNumber(1, ValueType.INT),
+            new CfIfCmp(If.Type.NE, ValueType.INT, label7),
+            label6,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfReturn(ValueType.OBJECT),
+            label7,
+            new CfNew(options.itemFactory.createType("Ljava/lang/StringBuilder;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfLoad(ValueType.INT, 2),
+            new CfLoad(ValueType.INT, 1),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Mul, NumericType.INT),
+            new CfInvoke(
+                183,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/StringBuilder;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("V"), options.itemFactory.createType("I")),
+                    options.itemFactory.createString("<init>")),
+                false),
+            new CfStore(ValueType.OBJECT, 3),
+            label8,
+            new CfConstNumber(0, ValueType.INT),
+            new CfStore(ValueType.INT, 4),
+            label9,
+            new CfLoad(ValueType.INT, 4),
+            new CfLoad(ValueType.INT, 1),
+            new CfIfCmp(If.Type.GE, ValueType.INT, label12),
+            label10,
+            new CfLoad(ValueType.OBJECT, 3),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/StringBuilder;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("Ljava/lang/StringBuilder;"),
+                        options.itemFactory.createType("Ljava/lang/String;")),
+                    options.itemFactory.createString("append")),
+                false),
+            new CfStackInstruction(CfStackInstruction.Opcode.Pop),
+            label11,
+            new CfIinc(4, 1),
+            new CfGoto(label9),
+            label12,
+            new CfLoad(ValueType.OBJECT, 3),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/StringBuilder;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("Ljava/lang/String;")),
+                    options.itemFactory.createString("toString")),
+                false),
+            new CfReturn(ValueType.OBJECT),
+            label13),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
 }
diff --git a/src/test/examplesJava11/backport/StringBackportJava11Main.java b/src/test/examplesJava11/backport/StringBackportJava11Main.java
new file mode 100644
index 0000000..2af923e
--- /dev/null
+++ b/src/test/examplesJava11/backport/StringBackportJava11Main.java
@@ -0,0 +1,31 @@
+package backport;
+
+public final class StringBackportJava11Main {
+  public static void main(String[] args) {
+    testRepeat();
+  }
+
+  private static void testRepeat() {
+    try {
+      throw new AssertionError("hey".repeat(-1));
+    } catch (IllegalArgumentException e) {
+      assertEquals("count is negative: -1", e.getMessage());
+    }
+
+    assertEquals("", "".repeat(0));
+    assertEquals("", "".repeat(1));
+    assertEquals("", "".repeat(2));
+
+    assertEquals("", "hey".repeat(0));
+    assertEquals("hey", "hey".repeat(1));
+    assertEquals("heyhey", "hey".repeat(2));
+    assertEquals("heyheyhey", "hey".repeat(3));
+    assertEquals("heyheyheyhey", "hey".repeat(4));
+  }
+
+  private static void assertEquals(Object expected, Object actual) {
+    if (expected != actual && (expected == null || !expected.equals(actual))) {
+      throw new AssertionError("Expected <" + expected + "> but was <" + actual + '>');
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/StringBackportJava11Test.java b/src/test/java/com/android/tools/r8/desugar/backports/StringBackportJava11Test.java
new file mode 100644
index 0000000..b7f4b6d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/StringBackportJava11Test.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.backports;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+
+@RunWith(Parameterized.class)
+public final class StringBackportJava11Test extends AbstractBackportTest {
+  @Parameters(name = "{0}")
+  public static Iterable<?> data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withAllApiLevels()
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK11)
+        .build();
+  }
+
+  private static final Path TEST_JAR =
+      Paths.get(ToolHelper.EXAMPLES_JAVA11_JAR_DIR).resolve("backport" + JAR_EXTENSION);
+
+  public StringBackportJava11Test(TestParameters parameters) {
+    super(parameters, String.class, TEST_JAR, "backport.StringBackportJava11Main");
+    // Note: None of the methods in this test exist in the latest android.jar. If/when they ship in
+    // an actual API level, migrate these tests to CharacterBackportTest.
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/StringMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/StringMethods.java
index 566f6d6..476e321 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/StringMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/StringMethods.java
@@ -35,4 +35,22 @@
     }
     return builder.toString();
   }
+
+  public static String repeat(String receiver, int count) {
+    if (count < 0) {
+      throw new IllegalArgumentException("count is negative: " + count);
+    }
+    int length = receiver.length();
+    if (count == 0 || length == 0) {
+      return "";
+    }
+    if (count == 1) {
+      return receiver;
+    }
+    StringBuilder builder = new StringBuilder(length * count);
+    for (int i = 0; i < count; i++) {
+      builder.append(receiver);
+    }
+    return builder.toString();
+  }
 }