Backport String.isBlank/strip/stripLeading/stripTrailing

Test: tools/test.py --dex_vm all --no-internal -v *Backport*Test*
Test: tools/test.py --no-internal -v *GenerateBackportMethods*
Change-Id: I8741da099597e06e7115ef92c2e622d67f8bb0a3
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 985195b..fa68de4 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
@@ -1428,6 +1428,38 @@
       addProvider(
           new StatifyingMethodGenerator(
               method, BackportedMethods::StringMethods_repeat, "repeat", type));
+
+      // boolean String.isBlank()
+      name = factory.createString("isBlank");
+      proto = factory.createProto(factory.booleanType);
+      method = factory.createMethod(type, proto, name);
+      addProvider(
+          new StatifyingMethodGenerator(
+              method, BackportedMethods::StringMethods_isBlank, "isBlank", type));
+
+      // String String.strip()
+      name = factory.createString("strip");
+      proto = factory.createProto(factory.stringType);
+      method = factory.createMethod(type, proto, name);
+      addProvider(
+          new StatifyingMethodGenerator(
+              method, BackportedMethods::StringMethods_strip, "strip", type));
+
+      // String String.stripLeading()
+      name = factory.createString("stripLeading");
+      proto = factory.createProto(factory.stringType);
+      method = factory.createMethod(type, proto, name);
+      addProvider(
+          new StatifyingMethodGenerator(
+              method, BackportedMethods::StringMethods_stripLeading, "stripLeading", type));
+
+      // String String.stripTrailing()
+      name = factory.createString("stripTrailing");
+      proto = factory.createProto(factory.stringType);
+      method = factory.createMethod(type, proto, name);
+      addProvider(
+          new StatifyingMethodGenerator(
+              method, BackportedMethods::StringMethods_stripTrailing, "stripTrailing", 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 ce1d21f..744b10c 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
@@ -5695,6 +5695,88 @@
         ImmutableList.of());
   }
 
+  public static CfCode StringMethods_isBlank(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();
+    return new CfCode(
+        method.holder,
+        2,
+        4,
+        ImmutableList.of(
+            label0,
+            new CfConstNumber(0, ValueType.INT),
+            new CfStore(ValueType.INT, 1),
+            label1,
+            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),
+            label2,
+            new CfLoad(ValueType.INT, 1),
+            new CfLoad(ValueType.INT, 2),
+            new CfIfCmp(If.Type.GE, ValueType.INT, label8),
+            label3,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.INT, 1),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/String;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("I"), options.itemFactory.createType("I")),
+                    options.itemFactory.createString("codePointAt")),
+                false),
+            new CfStore(ValueType.INT, 3),
+            label4,
+            new CfLoad(ValueType.INT, 3),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Character;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("Z"), options.itemFactory.createType("I")),
+                    options.itemFactory.createString("isWhitespace")),
+                false),
+            new CfIf(If.Type.NE, ValueType.INT, label6),
+            label5,
+            new CfConstNumber(0, ValueType.INT),
+            new CfReturn(ValueType.INT),
+            label6,
+            new CfLoad(ValueType.INT, 1),
+            new CfLoad(ValueType.INT, 3),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Character;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("I"), options.itemFactory.createType("I")),
+                    options.itemFactory.createString("charCount")),
+                false),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT),
+            new CfStore(ValueType.INT, 1),
+            label7,
+            new CfGoto(label2),
+            label8,
+            new CfConstNumber(1, ValueType.INT),
+            new CfReturn(ValueType.INT),
+            label9),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
   public static CfCode StringMethods_joinArray(InternalOptions options, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
@@ -6112,4 +6194,332 @@
         ImmutableList.of(),
         ImmutableList.of());
   }
+
+  public static CfCode StringMethods_strip(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();
+    CfLabel label14 = new CfLabel();
+    CfLabel label15 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        3,
+        4,
+        ImmutableList.of(
+            label0,
+            new CfConstNumber(0, ValueType.INT),
+            new CfStore(ValueType.INT, 1),
+            label1,
+            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),
+            label2,
+            new CfLoad(ValueType.INT, 1),
+            new CfLoad(ValueType.INT, 2),
+            new CfIfCmp(If.Type.GE, ValueType.INT, label8),
+            label3,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.INT, 1),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/String;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("I"), options.itemFactory.createType("I")),
+                    options.itemFactory.createString("codePointAt")),
+                false),
+            new CfStore(ValueType.INT, 3),
+            label4,
+            new CfLoad(ValueType.INT, 3),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Character;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("Z"), options.itemFactory.createType("I")),
+                    options.itemFactory.createString("isWhitespace")),
+                false),
+            new CfIf(If.Type.NE, ValueType.INT, label6),
+            label5,
+            new CfGoto(label8),
+            label6,
+            new CfLoad(ValueType.INT, 1),
+            new CfLoad(ValueType.INT, 3),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Character;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("I"), options.itemFactory.createType("I")),
+                    options.itemFactory.createString("charCount")),
+                false),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT),
+            new CfStore(ValueType.INT, 1),
+            label7,
+            new CfGoto(label2),
+            label8,
+            new CfLoad(ValueType.INT, 2),
+            new CfLoad(ValueType.INT, 1),
+            new CfIfCmp(If.Type.LE, ValueType.INT, label14),
+            label9,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.INT, 2),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Character;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("I"),
+                        options.itemFactory.createType("Ljava/lang/CharSequence;"),
+                        options.itemFactory.createType("I")),
+                    options.itemFactory.createString("codePointBefore")),
+                false),
+            new CfStore(ValueType.INT, 3),
+            label10,
+            new CfLoad(ValueType.INT, 3),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Character;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("Z"), options.itemFactory.createType("I")),
+                    options.itemFactory.createString("isWhitespace")),
+                false),
+            new CfIf(If.Type.NE, ValueType.INT, label12),
+            label11,
+            new CfGoto(label14),
+            label12,
+            new CfLoad(ValueType.INT, 2),
+            new CfLoad(ValueType.INT, 3),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Character;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("I"), options.itemFactory.createType("I")),
+                    options.itemFactory.createString("charCount")),
+                false),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT),
+            new CfStore(ValueType.INT, 2),
+            label13,
+            new CfGoto(label8),
+            label14,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.INT, 1),
+            new CfLoad(ValueType.INT, 2),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/String;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("Ljava/lang/String;"),
+                        options.itemFactory.createType("I"),
+                        options.itemFactory.createType("I")),
+                    options.itemFactory.createString("substring")),
+                false),
+            new CfReturn(ValueType.OBJECT),
+            label15),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode StringMethods_stripLeading(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();
+    return new CfCode(
+        method.holder,
+        3,
+        4,
+        ImmutableList.of(
+            label0,
+            new CfConstNumber(0, ValueType.INT),
+            new CfStore(ValueType.INT, 1),
+            label1,
+            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),
+            label2,
+            new CfLoad(ValueType.INT, 1),
+            new CfLoad(ValueType.INT, 2),
+            new CfIfCmp(If.Type.GE, ValueType.INT, label8),
+            label3,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.INT, 1),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/String;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("I"), options.itemFactory.createType("I")),
+                    options.itemFactory.createString("codePointAt")),
+                false),
+            new CfStore(ValueType.INT, 3),
+            label4,
+            new CfLoad(ValueType.INT, 3),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Character;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("Z"), options.itemFactory.createType("I")),
+                    options.itemFactory.createString("isWhitespace")),
+                false),
+            new CfIf(If.Type.NE, ValueType.INT, label6),
+            label5,
+            new CfGoto(label8),
+            label6,
+            new CfLoad(ValueType.INT, 1),
+            new CfLoad(ValueType.INT, 3),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Character;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("I"), options.itemFactory.createType("I")),
+                    options.itemFactory.createString("charCount")),
+                false),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT),
+            new CfStore(ValueType.INT, 1),
+            label7,
+            new CfGoto(label2),
+            label8,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.INT, 1),
+            new CfLoad(ValueType.INT, 2),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/String;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("Ljava/lang/String;"),
+                        options.itemFactory.createType("I"),
+                        options.itemFactory.createType("I")),
+                    options.itemFactory.createString("substring")),
+                false),
+            new CfReturn(ValueType.OBJECT),
+            label9),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode StringMethods_stripTrailing(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();
+    return new CfCode(
+        method.holder,
+        3,
+        3,
+        ImmutableList.of(
+            label0,
+            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, 1),
+            label1,
+            new CfLoad(ValueType.INT, 1),
+            new CfIf(If.Type.LE, ValueType.INT, label7),
+            label2,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.INT, 1),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Character;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("I"),
+                        options.itemFactory.createType("Ljava/lang/CharSequence;"),
+                        options.itemFactory.createType("I")),
+                    options.itemFactory.createString("codePointBefore")),
+                false),
+            new CfStore(ValueType.INT, 2),
+            label3,
+            new CfLoad(ValueType.INT, 2),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Character;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("Z"), options.itemFactory.createType("I")),
+                    options.itemFactory.createString("isWhitespace")),
+                false),
+            new CfIf(If.Type.NE, ValueType.INT, label5),
+            label4,
+            new CfGoto(label7),
+            label5,
+            new CfLoad(ValueType.INT, 1),
+            new CfLoad(ValueType.INT, 2),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Character;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("I"), options.itemFactory.createType("I")),
+                    options.itemFactory.createString("charCount")),
+                false),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT),
+            new CfStore(ValueType.INT, 1),
+            label6,
+            new CfGoto(label1),
+            label7,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfConstNumber(0, ValueType.INT),
+            new CfLoad(ValueType.INT, 1),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/String;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("Ljava/lang/String;"),
+                        options.itemFactory.createType("I"),
+                        options.itemFactory.createType("I")),
+                    options.itemFactory.createString("substring")),
+                false),
+            new CfReturn(ValueType.OBJECT),
+            label8),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
 }
diff --git a/src/test/examplesJava11/backport/StringBackportJava11Main.java b/src/test/examplesJava11/backport/StringBackportJava11Main.java
index 2af923e..6af33fa 100644
--- a/src/test/examplesJava11/backport/StringBackportJava11Main.java
+++ b/src/test/examplesJava11/backport/StringBackportJava11Main.java
@@ -3,6 +3,10 @@
 public final class StringBackportJava11Main {
   public static void main(String[] args) {
     testRepeat();
+    testIsBlank();
+    testStrip();
+    testStripLeading();
+    testStripTrailing();
   }
 
   private static void testRepeat() {
@@ -23,6 +27,102 @@
     assertEquals("heyheyheyhey", "hey".repeat(4));
   }
 
+  /** Per {@link Character#isWhitespace(int)} */
+  private static final String WHITESPACE = ""
+      // Unicode "Zs" category:
+      + "\u0020"
+      + "\u1680"
+      //+ "\u00A0" Exception per Javadoc
+      + "\u1680"
+      + "\u2000"
+      + "\u2001"
+      + "\u2002"
+      + "\u2003"
+      + "\u2004"
+      + "\u2005"
+      + "\u2006"
+      //+ "\u2007" Exception per Javadoc
+      + "\u2008"
+      + "\u2009"
+      + "\u200A"
+      //+ "\u200F" Exception per Javadoc
+      //+ "\u205F" Not honored on Android 4.0.4
+      + "\u3000"
+      // Unicode "Zl" category:
+      + "\u2028"
+      // Unicode "Zp" category:
+      + "\u2029"
+      // Others:
+      + "\t"
+      + "\n"
+      + "\u000B"
+      + "\f"
+      + "\r"
+      + "\u001C"
+      + "\u001D"
+      + "\u001E"
+      + "\u001F"
+      ;
+
+  public static void testIsBlank() {
+    assertEquals(true, "".isBlank());
+    assertEquals(true, WHITESPACE.isBlank());
+
+    // Android <=4.0.4 does not recognize this as whitespace. Just ensure local consistency.
+    assertEquals(Character.isWhitespace(0x205F), "\u205F".isBlank());
+
+    assertEquals(false, "a".isBlank());
+    assertEquals(false, "å".isBlank());
+    assertEquals(false, "a\u030A".isBlank());
+    assertEquals(false, "\uD83D\uDE00".isBlank());
+    assertEquals(false, (WHITESPACE + "a").isBlank());
+    assertEquals(false, ("a" + WHITESPACE).isBlank());
+  }
+
+  public static void testStrip() {
+    assertEquals("", "".strip());
+    assertEquals("", WHITESPACE.strip());
+    assertEquals("a", "a".strip());
+    assertEquals("a", (WHITESPACE + "a").strip());
+    assertEquals("a", ("a" + WHITESPACE).strip());
+    assertEquals("a", (WHITESPACE + "a" + WHITESPACE).strip());
+    assertEquals("a" + WHITESPACE + "a", ("a" + WHITESPACE + "a").strip());
+    assertEquals("a" + WHITESPACE + "a", (WHITESPACE + "a" + WHITESPACE + "a").strip());
+    assertEquals("a" + WHITESPACE + "a", ("a" + WHITESPACE + "a" + WHITESPACE).strip());
+    assertEquals("a" + WHITESPACE + "a",
+        (WHITESPACE + "a" + WHITESPACE + "a" + WHITESPACE).strip());
+  }
+
+  public static void testStripLeading() {
+    assertEquals("", "".stripLeading());
+    assertEquals("", WHITESPACE.stripLeading());
+    assertEquals("a", "a".stripLeading());
+    assertEquals("a", (WHITESPACE + "a").stripLeading());
+    assertEquals("a" + WHITESPACE, ("a" + WHITESPACE).stripLeading());
+    assertEquals("a" + WHITESPACE, (WHITESPACE + "a" + WHITESPACE).stripLeading());
+    assertEquals("a" + WHITESPACE + "a", ("a" + WHITESPACE + "a").stripLeading());
+    assertEquals("a" + WHITESPACE + "a", (WHITESPACE + "a" + WHITESPACE + "a").stripLeading());
+    assertEquals("a" + WHITESPACE + "a" + WHITESPACE,
+        ("a" + WHITESPACE + "a" + WHITESPACE).stripLeading());
+    assertEquals("a" + WHITESPACE + "a" + WHITESPACE,
+        (WHITESPACE + "a" + WHITESPACE + "a" + WHITESPACE).stripLeading());
+  }
+
+  public static void testStripTrailing() {
+    assertEquals("", "".stripTrailing());
+    assertEquals("", WHITESPACE.stripTrailing());
+    assertEquals("a", "a".stripTrailing());
+    assertEquals(WHITESPACE + "a", (WHITESPACE + "a").stripTrailing());
+    assertEquals("a", ("a" + WHITESPACE).stripTrailing());
+    assertEquals(WHITESPACE + "a", (WHITESPACE + "a" + WHITESPACE).stripTrailing());
+    assertEquals("a" + WHITESPACE + "a", ("a" + WHITESPACE + "a").stripTrailing());
+    assertEquals(WHITESPACE + "a" + WHITESPACE + "a",
+        (WHITESPACE + "a" + WHITESPACE + "a").stripTrailing());
+    assertEquals("a" + WHITESPACE + "a", ("a" + WHITESPACE + "a" + WHITESPACE).stripTrailing());
+    assertEquals(WHITESPACE + "a" + WHITESPACE + "a",
+        (WHITESPACE + "a" + WHITESPACE + "a" + WHITESPACE).stripTrailing());
+  }
+
   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/ir/desugar/backports/StringMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/StringMethods.java
index 476e321..9a2fc52 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
@@ -53,4 +53,60 @@
     }
     return builder.toString();
   }
+
+  public static boolean isBlank(String receiver) {
+    for (int i = 0, length = receiver.length(); i < length; ) {
+      int codePoint = receiver.codePointAt(i);
+      if (!Character.isWhitespace(codePoint)) {
+        return false;
+      }
+      i += Character.charCount(codePoint);
+    }
+    return true;
+  }
+
+  public static String strip(String receiver) {
+    int start = 0;
+    int end = receiver.length();
+    while (start < end) {
+      int codePoint = receiver.codePointAt(start);
+      if (!Character.isWhitespace(codePoint)) {
+        break;
+      }
+      start += Character.charCount(codePoint);
+    }
+    while (end > start) {
+      int codePoint = Character.codePointBefore(receiver, end);
+      if (!Character.isWhitespace(codePoint)) {
+        break;
+      }
+      end -= Character.charCount(codePoint);
+    }
+    return receiver.substring(start, end);
+  }
+
+  public static String stripLeading(String receiver) {
+    int start = 0;
+    int end = receiver.length();
+    while (start < end) {
+      int codePoint = receiver.codePointAt(start);
+      if (!Character.isWhitespace(codePoint)) {
+        break;
+      }
+      start += Character.charCount(codePoint);
+    }
+    return receiver.substring(start, end);
+  }
+
+  public static String stripTrailing(String receiver) {
+    int end = receiver.length();
+    while (end > 0) {
+      int codePoint = Character.codePointBefore(receiver, end);
+      if (!Character.isWhitespace(codePoint)) {
+        break;
+      }
+      end -= Character.charCount(codePoint);
+    }
+    return receiver.substring(0, end);
+  }
 }