Backport String.join methods
Test: tools/test.py --dex_vm all --no-internal *BackportedMethodRewriterTest*
Change-Id: I9610140661f82cdb8f9f89c3d08ceaa176dd1c32
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 79e15c5..a476e44 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -179,6 +179,7 @@
public final DexString invokeExactMethodName = createString("invokeExact");
public final DexString charSequenceDescriptor = createString("Ljava/lang/CharSequence;");
+ public final DexString charSequenceArrayDescriptor = createString("[Ljava/lang/CharSequence;");
public final DexString stringDescriptor = createString("Ljava/lang/String;");
public final DexString stringArrayDescriptor = createString("[Ljava/lang/String;");
public final DexString objectDescriptor = createString("Ljava/lang/Object;");
@@ -193,6 +194,7 @@
public final DexString enumDescriptor = createString("Ljava/lang/Enum;");
public final DexString annotationDescriptor = createString("Ljava/lang/annotation/Annotation;");
public final DexString objectsDescriptor = createString("Ljava/util/Objects;");
+ public final DexString iterableDescriptor = createString("Ljava/lang/Iterable;");
public final DexString stringBuilderDescriptor = createString("Ljava/lang/StringBuilder;");
public final DexString stringBufferDescriptor = createString("Ljava/lang/StringBuffer;");
@@ -256,6 +258,7 @@
public final DexType boxedNumberType = createType(boxedNumberDescriptor);
public final DexType charSequenceType = createType(charSequenceDescriptor);
+ public final DexType charSequenceArrayType = createType(charSequenceArrayDescriptor);
public final DexType stringType = createType(stringDescriptor);
public final DexType stringArrayType = createType(stringArrayDescriptor);
public final DexType objectType = createType(objectDescriptor);
@@ -263,6 +266,7 @@
public final DexType classArrayType = createType(classArrayDescriptor);
public final DexType enumType = createType(enumDescriptor);
public final DexType annotationType = createType(annotationDescriptor);
+ public final DexType iterableType = createType(iterableDescriptor);
public final DexType classType = createType(classDescriptor);
public final DexType classLoaderType = createType(classLoaderDescriptor);
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 11a92d6..9e80a2e 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
@@ -38,6 +38,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -456,6 +457,40 @@
}
}
+ private static final class StringMethods extends TemplateMethodCode {
+ StringMethods(InternalOptions options, DexMethod method, String methodName) {
+ super(options, method, methodName, method.proto.toDescriptorString());
+ }
+
+ public static String joinArrayImpl(CharSequence delimiter, CharSequence... elements) {
+ if (delimiter == null) throw new NullPointerException("delimiter");
+ StringBuilder builder = new StringBuilder();
+ if (elements.length > 0) {
+ builder.append(elements[0]);
+ for (int i = 1; i < elements.length; i++) {
+ builder.append(delimiter);
+ builder.append(elements[i]);
+ }
+ }
+ return builder.toString();
+ }
+
+ public static String joinIterableImpl(CharSequence delimiter,
+ Iterable<? extends CharSequence> elements) {
+ if (delimiter == null) throw new NullPointerException("delimiter");
+ StringBuilder builder = new StringBuilder();
+ Iterator<? extends CharSequence> iterator = elements.iterator();
+ if (iterator.hasNext()) {
+ builder.append(iterator.next());
+ while (iterator.hasNext()) {
+ builder.append(delimiter);
+ builder.append(iterator.next());
+ }
+ }
+ return builder.toString();
+ }
+ }
+
private static final class ObjectsMethods extends TemplateMethodCode {
ObjectsMethods(InternalOptions options, DexMethod method, String methodName) {
super(options, method, methodName, method.proto.toDescriptorString());
@@ -922,6 +957,23 @@
proto = factory.createProto(factory.intType, factory.longType, factory.longType);
addOrGetMethod(clazz, method).put(proto,
new MethodGenerator(LongMethods::new, "compareUnsignedImpl", clazz, method, proto));
+
+ // String
+ clazz = factory.stringDescriptor;
+
+ // String String.join(CharSequence, CharSequence...)
+ method = factory.createString("join");
+ proto = factory.createProto(factory.stringType, factory.charSequenceType,
+ factory.charSequenceArrayType);
+ addOrGetMethod(clazz, method).put(proto,
+ new MethodGenerator(StringMethods::new, "joinArrayImpl", clazz, method, proto));
+
+ // String String.join(CharSequence, Iterable<? extends CharSequence>)
+ method = factory.createString("join");
+ proto =
+ factory.createProto(factory.stringType, factory.charSequenceType, factory.iterableType);
+ addOrGetMethod(clazz, method).put(proto,
+ new MethodGenerator(StringMethods::new, "joinIterableImpl", clazz, method, proto));
}
private Map<DexString, Map<DexProto, MethodGenerator>> addOrGetClass(DexString clazz) {
diff --git a/src/test/java/com/android/tools/r8/desugar/BackportedMethodRewriterTest.java b/src/test/java/com/android/tools/r8/desugar/BackportedMethodRewriterTest.java
index 5a77115..5eb581d 100644
--- a/src/test/java/com/android/tools/r8/desugar/BackportedMethodRewriterTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/BackportedMethodRewriterTest.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.nio.file.Path;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@@ -39,7 +40,7 @@
.run(TestMethods.class)
.assertSuccessWithOutput(expectedOutput);
- assertDesugaring(AndroidApiLevel.O, 60);
+ assertDesugaring(AndroidApiLevel.O, 70);
assertDesugaring(AndroidApiLevel.N, 49);
assertDesugaring(AndroidApiLevel.K, 20);
assertDesugaring(AndroidApiLevel.J_MR2, 0);
@@ -137,6 +138,12 @@
return (int) Math.signum(value);
}
+ // Defined as a static method on this class to avoid affecting invoke-static counts in main().
+ @SafeVarargs
+ private static <T> List<T> listOf(T... values) {
+ return Arrays.asList(values);
+ }
+
public static void main(String[] args) {
byte[] aBytes = new byte[]{42, 1, -1, Byte.MAX_VALUE, Byte.MIN_VALUE};
for (byte aByte : aBytes) {
@@ -244,6 +251,30 @@
}
}
+ System.out.println(String.join(", "));
+ System.out.println(String.join(", ", "one", "two", "three"));
+ System.out.println(String.join("", "one", "two", "three"));
+ try {
+ throw new AssertionError(String.join(null, "one", "two", "three"));
+ } catch (NullPointerException expected) {
+ }
+ try {
+ throw new AssertionError(String.join(", ", (CharSequence[]) null));
+ } catch (NullPointerException expected) {
+ }
+
+ System.out.println(String.join(", ", listOf()));
+ System.out.println(String.join(", ", listOf("one", "two", "three")));
+ System.out.println(String.join("", listOf("one", "two", "three")));
+ try {
+ throw new AssertionError(String.join(null, listOf("one", "two", "three")));
+ } catch (NullPointerException expected) {
+ }
+ try {
+ throw new AssertionError(String.join(", ", (Iterable<CharSequence>) null));
+ } catch (NullPointerException expected) {
+ }
+
System.out.println(Objects.compare("a", "b", reverse()));
System.out.println(Objects.compare("b", "a", reverse()));
System.out.println(Objects.compare("a", "a", reverse()));