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); + } }