|  | // Copyright (c) 2020, 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.desugaredlibrary; | 
|  |  | 
|  | import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod; | 
|  | import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; | 
|  | import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf; | 
|  | import static org.hamcrest.MatcherAssert.assertThat; | 
|  | import static org.junit.Assert.assertEquals; | 
|  |  | 
|  | import com.android.tools.r8.NeverInline; | 
|  | import com.android.tools.r8.StringResource; | 
|  | import com.android.tools.r8.TestParameters; | 
|  | import com.android.tools.r8.TestRuntime.CfVm; | 
|  | import com.android.tools.r8.ToolHelper; | 
|  | import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration; | 
|  | import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser; | 
|  | import com.android.tools.r8.utils.AndroidApiLevel; | 
|  | import com.android.tools.r8.utils.BooleanUtils; | 
|  | import com.android.tools.r8.utils.InternalOptions; | 
|  | import com.android.tools.r8.utils.StringUtils; | 
|  | import com.android.tools.r8.utils.codeinspector.ClassSubject; | 
|  | import com.android.tools.r8.utils.codeinspector.CodeInspector; | 
|  | import com.android.tools.r8.utils.codeinspector.MethodSubject; | 
|  | import com.android.tools.r8.utils.structural.Ordered; | 
|  | import com.google.common.collect.ImmutableList; | 
|  | import java.nio.file.Path; | 
|  | import java.util.Comparator; | 
|  | import java.util.List; | 
|  | import java.util.Objects; | 
|  | import java.util.function.Supplier; | 
|  | import org.hamcrest.Matcher; | 
|  | import org.junit.Assume; | 
|  | import org.junit.Test; | 
|  | import org.junit.runner.RunWith; | 
|  | import org.junit.runners.Parameterized; | 
|  | import org.junit.runners.Parameterized.Parameters; | 
|  | import org.objectweb.asm.ClassWriter; | 
|  | import org.objectweb.asm.Label; | 
|  | import org.objectweb.asm.MethodVisitor; | 
|  | import org.objectweb.asm.Opcodes; | 
|  |  | 
|  | @RunWith(Parameterized.class) | 
|  | public class ObjectsTest extends DesugaredLibraryTestBase implements Opcodes { | 
|  |  | 
|  | private final TestParameters parameters; | 
|  | private final boolean libraryDesugarJavaUtilObjects; | 
|  | private final boolean shrinkDesugaredLibrary = false; | 
|  | private final Path androidJar; | 
|  |  | 
|  | @Parameters(name = "{0}, libraryDesugarJavaUtilObjects: {1}") | 
|  | public static List<Object[]> data() { | 
|  | return buildParameters( | 
|  | getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(), | 
|  | BooleanUtils.values()); | 
|  | } | 
|  |  | 
|  | public ObjectsTest(TestParameters parameters, boolean libraryDesugarJavaUtilObjects) { | 
|  | this.parameters = parameters; | 
|  | this.libraryDesugarJavaUtilObjects = libraryDesugarJavaUtilObjects; | 
|  | // Using desugared library require a compile SDK of 26 or higher. | 
|  | this.androidJar = | 
|  | ToolHelper.getAndroidJar(Ordered.max(parameters.getApiLevel(), AndroidApiLevel.O)); | 
|  | } | 
|  |  | 
|  | DesugaredLibraryConfiguration desugaredLibraryConfiguration( | 
|  | InternalOptions options, boolean libraryCompilation, TestParameters parameters) { | 
|  | return new DesugaredLibraryConfigurationParser( | 
|  | options.dexItemFactory(), | 
|  | options.reporter, | 
|  | libraryCompilation, | 
|  | parameters.getApiLevel().getLevel()) | 
|  | .parse( | 
|  | StringResource.fromFile( | 
|  | libraryDesugarJavaUtilObjects | 
|  | ? ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING_ALTERNATIVE_3 | 
|  | : ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING)); | 
|  | } | 
|  |  | 
|  | private void configurationForProgramCompilation(InternalOptions options) { | 
|  | options.desugaredLibraryConfiguration = | 
|  | desugaredLibraryConfiguration(options, false, parameters); | 
|  | } | 
|  |  | 
|  | private void configurationForLibraryCompilation(InternalOptions options) { | 
|  | options.desugaredLibraryConfiguration = | 
|  | desugaredLibraryConfiguration(options, true, parameters); | 
|  | } | 
|  |  | 
|  | private Matcher<MethodSubject> invokesObjectsCompare(String holder) { | 
|  | return invokesMethod( | 
|  | "int", | 
|  | holder, | 
|  | "compare", | 
|  | ImmutableList.of("java.lang.Object", "java.lang.Object", "java.util.Comparator")); | 
|  | } | 
|  |  | 
|  | private Matcher<MethodSubject> invokesObjectsEquals(String holder) { | 
|  | return invokesMethod( | 
|  | "boolean", holder, "equals", ImmutableList.of("java.lang.Object", "java.lang.Object")); | 
|  | } | 
|  |  | 
|  | private Matcher<MethodSubject> invokesObjectsDeepEquals(String holder) { | 
|  | return invokesMethod( | 
|  | "boolean", holder, "deepEquals", ImmutableList.of("java.lang.Object", "java.lang.Object")); | 
|  | } | 
|  |  | 
|  | private Matcher<MethodSubject> invokesObjectsHash(String holder) { | 
|  | return invokesMethod("int", holder, "hash", ImmutableList.of("java.lang.Object[]")); | 
|  | } | 
|  |  | 
|  | private Matcher<MethodSubject> invokesObjectsHashCode(String holder) { | 
|  | return invokesMethod("int", holder, "hashCode", ImmutableList.of("java.lang.Object")); | 
|  | } | 
|  |  | 
|  | private Matcher<MethodSubject> invokesObjectsRequireNonNull(String holder) { | 
|  | return invokesMethod( | 
|  | "java.lang.Object", holder, "requireNonNull", ImmutableList.of("java.lang.Object")); | 
|  | } | 
|  |  | 
|  | private Matcher<MethodSubject> invokesObjectsRequireNonNullWithMessage(String holder) { | 
|  | return invokesMethod( | 
|  | "java.lang.Object", | 
|  | holder, | 
|  | "requireNonNull", | 
|  | ImmutableList.of("java.lang.Object", "java.lang.String")); | 
|  | } | 
|  |  | 
|  | private Matcher<MethodSubject> invokesObjectsRequireNonNullWithSupplier( | 
|  | String holder, String Supplier) { | 
|  | return invokesMethod( | 
|  | "java.lang.Object", | 
|  | holder, | 
|  | "requireNonNull", | 
|  | ImmutableList.of("java.lang.Object", Supplier)); | 
|  | } | 
|  |  | 
|  | private Matcher<MethodSubject> invokesObjectsToString(String holder) { | 
|  | return invokesMethod( | 
|  | "java.lang.String", holder, "toString", ImmutableList.of("java.lang.Object")); | 
|  | } | 
|  |  | 
|  | private Matcher<MethodSubject> invokesObjectsToStringWithNullDefault(String holder) { | 
|  | return invokesMethod( | 
|  | "java.lang.String", | 
|  | holder, | 
|  | "toString", | 
|  | ImmutableList.of("java.lang.Object", "java.lang.String")); | 
|  | } | 
|  |  | 
|  | private Matcher<MethodSubject> invokesObjectsIsNull(String holder) { | 
|  | return invokesMethod("boolean", holder, "isNull", ImmutableList.of("java.lang.Object")); | 
|  | } | 
|  |  | 
|  | private Matcher<MethodSubject> invokesObjectsNonNull(String holder) { | 
|  | return invokesMethod("boolean", holder, "nonNull", ImmutableList.of("java.lang.Object")); | 
|  | } | 
|  |  | 
|  | private void inspect(CodeInspector inspector) { | 
|  | ClassSubject testClass = inspector.clazz(TestClass.class); | 
|  | assertThat(testClass, isPresent()); | 
|  |  | 
|  | // Objects.equals as added in Android K, so when backporting, this is only backported below K. | 
|  | // However, for library desugaring, the desugaring of Objects.equals happens all the way up to | 
|  | // Android M, as that is grouped with other methods like Objects.requireNonNull which was | 
|  | // added in Android N. | 
|  | boolean invokeJavaUtilObjects = | 
|  | !libraryDesugarJavaUtilObjects | 
|  | && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K) | 
|  | || (libraryDesugarJavaUtilObjects | 
|  | && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)); | 
|  | boolean invokeJDollarUtilObjects = | 
|  | libraryDesugarJavaUtilObjects && parameters.getApiLevel().isLessThan(AndroidApiLevel.N); | 
|  | boolean invokeJavaUtilObjectsWithSupplier = | 
|  | !libraryDesugarJavaUtilObjects || !parameters.getApiLevel().isLessThan(AndroidApiLevel.N); | 
|  | boolean invokeJDollarUtilObjectsWithSupplier = | 
|  | libraryDesugarJavaUtilObjects && parameters.getApiLevel().isLessThan(AndroidApiLevel.N); | 
|  |  | 
|  | assertThat( | 
|  | testClass.uniqueMethodWithName("objectsCompare"), | 
|  | onlyIf(invokeJavaUtilObjects, invokesObjectsCompare("java.util.Objects"))); | 
|  | assertThat( | 
|  | testClass.uniqueMethodWithName("objectsCompare"), | 
|  | onlyIf(invokeJDollarUtilObjects, invokesObjectsCompare("j$.util.Objects"))); | 
|  |  | 
|  | assertThat( | 
|  | testClass.uniqueMethodWithName("objectsDeepEquals"), | 
|  | onlyIf(invokeJavaUtilObjects, invokesObjectsDeepEquals("java.util.Objects"))); | 
|  | assertThat( | 
|  | testClass.uniqueMethodWithName("objectsDeepEquals"), | 
|  | onlyIf(invokeJDollarUtilObjects, invokesObjectsDeepEquals("j$.util.Objects"))); | 
|  |  | 
|  | assertThat( | 
|  | testClass.uniqueMethodWithName("objectsEquals"), | 
|  | onlyIf(invokeJavaUtilObjects, invokesObjectsEquals("java.util.Objects"))); | 
|  | assertThat( | 
|  | testClass.uniqueMethodWithName("objectsEquals"), | 
|  | onlyIf(invokeJDollarUtilObjects, invokesObjectsEquals("j$.util.Objects"))); | 
|  |  | 
|  | assertThat( | 
|  | testClass.uniqueMethodWithName("objectsHash"), | 
|  | onlyIf(invokeJavaUtilObjects, invokesObjectsHash("java.util.Objects"))); | 
|  | assertThat( | 
|  | testClass.uniqueMethodWithName("objectsHash"), | 
|  | onlyIf(invokeJDollarUtilObjects, invokesObjectsHash("j$.util.Objects"))); | 
|  |  | 
|  | assertThat( | 
|  | testClass.uniqueMethodWithName("objectsHashCode"), | 
|  | onlyIf(invokeJavaUtilObjects, invokesObjectsHashCode("java.util.Objects"))); | 
|  | assertThat( | 
|  | testClass.uniqueMethodWithName("objectsHashCode"), | 
|  | onlyIf(invokeJDollarUtilObjects, invokesObjectsHashCode("j$.util.Objects"))); | 
|  |  | 
|  | assertThat( | 
|  | testClass.uniqueMethodWithName("objectsRequireNonNull"), | 
|  | onlyIf(invokeJavaUtilObjects, invokesObjectsRequireNonNull("java.util.Objects"))); | 
|  | assertThat( | 
|  | testClass.uniqueMethodWithName("objectsRequireNonNull"), | 
|  | onlyIf(invokeJDollarUtilObjects, invokesObjectsRequireNonNull("j$.util.Objects"))); | 
|  |  | 
|  | assertThat( | 
|  | testClass.uniqueMethodWithName("objectsRequireNonNullWithMessage"), | 
|  | onlyIf( | 
|  | invokeJavaUtilObjects, invokesObjectsRequireNonNullWithMessage("java.util.Objects"))); | 
|  | assertThat( | 
|  | testClass.uniqueMethodWithName("objectsRequireNonNullWithMessage"), | 
|  | onlyIf( | 
|  | invokeJDollarUtilObjects, invokesObjectsRequireNonNullWithMessage("j$.util.Objects"))); | 
|  |  | 
|  | assertThat( | 
|  | testClass.uniqueMethodWithName("objectsRequireNonNullWithSupplier"), | 
|  | onlyIf( | 
|  | invokeJavaUtilObjectsWithSupplier, | 
|  | invokesObjectsRequireNonNullWithSupplier( | 
|  | "java.util.Objects", "java.util.function.Supplier"))); | 
|  | assertThat( | 
|  | testClass.uniqueMethodWithName("objectsRequireNonNullWithSupplier"), | 
|  | onlyIf( | 
|  | invokeJDollarUtilObjectsWithSupplier, | 
|  | invokesObjectsRequireNonNullWithSupplier( | 
|  | "j$.util.Objects", "j$.util.function.Supplier"))); | 
|  |  | 
|  | assertThat( | 
|  | testClass.uniqueMethodWithName("objectsToString"), | 
|  | onlyIf(invokeJavaUtilObjects, invokesObjectsToString("java.util.Objects"))); | 
|  | assertThat( | 
|  | testClass.uniqueMethodWithName("objectsToString"), | 
|  | onlyIf(invokeJDollarUtilObjects, invokesObjectsToString("j$.util.Objects"))); | 
|  |  | 
|  | assertThat( | 
|  | testClass.uniqueMethodWithName("objectsToStringWithNullDefault"), | 
|  | onlyIf(invokeJavaUtilObjects, invokesObjectsToStringWithNullDefault("java.util.Objects"))); | 
|  | assertThat( | 
|  | testClass.uniqueMethodWithName("objectsToStringWithNullDefault"), | 
|  | onlyIf(invokeJDollarUtilObjects, invokesObjectsToStringWithNullDefault("j$.util.Objects"))); | 
|  |  | 
|  | invokeJavaUtilObjects = parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N); | 
|  |  | 
|  | assertThat( | 
|  | testClass.uniqueMethodWithName("objectsIsNull"), | 
|  | onlyIf(invokeJavaUtilObjects, invokesObjectsIsNull("java.util.Objects"))); | 
|  | assertThat( | 
|  | testClass.uniqueMethodWithName("objectsIsNull"), | 
|  | onlyIf(invokeJDollarUtilObjects, invokesObjectsIsNull("j$.util.Objects"))); | 
|  |  | 
|  | assertThat( | 
|  | testClass.uniqueMethodWithName("objectsNonNull"), | 
|  | onlyIf(invokeJavaUtilObjects, invokesObjectsNonNull("java.util.Objects"))); | 
|  | assertThat( | 
|  | testClass.uniqueMethodWithName("objectsNonNull"), | 
|  | onlyIf(invokeJDollarUtilObjects, invokesObjectsNonNull("j$.util.Objects"))); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testD8Cf() throws Exception { | 
|  | // Adjust API level if running on JDK 8. The java.util.Objects methods added in | 
|  | // Android R where added in JDK 9, so setting the the API level to Android P will backport | 
|  | // these methods for JDK 8. | 
|  | AndroidApiLevel apiLevel = parameters.getApiLevel(); | 
|  | if (parameters.getRuntime().isCf() | 
|  | && parameters.getRuntime().asCf().getVm() == CfVm.JDK8 | 
|  | && apiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.R)) { | 
|  | apiLevel = AndroidApiLevel.P; | 
|  | } | 
|  |  | 
|  | KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters); | 
|  | // Use D8 to desugar with Java classfile output. | 
|  | Path jar = | 
|  | testForD8(Backend.CF) | 
|  | .addLibraryFiles(androidJar) | 
|  | .addOptionsModification(this::configurationForProgramCompilation) | 
|  | .addInnerClasses(ObjectsTest.class) | 
|  | .addProgramClassFileData(dumpAndroidRUtilsObjectsMethods()) | 
|  | .setMinApi(apiLevel) | 
|  | .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer) | 
|  | .compile() | 
|  | .inspect(this::inspect) | 
|  | .writeToZip(); | 
|  |  | 
|  | if (parameters.getRuntime().isDex()) { | 
|  | // Collection keep rules is only implemented in the DEX writer. | 
|  | String desugaredLibraryKeepRules = keepRuleConsumer.get(); | 
|  | if (desugaredLibraryKeepRules != null) { | 
|  | assertEquals(0, desugaredLibraryKeepRules.length()); | 
|  | desugaredLibraryKeepRules = "-keep class * { *; }"; | 
|  | } | 
|  |  | 
|  | // Convert to DEX without desugaring and run. | 
|  | testForD8() | 
|  | .addLibraryFiles(androidJar) | 
|  | .addProgramFiles(jar) | 
|  | .setMinApi(apiLevel) | 
|  | .disableDesugaring() | 
|  | .compile() | 
|  | .addDesugaredCoreLibraryRunClassPath( | 
|  | (apiLevel_, keepRules, shrink) -> | 
|  | buildDesugaredLibrary( | 
|  | apiLevel_, | 
|  | keepRules, | 
|  | shrink, | 
|  | ImmutableList.of(), | 
|  | this::configurationForLibraryCompilation), | 
|  | parameters.getApiLevel(), | 
|  | desugaredLibraryKeepRules, | 
|  | shrinkDesugaredLibrary) | 
|  | .run( | 
|  | parameters.getRuntime(), | 
|  | TestClass.class, | 
|  | Boolean.toString(libraryDesugarJavaUtilObjects)) | 
|  | .assertSuccessWithOutput(expectedOutput(libraryDesugarJavaUtilObjects)); | 
|  | } else { | 
|  | // Build the desugared library in class file format. | 
|  | Path desugaredLib = | 
|  | getDesugaredLibraryInCF(parameters, this::configurationForLibraryCompilation); | 
|  |  | 
|  | // Run on the JVM with desuagred library on classpath. | 
|  | testForJvm() | 
|  | .addProgramFiles(jar) | 
|  | .addRunClasspathFiles(desugaredLib) | 
|  | .run( | 
|  | parameters.getRuntime(), | 
|  | TestClass.class, | 
|  | Boolean.toString(libraryDesugarJavaUtilObjects)) | 
|  | .assertSuccessWithOutput(expectedOutput(libraryDesugarJavaUtilObjects)); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testD8() throws Exception { | 
|  | Assume.assumeTrue(parameters.getRuntime().isDex()); | 
|  | KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters); | 
|  | testForD8() | 
|  | .addLibraryFiles(androidJar) | 
|  | .addOptionsModification(this::configurationForProgramCompilation) | 
|  | .addInnerClasses(ObjectsTest.class) | 
|  | .addProgramClassFileData(dumpAndroidRUtilsObjectsMethods()) | 
|  | .setMinApi(parameters.getApiLevel()) | 
|  | .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer) | 
|  | .compile() | 
|  | .addDesugaredCoreLibraryRunClassPath( | 
|  | (apiLevel, keepRules, shrink) -> | 
|  | buildDesugaredLibrary( | 
|  | apiLevel, | 
|  | keepRules, | 
|  | shrink, | 
|  | ImmutableList.of(), | 
|  | this::configurationForLibraryCompilation), | 
|  | parameters.getApiLevel(), | 
|  | keepRuleConsumer.get(), | 
|  | shrinkDesugaredLibrary) | 
|  | .inspect(this::inspect) | 
|  | .run( | 
|  | parameters.getRuntime(), | 
|  | TestClass.class, | 
|  | Boolean.toString(libraryDesugarJavaUtilObjects)) | 
|  | .assertSuccessWithOutput(expectedOutput(libraryDesugarJavaUtilObjects)); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testR8() throws Exception { | 
|  | Assume.assumeTrue(parameters.getRuntime().isDex()); | 
|  | KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters); | 
|  | testForR8(parameters.getBackend()) | 
|  | .addLibraryFiles(androidJar) | 
|  | .addOptionsModification(this::configurationForProgramCompilation) | 
|  | .addInnerClasses(ObjectsTest.class) | 
|  | .addKeepMainRule(TestClass.class) | 
|  | .addProgramClassFileData(dumpAndroidRUtilsObjectsMethods()) | 
|  | .enableInliningAnnotations() | 
|  | .noMinification() | 
|  | .addKeepRules("-keep class AndroidRUtilsObjectsMethods { *; }") | 
|  | .addKeepRules("-neverinline class AndroidRUtilsObjectsMethods { *; }") | 
|  | .setMinApi(parameters.getApiLevel()) | 
|  | .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer) | 
|  | .compile() | 
|  | .addDesugaredCoreLibraryRunClassPath( | 
|  | (apiLevel, keepRules, shrink) -> | 
|  | buildDesugaredLibrary( | 
|  | apiLevel, | 
|  | keepRules, | 
|  | shrink, | 
|  | ImmutableList.of(), | 
|  | this::configurationForLibraryCompilation), | 
|  | parameters.getApiLevel(), | 
|  | keepRuleConsumer.get(), | 
|  | shrinkDesugaredLibrary) | 
|  | .inspect(this::inspect) | 
|  | .run( | 
|  | parameters.getRuntime(), | 
|  | TestClass.class, | 
|  | Boolean.toString(libraryDesugarJavaUtilObjects)) | 
|  | .assertSuccessWithOutput(expectedOutput(libraryDesugarJavaUtilObjects)); | 
|  | } | 
|  |  | 
|  | private String expectedOutput(boolean objectsRequireNonNullWithSupplierSupported) { | 
|  | return StringUtils.lines( | 
|  | "1", | 
|  | "true", | 
|  | "true", | 
|  | Objects.toString(Objects.hash(1, 2)), | 
|  | "4", | 
|  | "NPE", | 
|  | "Was null", | 
|  | objectsRequireNonNullWithSupplierSupported | 
|  | ? "Supplier said was null" | 
|  | : "Not supported (b/174840626)", | 
|  | "5", | 
|  | "6", | 
|  | "true", | 
|  | "false", | 
|  | "1", | 
|  | "2", | 
|  | "3", | 
|  | "4"); | 
|  | } | 
|  |  | 
|  | static class TestClass { | 
|  | @NeverInline | 
|  | private static void objectsCompare(String s1, String s2) { | 
|  | Comparator<String> stringsNullLast = | 
|  | (o1, o2) -> { | 
|  | if (o1 == null) { | 
|  | return o2 == null ? 0 : 1; | 
|  | } | 
|  | return o2 == null ? -1 : o1.compareTo(o2); | 
|  | }; | 
|  | System.out.println(Objects.compare(s1, s2, stringsNullLast)); | 
|  | } | 
|  |  | 
|  | @NeverInline | 
|  | private static void objectsDeepEquals(Object o1, Object o2) { | 
|  | System.out.println(Objects.deepEquals(o1, o2)); | 
|  | } | 
|  |  | 
|  | @NeverInline | 
|  | private static void objectsEquals(Object o1, Object o2) { | 
|  | System.out.println(Objects.equals(o1, o2)); | 
|  | } | 
|  |  | 
|  | @NeverInline | 
|  | private static void objectsHash(Object o1, Object o2) { | 
|  | System.out.println(Objects.hash(o1, o2)); | 
|  | } | 
|  |  | 
|  | @NeverInline | 
|  | private static void objectsHashCode(Object o) { | 
|  | System.out.println(Objects.hashCode(o)); | 
|  | } | 
|  |  | 
|  | @NeverInline | 
|  | private static void objectsRequireNonNull(Object o) { | 
|  | try { | 
|  | System.out.println(Objects.requireNonNull(o)); | 
|  | } catch (NullPointerException e) { | 
|  | System.out.println("NPE"); | 
|  | } | 
|  | } | 
|  |  | 
|  | @NeverInline | 
|  | private static void objectsRequireNonNullWithMessage(Object o, String message) { | 
|  | try { | 
|  | System.out.println(Objects.requireNonNull(o, message)); | 
|  | } catch (NullPointerException e) { | 
|  | System.out.println(e.getMessage()); | 
|  | } | 
|  | } | 
|  |  | 
|  | @NeverInline | 
|  | private static void objectsRequireNonNullWithSupplier( | 
|  | Object o, Supplier<String> messageSupplier) { | 
|  | try { | 
|  | System.out.println(Objects.requireNonNull(o, messageSupplier)); | 
|  | } catch (NullPointerException e) { | 
|  | System.out.println(e.getMessage()); | 
|  | } | 
|  | } | 
|  |  | 
|  | @NeverInline | 
|  | private static void objectsToString(Object o) { | 
|  | System.out.println(Objects.toString(o)); | 
|  | } | 
|  |  | 
|  | @NeverInline | 
|  | private static void objectsToStringWithNullDefault(Object o, String nullDefault) { | 
|  | System.out.println(Objects.toString(o, nullDefault)); | 
|  | } | 
|  |  | 
|  | @NeverInline | 
|  | private static void objectsIsNull(Object o) { | 
|  | System.out.println(Objects.isNull(o)); | 
|  | } | 
|  |  | 
|  | @NeverInline | 
|  | private static void objectsNonNull(Object o) { | 
|  | System.out.println(Objects.nonNull(o)); | 
|  | } | 
|  |  | 
|  | public static void main(String[] args) throws Exception { | 
|  | boolean objectsRequireNonNullWithSupplierSupported = Boolean.parseBoolean(args[0]); | 
|  | // Android K methods. | 
|  | objectsCompare("b", "a"); | 
|  | objectsDeepEquals(args, args); | 
|  | objectsEquals(args, args); | 
|  | objectsHash(1, 2); | 
|  | objectsHashCode(4); | 
|  | objectsRequireNonNull(null); | 
|  | objectsRequireNonNullWithMessage(null, "Was null"); | 
|  | if (objectsRequireNonNullWithSupplierSupported) { | 
|  | objectsRequireNonNullWithSupplier(null, () -> "Supplier said was null"); | 
|  | } else { | 
|  | System.out.println("Not supported (b/174840626)"); | 
|  | } | 
|  | objectsToString("5"); | 
|  | objectsToStringWithNullDefault(null, "6"); | 
|  |  | 
|  | // Android N methods. | 
|  | objectsIsNull(null); | 
|  | objectsNonNull(null); | 
|  |  | 
|  | // Android R methods. | 
|  | Class<?> c = Class.forName("AndroidRUtilsObjectsMethods"); | 
|  | c.getDeclaredMethod("checkFromIndexSize", int.class, int.class, int.class) | 
|  | .invoke(null, 1, 2, 10); | 
|  | c.getDeclaredMethod("checkFromToIndex", int.class, int.class, int.class) | 
|  | .invoke(null, 2, 4, 10); | 
|  | c.getDeclaredMethod("checkIndex", int.class, int.class).invoke(null, 3, 10); | 
|  | c.getDeclaredMethod("requireNonNullElse", Object.class, Object.class).invoke(null, null, 4); | 
|  | // TODO(b/174840626) Also support requireNonNullElseGet. | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | Dump below is from this source: | 
|  |  | 
|  | import java.util.function.Supplier; | 
|  | import java.util.Objects; | 
|  |  | 
|  | public class AndroidRUtilsObjectsMethods { | 
|  | public static void checkFromIndexSize(int fromIndex, int size, int length) { | 
|  | System.out.println(Objects.checkFromIndexSize(fromIndex, size, length)); | 
|  | } | 
|  | public static void checkFromToIndex(int fromIndex, int toIndex, int length) { | 
|  | System.out.println(Objects.checkFromToIndex(fromIndex, toIndex, length)); | 
|  | } | 
|  | public static void checkIndex(int index, int length) { | 
|  | System.out.println(Objects.checkIndex(index, length)); | 
|  | } | 
|  | public static <T> void requireNonNullElse(T obj, T defaultObj) { | 
|  | System.out.println(Objects.requireNonNullElse(obj, defaultObj)); | 
|  | } | 
|  | public static <T> void requireNonNullElseGet(T obj, Supplier<? extends T> supplier) { | 
|  | System.out.println(Objects.requireNonNullElse(obj, supplier)); | 
|  | } | 
|  | } | 
|  |  | 
|  | This is added as a dump as it use APIs which are only abailable from JDK 9. | 
|  | */ | 
|  | public static byte[] dumpAndroidRUtilsObjectsMethods() throws Exception { | 
|  |  | 
|  | ClassWriter classWriter = new ClassWriter(0); | 
|  | MethodVisitor methodVisitor; | 
|  |  | 
|  | classWriter.visit( | 
|  | V9, ACC_PUBLIC | ACC_SUPER, "AndroidRUtilsObjectsMethods", null, "java/lang/Object", null); | 
|  |  | 
|  | classWriter.visitSource("AndroidRUtilsObjectsMethods.java", null); | 
|  |  | 
|  | { | 
|  | methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); | 
|  | methodVisitor.visitCode(); | 
|  | Label label0 = new Label(); | 
|  | methodVisitor.visitLabel(label0); | 
|  | methodVisitor.visitLineNumber(3, label0); | 
|  | methodVisitor.visitVarInsn(ALOAD, 0); | 
|  | methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); | 
|  | methodVisitor.visitInsn(RETURN); | 
|  | methodVisitor.visitMaxs(1, 1); | 
|  | methodVisitor.visitEnd(); | 
|  | } | 
|  | { | 
|  | methodVisitor = | 
|  | classWriter.visitMethod( | 
|  | ACC_PUBLIC | ACC_STATIC, "checkFromIndexSize", "(III)V", null, null); | 
|  | methodVisitor.visitCode(); | 
|  | Label label0 = new Label(); | 
|  | methodVisitor.visitLabel(label0); | 
|  | methodVisitor.visitLineNumber(5, label0); | 
|  | methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); | 
|  | methodVisitor.visitVarInsn(ILOAD, 0); | 
|  | methodVisitor.visitVarInsn(ILOAD, 1); | 
|  | methodVisitor.visitVarInsn(ILOAD, 2); | 
|  | methodVisitor.visitMethodInsn( | 
|  | INVOKESTATIC, "java/util/Objects", "checkFromIndexSize", "(III)I", false); | 
|  | methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false); | 
|  | Label label1 = new Label(); | 
|  | methodVisitor.visitLabel(label1); | 
|  | methodVisitor.visitLineNumber(6, label1); | 
|  | methodVisitor.visitInsn(RETURN); | 
|  | methodVisitor.visitMaxs(4, 3); | 
|  | methodVisitor.visitEnd(); | 
|  | } | 
|  | { | 
|  | methodVisitor = | 
|  | classWriter.visitMethod( | 
|  | ACC_PUBLIC | ACC_STATIC, "checkFromToIndex", "(III)V", null, null); | 
|  | methodVisitor.visitCode(); | 
|  | Label label0 = new Label(); | 
|  | methodVisitor.visitLabel(label0); | 
|  | methodVisitor.visitLineNumber(8, label0); | 
|  | methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); | 
|  | methodVisitor.visitVarInsn(ILOAD, 0); | 
|  | methodVisitor.visitVarInsn(ILOAD, 1); | 
|  | methodVisitor.visitVarInsn(ILOAD, 2); | 
|  | methodVisitor.visitMethodInsn( | 
|  | INVOKESTATIC, "java/util/Objects", "checkFromToIndex", "(III)I", false); | 
|  | methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false); | 
|  | Label label1 = new Label(); | 
|  | methodVisitor.visitLabel(label1); | 
|  | methodVisitor.visitLineNumber(9, label1); | 
|  | methodVisitor.visitInsn(RETURN); | 
|  | methodVisitor.visitMaxs(4, 3); | 
|  | methodVisitor.visitEnd(); | 
|  | } | 
|  | { | 
|  | methodVisitor = | 
|  | classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "checkIndex", "(II)V", null, null); | 
|  | methodVisitor.visitCode(); | 
|  | Label label0 = new Label(); | 
|  | methodVisitor.visitLabel(label0); | 
|  | methodVisitor.visitLineNumber(11, label0); | 
|  | methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); | 
|  | methodVisitor.visitVarInsn(ILOAD, 0); | 
|  | methodVisitor.visitVarInsn(ILOAD, 1); | 
|  | methodVisitor.visitMethodInsn( | 
|  | INVOKESTATIC, "java/util/Objects", "checkIndex", "(II)I", false); | 
|  | methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false); | 
|  | Label label1 = new Label(); | 
|  | methodVisitor.visitLabel(label1); | 
|  | methodVisitor.visitLineNumber(12, label1); | 
|  | methodVisitor.visitInsn(RETURN); | 
|  | methodVisitor.visitMaxs(3, 2); | 
|  | methodVisitor.visitEnd(); | 
|  | } | 
|  | { | 
|  | methodVisitor = | 
|  | classWriter.visitMethod( | 
|  | ACC_PUBLIC | ACC_STATIC, | 
|  | "requireNonNullElse", | 
|  | "(Ljava/lang/Object;Ljava/lang/Object;)V", | 
|  | "<T:Ljava/lang/Object;>(TT;TT;)V", | 
|  | null); | 
|  | methodVisitor.visitCode(); | 
|  | Label label0 = new Label(); | 
|  | methodVisitor.visitLabel(label0); | 
|  | methodVisitor.visitLineNumber(14, label0); | 
|  | methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); | 
|  | methodVisitor.visitVarInsn(ALOAD, 0); | 
|  | methodVisitor.visitVarInsn(ALOAD, 1); | 
|  | methodVisitor.visitMethodInsn( | 
|  | INVOKESTATIC, | 
|  | "java/util/Objects", | 
|  | "requireNonNullElse", | 
|  | "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", | 
|  | false); | 
|  | methodVisitor.visitMethodInsn( | 
|  | INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false); | 
|  | Label label1 = new Label(); | 
|  | methodVisitor.visitLabel(label1); | 
|  | methodVisitor.visitLineNumber(15, label1); | 
|  | methodVisitor.visitInsn(RETURN); | 
|  | methodVisitor.visitMaxs(3, 2); | 
|  | methodVisitor.visitEnd(); | 
|  | } | 
|  | { | 
|  | methodVisitor = | 
|  | classWriter.visitMethod( | 
|  | ACC_PUBLIC | ACC_STATIC, | 
|  | "requireNonNullElseGet", | 
|  | "(Ljava/lang/Object;Ljava/util/function/Supplier;)V", | 
|  | "<T:Ljava/lang/Object;>(TT;Ljava/util/function/Supplier<+TT;>;)V", | 
|  | null); | 
|  | methodVisitor.visitCode(); | 
|  | Label label0 = new Label(); | 
|  | methodVisitor.visitLabel(label0); | 
|  | methodVisitor.visitLineNumber(18, label0); | 
|  | methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); | 
|  | methodVisitor.visitVarInsn(ALOAD, 0); | 
|  | methodVisitor.visitVarInsn(ALOAD, 1); | 
|  | methodVisitor.visitMethodInsn( | 
|  | INVOKESTATIC, | 
|  | "java/util/Objects", | 
|  | "requireNonNullElse", | 
|  | "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", | 
|  | false); | 
|  | methodVisitor.visitMethodInsn( | 
|  | INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false); | 
|  | Label label1 = new Label(); | 
|  | methodVisitor.visitLabel(label1); | 
|  | methodVisitor.visitLineNumber(19, label1); | 
|  | methodVisitor.visitInsn(RETURN); | 
|  | methodVisitor.visitMaxs(3, 2); | 
|  | methodVisitor.visitEnd(); | 
|  | } | 
|  | classWriter.visitEnd(); | 
|  |  | 
|  | return classWriter.toByteArray(); | 
|  | } | 
|  | } |