Optimize Desugar action startup, option normalization, and classpath archive processing.

PiperOrigin-RevId: 975774224
Change-Id: If52c4f6bb77913a0c93d4efa59a61e7141fb7426
diff --git a/src/tools/java/com/google/devtools/build/android/AndroidOptionsUtils.java b/src/tools/java/com/google/devtools/build/android/AndroidOptionsUtils.java
index ac7188e..54a1413 100644
--- a/src/tools/java/com/google/devtools/build/android/AndroidOptionsUtils.java
+++ b/src/tools/java/com/google/devtools/build/android/AndroidOptionsUtils.java
@@ -24,8 +24,11 @@
 import java.nio.file.FileSystem;
 import java.nio.file.FileSystems;
 import java.nio.file.Path;
-import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /** Utility class for JCommander-based Android options. */
 public class AndroidOptionsUtils {
@@ -33,14 +36,13 @@
   private AndroidOptionsUtils() {}
 
   /** Run the CompatShellQuotedParamsFilePreProcessor on a list of args. */
-  public static String[] runArgFilePreprocessor(JCommander jc, String[] argsAsArray)
-      throws ParameterException {
+  public static String[] runArgFilePreprocessor(JCommander jc, String[] argsAsArray) {
     jc.setExpandAtSign(false);
     return runArgFilePreprocessor(argsAsArray);
   }
 
   /** Run the CompatShellQuotedParamsFilePreProcessor on a list of args. */
-  public static String[] runArgFilePreprocessor(String[] argsAsArray) throws ParameterException {
+  public static String[] runArgFilePreprocessor(String[] argsAsArray) {
     List<String> args = ImmutableList.copyOf(argsAsArray);
     if (args.size() == 1 && args.get(0).startsWith("@")) {
       // Use CompatShellQuotedParamsFilePreProcessor to handle the arg file.
@@ -53,19 +55,6 @@
     return args.toArray(new String[0]);
   }
 
-  /**
-   * Same as AndroidOptionsUtils#normalizeBooleanOptions, but accepts an array of option classes
-   * instead.
-   */
-  public static String[] normalizeBooleanOptions(Object[] options, String[] args)
-      throws ParameterException {
-    String[] normalizedArgs = args;
-    for (Object optionsObject : options) {
-      normalizedArgs = normalizeBooleanOptions(optionsObject, normalizedArgs);
-    }
-    return normalizedArgs;
-  }
-
   private static int countLeadingChars(String s, char c) {
     int count = 0;
     for (int i = 0; i < s.length(); i++) {
@@ -78,27 +67,36 @@
   }
 
   /**
+   * Same as AndroidOptionsUtils#normalizeBooleanOptions, but accepts an array of option classes
+   * instead.
+   */
+  public static String[] normalizeBooleanOptions(Object[] options, String[] args) {
+    String[] normalizedArgs = args;
+    for (Object optionsObject : options) {
+      normalizedArgs = normalizeBooleanOptions(optionsObject, normalizedArgs);
+    }
+    return normalizedArgs;
+  }
+
+  /**
    * Normalize boolean options to use --<flagname>=true or --<flagname>=false syntax.
    *
    * <p>This is useful for JCommander-based options.
    */
   public static String[] normalizeBooleanOptions(Object options, String[] args) {
-    List<String> booleanOptions = new ArrayList<>();
-    // The normalized arg list will be as long as the original args.
-    List<String> normalizedArgs = new ArrayList<>(args.length);
+    Set<String> booleanOptionNames = new HashSet<>();
     // Find the list of boolean fields
     for (Field field : options.getClass().getDeclaredFields()) {
       if (field.getType().equals(boolean.class)) {
         // Get the `names` from the annotation of this field.
         // Iterate through the annotations
         for (Annotation annotation : field.getAnnotations()) {
-          if (annotation instanceof Parameter) {
-            Parameter parameter = (Parameter) annotation;
+          if (annotation instanceof Parameter parameter) {
             for (String name : parameter.names()) {
               // Strip leading dashes from the name and assert that the name starts with --.
               try {
                 Preconditions.checkState(name.startsWith("--") || name.startsWith("-"));
-                booleanOptions.add(name.substring(countLeadingChars(name, '-')));
+                booleanOptionNames.add(name.substring(countLeadingChars(name, '-')));
               } catch (IllegalStateException e) {
                 throw new ParameterException(
                     "ParameterException in args: Found an arg not prefixed with '--' or '-': '"
@@ -112,17 +110,19 @@
       }
     }
 
-    // Iterate through the args and normalize boolean options with --<flagname> syntax.
-    for (String arg : args) {
-      for (String booleanOption : booleanOptions) {
-        if (arg.equals("--no" + booleanOption)) {
-          arg = "--" + booleanOption + "=false";
-        } else if (arg.equals("--" + booleanOption)) {
-          arg = "--" + booleanOption + "=true";
-        }
-      }
-      normalizedArgs.add(arg);
+    Map<String, String> replacements = new HashMap<>();
+    for (String booleanOption : booleanOptionNames) {
+      replacements.put("--no" + booleanOption, "--" + booleanOption + "=false");
+      replacements.put("--" + booleanOption, "--" + booleanOption + "=true");
     }
-    return normalizedArgs.toArray(new String[0]);
+
+    // Iterate through the args and normalize boolean options with --<flagname> syntax.
+    String[] normalizedArgs = new String[args.length];
+    for (int i = 0; i < args.length; i++) {
+      String arg = args[i];
+      String replacement = replacements.get(arg);
+      normalizedArgs[i] = replacement != null ? replacement : arg;
+    }
+    return normalizedArgs;
   }
 }
diff --git a/src/tools/java/com/google/devtools/build/android/r8/Desugar.java b/src/tools/java/com/google/devtools/build/android/r8/Desugar.java
index 5dda2cd..3c82de3 100644
--- a/src/tools/java/com/google/devtools/build/android/r8/Desugar.java
+++ b/src/tools/java/com/google/devtools/build/android/r8/Desugar.java
@@ -14,6 +14,7 @@
 package com.google.devtools.build.android.r8;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.ImmutableList.toImmutableList;
 import static java.lang.Math.max;
 import static java.util.stream.Collectors.joining;
 
@@ -45,11 +46,13 @@
 import java.io.IOException;
 import java.io.PrintStream;
 import java.io.PrintWriter;
+import java.io.UncheckedIOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.time.Duration;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.logging.Logger;
 
 /** Desugar compatible wrapper based on D8 desugaring engine */
@@ -220,8 +223,6 @@
         description = "Method invocations not to rewrite, given as \"class/Name#method\".")
     public List<String> dontTouchCoreLibraryMembers = ImmutableList.of();
 
-    ;
-
     @Parameter(
         names = "--preserve_core_library_override",
         description =
@@ -332,9 +333,7 @@
 
     @Override
     public void warning(Diagnostic warning) {
-      if (warning instanceof InterfaceDesugarMissingTypeDiagnostic) {
-        InterfaceDesugarMissingTypeDiagnostic missingTypeDiagnostic =
-            (InterfaceDesugarMissingTypeDiagnostic) warning;
+      if (warning instanceof InterfaceDesugarMissingTypeDiagnostic missingTypeDiagnostic) {
         outputConsumer.missingImplementedInterface(
             DescriptorUtils.descriptorToBinaryName(
                 missingTypeDiagnostic.getContextType().getDescriptor()),
@@ -360,8 +359,7 @@
 
     @Override
     public void error(Diagnostic error) {
-      if (error instanceof DexFileOverflowDiagnostic) {
-        DexFileOverflowDiagnostic overflowDiagnostic = (DexFileOverflowDiagnostic) error;
+      if (error instanceof DexFileOverflowDiagnostic overflowDiagnostic) {
         if (!overflowDiagnostic.hasMainDexSpecification()) {
           DiagnosticsHandler.super.error(
               new StringDiagnostic(
@@ -378,6 +376,14 @@
     public void accept(ByteDataView data, ClassReference context, DiagnosticsHandler handler) {}
   }
 
+  private static ArchiveClassFileProvider createArchiveClassFileProvider(Path path) {
+    try {
+      return new ArchiveClassFileProvider(path);
+    } catch (IOException e) {
+      throw new UncheckedIOException(e);
+    }
+  }
+
   private void desugar(
       List<ClassFileResourceProvider> bootclasspathProviders,
       ClassFileResourceProvider classpath,
@@ -438,19 +444,31 @@
   private void desugar() throws CompilationFailedException, IOException {
     // Prepare bootclasspath and classpath. Some jars on the classpath are considered to be
     // bootclasspath, and are moved there.
+    ImmutableList<ClassFileResourceProvider> bootclasspathProvidersList =
+        options.bootclasspath.parallelStream()
+            .map(Desugar::createArchiveClassFileProvider)
+            .collect(toImmutableList());
+
+    ImmutableList<Map.Entry<ClassFileResourceProvider, Boolean>> evaluatedClasspath =
+        options.classpath.parallelStream()
+            .map(
+                path -> {
+                  ClassFileResourceProvider provider = createArchiveClassFileProvider(path);
+                  return Map.entry(provider, isPlatform(path, provider));
+                })
+            .collect(toImmutableList());
+
     ImmutableList.Builder<ClassFileResourceProvider> bootclasspathProvidersBuilder =
         ImmutableList.builder();
-    for (Path path : options.bootclasspath) {
-      bootclasspathProvidersBuilder.add(new ArchiveClassFileProvider(path));
-    }
+    bootclasspathProvidersBuilder.addAll(bootclasspathProvidersList);
+
     ImmutableList.Builder<ClassFileResourceProvider> classpathProvidersBuilder =
         ImmutableList.builder();
-    for (Path path : options.classpath) {
-      ClassFileResourceProvider provider = new ArchiveClassFileProvider(path);
-      if (isPlatform(path, provider)) {
-        bootclasspathProvidersBuilder.add(provider);
+    for (Map.Entry<ClassFileResourceProvider, Boolean> entry : evaluatedClasspath) {
+      if (entry.getValue()) {
+        bootclasspathProvidersBuilder.add(entry.getKey());
       } else {
-        classpathProvidersBuilder.add(provider);
+        classpathProvidersBuilder.add(entry.getKey());
       }
     }
 
@@ -594,8 +612,7 @@
         options.outputJars.size());
   }
 
-  public static int processRequest(List<String> args, PrintStream diagnosticsHandlerPrintStream)
-      throws Exception {
+  public static int processRequest(List<String> args, PrintStream diagnosticsHandlerPrintStream) {
     setDesugarJvmFlags();
     int exitCode = 0;
     try {
@@ -660,7 +677,7 @@
     }
   }
 
-  public static void main(String[] args) throws Exception {
+  public static void main(String[] args) {
     if (args.length > 0 && args[0].equals("--persistent_worker")) {
       System.exit(runPersistentWorker());
     } else {
diff --git a/src/tools/java/com/google/devtools/build/android/r8/desugar/OrderedClassFileResourceProvider.java b/src/tools/java/com/google/devtools/build/android/r8/desugar/OrderedClassFileResourceProvider.java
index 7687062..ce1159e 100644
--- a/src/tools/java/com/google/devtools/build/android/r8/desugar/OrderedClassFileResourceProvider.java
+++ b/src/tools/java/com/google/devtools/build/android/r8/desugar/OrderedClassFileResourceProvider.java
@@ -16,32 +16,31 @@
 import com.android.tools.r8.ClassFileResourceProvider;
 import com.android.tools.r8.ProgramResource;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Sets;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
+import javax.annotation.Nullable;
 
 /**
  * Classpath provider which will de-dupe duplicate classes from several providers. For any defined
  * class the definition from the first provider defining the class is used.
  */
 public class OrderedClassFileResourceProvider implements ClassFileResourceProvider {
-  private final Set<String> descriptors = Sets.newHashSet();
   private final Map<String, ClassFileResourceProvider> descriptorToProvider = new HashMap<>();
 
   public OrderedClassFileResourceProvider(
       ImmutableList<ClassFileResourceProvider> bootclasspathProviders,
       ImmutableList<ClassFileResourceProvider> classfileProviders) {
-    final Set<String> bootclasspathDescriptors = Sets.newHashSet();
+    final Set<String> bootclasspathDescriptors = new HashSet<>();
     bootclasspathProviders.forEach(p -> bootclasspathDescriptors.addAll(p.getClassDescriptors()));
     for (ClassFileResourceProvider provider : classfileProviders) {
       // Collect all descriptors provided and the first provider providing each.
       for (String descriptor : provider.getClassDescriptors()) {
         // Pick first definition of classpath class and filter out platform classes
         // from classpath if present.
-        if (!bootclasspathDescriptors.contains(descriptor)
-            && descriptors.add(descriptor)) {
-          descriptorToProvider.put(descriptor, provider);
+        if (!bootclasspathDescriptors.contains(descriptor)) {
+          descriptorToProvider.putIfAbsent(descriptor, provider);
         }
       }
     }
@@ -49,10 +48,11 @@
 
   @Override
   public Set<String> getClassDescriptors() {
-    return descriptors;
+    return descriptorToProvider.keySet();
   }
 
   @Override
+  @Nullable
   public ProgramResource getProgramResource(String descriptor) {
     ClassFileResourceProvider provider = descriptorToProvider.get(descriptor);
     return provider != null ? provider.getProgramResource(descriptor) : null;