Refactor supportedClasses to introduce fields

- fix minor issues

Change-Id: I69ab6177d7ac3d886f4fc23b5c9b0f7a3c4e1653
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/AbstractGenerateFiles.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/AbstractGenerateFiles.java
index fdc1f13..c76c17f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/AbstractGenerateFiles.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/AbstractGenerateFiles.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
@@ -20,13 +19,11 @@
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Sets;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Collection;
-import java.util.Set;
 import java.util.concurrent.ExecutorService;
 
 public abstract class AbstractGenerateFiles {
@@ -44,7 +41,6 @@
   final Path desugaredLibrarySpecificationPath;
   final Collection<Path> desugaredLibraryImplementation;
   final Path outputDirectory;
-  final Set<DexMethod> parallelMethods = Sets.newIdentityHashSet();
 
   public AbstractGenerateFiles(
       String desugarConfigurationPath, String desugarImplementationPath, String outputDirectory)
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java
index ead34cb..859bc95 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java
@@ -9,13 +9,13 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.MethodAccessFlags;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedMethodsWithAnnotations.ClassAnnotation;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedMethodsWithAnnotations.MethodAnnotation;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.ClassAnnotation;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.MethodAnnotation;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.SupportedClass;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import java.io.PrintStream;
 import java.nio.file.Files;
@@ -467,7 +467,7 @@
       }
       builder.end("ul").end("td");
       StringBuilder commentBuilder = new StringBuilder();
-      if (classAnnotation.fullySupported) {
+      if (classAnnotation.isFullySupported()) {
         commentBuilder.append("Fully implemented class.").append(HTML_SPLIT);
       }
       if (parallelStreamMethod) {
@@ -496,10 +496,10 @@
             .append(" Also supported with covariant return type.")
             .append(HTML_SPLIT);
       }
-      if (!classAnnotation.unsupportedMethods.isEmpty()) {
+      if (!classAnnotation.getUnsupportedMethods().isEmpty()) {
         commentBuilder
             .append("Some methods (")
-            .append(classAnnotation.unsupportedMethods.size())
+            .append(classAnnotation.getUnsupportedMethods().size())
             .append(") present in Android ")
             .append(MAX_TESTED_ANDROID_API_LEVEL)
             .append(" are not supported.");
@@ -510,25 +510,22 @@
     }
   }
 
-  private void generateClassHTML(
-      PrintStream ps,
-      DexClass clazz,
-      List<DexEncodedMethod> methods,
-      ClassAnnotation classAnnotation,
-      Map<DexMethod, MethodAnnotation> methodAnnotationMap) {
-    SourceBuilder<HTMLSourceBuilder> builder = new HTMLSourceBuilder(clazz, classAnnotation);
+  private void generateClassHTML(PrintStream ps, SupportedClass supportedClass) {
+    DexClass clazz = supportedClass.getClazz();
+    SourceBuilder<HTMLSourceBuilder> builder =
+        new HTMLSourceBuilder(clazz, supportedClass.getClassAnnotation());
     // We need to extend to support fields.
     StreamSupport.stream(clazz.fields().spliterator(), false)
         .filter(field -> field.accessFlags.isPublic() || field.accessFlags.isProtected())
         .sorted(Comparator.comparing(DexEncodedField::toSourceString))
         .forEach(builder::addField);
-    methods.stream()
-        .filter(
-            method ->
-                (method.accessFlags.isPublic() || method.accessFlags.isProtected())
-                    && !method.accessFlags.isBridge())
-        .sorted(Comparator.comparing(DexEncodedMethod::toSourceString))
-        .forEach(m -> builder.addMethod(m, methodAnnotationMap.get(m.getReference())));
+    supportedClass.forEachMethodAndAnnotation(
+        (method, methodAnnotation) -> {
+          if ((method.accessFlags.isPublic() || method.accessFlags.isProtected())
+              && !method.accessFlags.isBridge()) {
+            builder.addMethod(method, methodAnnotation);
+          }
+        });
     ps.println(builder);
   }
 
@@ -536,20 +533,12 @@
   AndroidApiLevel run() throws Exception {
     PrintStream ps = new PrintStream(Files.newOutputStream(outputDirectory.resolve("apis.html")));
 
-    SupportedMethodsWithAnnotations supportedMethods =
+    SupportedClasses supportedClasses =
         new SupportedMethodsGenerator(options)
             .run(desugaredLibraryImplementation, desugaredLibrarySpecificationPath);
 
     // Full classes added.
-    supportedMethods.supportedMethods.forEach(
-        (clazz, methods) -> {
-          generateClassHTML(
-              ps,
-              clazz,
-              methods,
-              supportedMethods.annotatedClasses.get(clazz.type),
-              supportedMethods.annotatedMethods);
-        });
+    supportedClasses.forEachClass(supportedClass -> generateClassHTML(ps, supportedClass));
     return MAX_TESTED_ANDROID_API_LEVEL;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
index 721a5d9..b7a1a27 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
@@ -5,10 +5,15 @@
 package com.android.tools.r8.ir.desugar.desugaredlibrary.lint;
 
 import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.Version;
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfThrow;
+import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.dex.Marker.Backend;
+import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -24,7 +29,7 @@
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.LazyLoadedDexApplication;
 import com.android.tools.r8.graph.MethodCollection.MethodCollectionFactory;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedMethodsWithAnnotations.MethodAnnotation;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.MethodAnnotation;
 import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
@@ -150,37 +155,38 @@
   private void writeLintFiles(
       AndroidApiLevel compilationApiLevel,
       AndroidApiLevel minApiLevel,
-      SupportedMethodsWithAnnotations supportedMethods)
+      SupportedClasses supportedClasses)
       throws Exception {
     // Build a plain text file with the desugared APIs.
     List<String> desugaredApisSignatures = new ArrayList<>();
 
     LazyLoadedDexApplication.Builder builder = DexApplication.builder(options, Timing.empty());
-    supportedMethods.supportedMethods.forEach(
-        (clazz, methods) -> {
+    supportedClasses.forEachClass(
+        (supportedClass) -> {
           String classBinaryName =
-              DescriptorUtils.getClassBinaryNameFromDescriptor(clazz.type.descriptor.toString());
-          if (!supportedMethods.annotatedClasses.get(clazz.type).fullySupported) {
-            for (DexEncodedMethod method : methods) {
-              if (method.isInstanceInitializer() || method.isClassInitializer()) {
-                // No new constructors are added.
-                continue;
-              }
-              MethodAnnotation methodAnnotation =
-                  supportedMethods.annotatedMethods.get(method.getReference());
-              if (shouldAddMethodToLint(methodAnnotation, minApiLevel)) {
-                desugaredApisSignatures.add(
-                    classBinaryName
-                        + '#'
-                        + method.getReference().name
-                        + method.getReference().proto.toDescriptorString());
-              }
-            }
+              DescriptorUtils.getClassBinaryNameFromDescriptor(
+                  supportedClass.getType().descriptor.toString());
+          if (!supportedClass.getClassAnnotation().isFullySupported()) {
+            supportedClass.forEachMethodAndAnnotation(
+                (method, methodAnnotation) -> {
+                  if (method.isInstanceInitializer() || method.isClassInitializer()) {
+                    // No new constructors are added.
+                    return;
+                  }
+                  if (shouldAddMethodToLint(methodAnnotation, minApiLevel)) {
+                    desugaredApisSignatures.add(
+                        classBinaryName
+                            + '#'
+                            + method.getReference().name
+                            + method.getReference().proto.toDescriptorString());
+                  }
+                });
           } else {
             desugaredApisSignatures.add(classBinaryName);
           }
 
-          addMethodsToHeaderJar(builder, clazz, methods);
+          addMethodsToHeaderJar(
+              builder, supportedClass.getClazz(), supportedClass.getSupportedMethods());
         });
 
     // Write a plain text file with the desugared APIs.
@@ -193,7 +199,12 @@
         AppView.createForD8(
             AppInfo.createInitialAppInfo(
                 builder.build(), GlobalSyntheticsStrategy.forNonSynthesizing()));
-    CfApplicationWriter writer = new CfApplicationWriter(appView, options.getMarker());
+    Marker marker =
+        new Marker(Tool.D8)
+            .setVersion(Version.LABEL)
+            .setCompilationMode(CompilationMode.DEBUG)
+            .setBackend(Backend.CF);
+    CfApplicationWriter writer = new CfApplicationWriter(appView, marker);
     ClassFileConsumer consumer =
         new ClassFileConsumer.ArchiveConsumer(
             lintFile(compilationApiLevel, minApiLevel, FileUtils.JAR_EXTENSION));
@@ -221,7 +232,7 @@
   private void generateLintFiles(
       AndroidApiLevel compilationApiLevel,
       AndroidApiLevel minApiLevel,
-      SupportedMethodsWithAnnotations supportedMethods)
+      SupportedClasses supportedMethods)
       throws Exception {
     System.out.print("  - generating for min API:");
     System.out.print(" " + minApiLevel);
@@ -232,7 +243,7 @@
   public AndroidApiLevel run() throws Exception {
     AndroidApiLevel compilationLevel =
         desugaredLibrarySpecification.getRequiredCompilationApiLevel();
-    SupportedMethodsWithAnnotations supportedMethods =
+    SupportedClasses supportedMethods =
         new SupportedMethodsGenerator(options)
             .run(desugaredLibraryImplementation, desugaredLibrarySpecificationPath);
     System.out.println("Generating lint files for compile API " + compilationLevel);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java
new file mode 100644
index 0000000..3c1cfd6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java
@@ -0,0 +1,332 @@
+// Copyright (c) 2023, 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.ir.desugar.desugaredlibrary.lint;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSortedMap;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+public class SupportedClasses {
+  private final Map<DexType, SupportedClass> supportedClasses;
+
+  public void forEachClass(Consumer<SupportedClass> consumer) {
+    supportedClasses.values().forEach(consumer);
+  }
+
+  SupportedClasses(Map<DexType, SupportedClass> supportedClasses) {
+    this.supportedClasses = supportedClasses;
+  }
+
+  public static class SupportedClass {
+
+    private final DexClass clazz;
+    private final ClassAnnotation classAnnotation;
+    private final List<DexEncodedMethod> supportedMethods;
+    private final Map<DexMethod, MethodAnnotation> methodAnnotations;
+
+    private SupportedClass(
+        DexClass clazz,
+        ClassAnnotation classAnnotation,
+        List<DexEncodedMethod> supportedMethods,
+        Map<DexMethod, MethodAnnotation> methodAnnotations) {
+      this.clazz = clazz;
+      this.classAnnotation = classAnnotation;
+      this.supportedMethods = supportedMethods;
+      this.methodAnnotations = methodAnnotations;
+    }
+
+    public DexType getType() {
+      return clazz.type;
+    }
+
+    public DexClass getClazz() {
+      return clazz;
+    }
+
+    public ClassAnnotation getClassAnnotation() {
+      return classAnnotation;
+    }
+
+    public List<DexEncodedMethod> getSupportedMethods() {
+      return supportedMethods;
+    }
+
+    public void forEachMethodAndAnnotation(
+        BiConsumer<DexEncodedMethod, MethodAnnotation> biConsumer) {
+      for (DexEncodedMethod supportedMethod : supportedMethods) {
+        biConsumer.accept(supportedMethod, getMethodAnnotation(supportedMethod.getReference()));
+      }
+    }
+
+    public MethodAnnotation getMethodAnnotation(DexMethod method) {
+      return methodAnnotations.get(method);
+    }
+
+    static Builder builder(DexClass clazz) {
+      return new Builder(clazz);
+    }
+
+    private static class Builder {
+
+      private final DexClass clazz;
+      private ClassAnnotation classAnnotation;
+      private final List<DexEncodedMethod> supportedMethods = new ArrayList<>();
+      private final Map<DexMethod, MethodAnnotation> methodAnnotations = new HashMap<>();
+
+      private Builder(DexClass clazz) {
+        this.clazz = clazz;
+      }
+
+      void forEachMethods(BiConsumer<DexClass, List<DexEncodedMethod>> biConsumer) {
+        biConsumer.accept(clazz, supportedMethods);
+      }
+
+      void forEachMethod(BiConsumer<DexClass, DexEncodedMethod> biConsumer) {
+        for (DexEncodedMethod dexEncodedMethod : supportedMethods) {
+          biConsumer.accept(clazz, dexEncodedMethod);
+        }
+      }
+
+      void addSupportedMethod(DexEncodedMethod method) {
+        assert method.getHolderType() == clazz.type;
+        supportedMethods.add(method);
+      }
+
+      void annotateClass(ClassAnnotation annotation) {
+        assert annotation != null;
+        assert classAnnotation == null;
+        classAnnotation = annotation;
+      }
+
+      void annotateMethod(DexMethod method, MethodAnnotation annotation) {
+        assert method.getHolderType() == clazz.type;
+        MethodAnnotation prev =
+            methodAnnotations.getOrDefault(method, MethodAnnotation.getDefault());
+        methodAnnotations.put(method, annotation.combine(prev));
+      }
+
+      MethodAnnotation getMethodAnnotation(DexMethod method) {
+        return methodAnnotations.get(method);
+      }
+
+      SupportedClass build() {
+        supportedMethods.sort(Comparator.comparing(DexEncodedMethod::getReference));
+        return new SupportedClass(
+            clazz, classAnnotation, ImmutableList.copyOf(supportedMethods), methodAnnotations);
+      }
+    }
+  }
+
+  static Builder builder() {
+    return new Builder();
+  }
+
+  static class Builder {
+
+    Map<DexType, SupportedClass.Builder> supportedClassBuilders = new IdentityHashMap<>();
+
+    void forEachClassAndMethods(BiConsumer<DexClass, List<DexEncodedMethod>> biConsumer) {
+      supportedClassBuilders
+          .values()
+          .forEach(classBuilder -> classBuilder.forEachMethods(biConsumer));
+    }
+
+    void forEachClassAndMethod(BiConsumer<DexClass, DexEncodedMethod> biConsumer) {
+      supportedClassBuilders
+          .values()
+          .forEach(classBuilder -> classBuilder.forEachMethod(biConsumer));
+    }
+
+    void addSupportedMethod(DexClass holder, DexEncodedMethod method) {
+      SupportedClass.Builder classBuilder =
+          supportedClassBuilders.computeIfAbsent(
+              holder.type, clazz -> SupportedClass.builder(holder));
+      classBuilder.addSupportedMethod(method);
+    }
+
+    void annotateClass(DexType type, ClassAnnotation annotation) {
+      SupportedClass.Builder classBuilder = supportedClassBuilders.get(type);
+      assert classBuilder != null;
+      classBuilder.annotateClass(annotation);
+    }
+
+    void annotateMethod(DexMethod method, MethodAnnotation annotation) {
+      SupportedClass.Builder classBuilder = supportedClassBuilders.get(method.getHolderType());
+      assert classBuilder != null;
+      classBuilder.annotateMethod(method, annotation);
+    }
+
+    void annotateMethodIfPresent(DexMethod method, MethodAnnotation annotation) {
+      SupportedClass.Builder classBuilder = supportedClassBuilders.get(method.getHolderType());
+      if (classBuilder == null) {
+        return;
+      }
+      annotateMethod(method, annotation);
+    }
+
+    MethodAnnotation getMethodAnnotation(DexMethod method) {
+      SupportedClass.Builder classBuilder = supportedClassBuilders.get(method.getHolderType());
+      assert classBuilder != null;
+      return classBuilder.getMethodAnnotation(method);
+    }
+
+    SupportedClasses build() {
+      Map<DexType, SupportedClass> map = new IdentityHashMap<>();
+      supportedClassBuilders.forEach(
+          (type, classBuilder) -> {
+            map.put(type, classBuilder.build());
+          });
+      return new SupportedClasses(ImmutableSortedMap.copyOf(map));
+    }
+  }
+
+  static class ClassAnnotation {
+
+    private final boolean fullySupported;
+    // Methods in latest android.jar but unsupported.
+    private final List<DexMethod> unsupportedMethods;
+
+    public ClassAnnotation(boolean fullySupported, List<DexMethod> unsupportedMethods) {
+      this.fullySupported = fullySupported;
+      unsupportedMethods.sort(Comparator.naturalOrder());
+      this.unsupportedMethods = ImmutableList.copyOf(unsupportedMethods);
+    }
+
+    public boolean isFullySupported() {
+      return fullySupported;
+    }
+
+    public List<DexMethod> getUnsupportedMethods() {
+      return unsupportedMethods;
+    }
+  }
+
+  public static class MethodAnnotation {
+
+    private static final MethodAnnotation COVARIANT_RETURN_SUPPORTED =
+        new MethodAnnotation(false, false, true, false, -1, -1);
+    private static final MethodAnnotation DEFAULT =
+        new MethodAnnotation(false, false, false, false, -1, -1);
+    private static final MethodAnnotation PARALLEL_STREAM_METHOD =
+        new MethodAnnotation(true, false, false, false, -1, -1);
+    private static final MethodAnnotation MISSING_FROM_LATEST_ANDROID_JAR =
+        new MethodAnnotation(false, true, false, false, -1, -1);
+
+    // ParallelStream methods are not supported when the runtime api level is strictly below 21.
+    final boolean parallelStreamMethod;
+    // Methods not in the latest android jar but still fully supported.
+    final boolean missingFromLatestAndroidJar;
+    // Methods not supported in a given min api range.
+    final boolean unsupportedInMinApiRange;
+    final boolean covariantReturnSupported;
+    final int minRange;
+    final int maxRange;
+
+    MethodAnnotation(
+        boolean parallelStreamMethod,
+        boolean missingFromLatestAndroidJar,
+        boolean covariantReturnSupported,
+        boolean unsupportedInMinApiRange,
+        int minRange,
+        int maxRange) {
+      this.parallelStreamMethod = parallelStreamMethod;
+      this.missingFromLatestAndroidJar = missingFromLatestAndroidJar;
+      this.covariantReturnSupported = covariantReturnSupported;
+      this.unsupportedInMinApiRange = unsupportedInMinApiRange;
+      this.minRange = minRange;
+      this.maxRange = maxRange;
+    }
+
+    public static MethodAnnotation getCovariantReturnSupported() {
+      return COVARIANT_RETURN_SUPPORTED;
+    }
+
+    public static MethodAnnotation getDefault() {
+      return DEFAULT;
+    }
+
+    public static MethodAnnotation getParallelStreamMethod() {
+      return PARALLEL_STREAM_METHOD;
+    }
+
+    public static MethodAnnotation getMissingFromLatestAndroidJar() {
+      return MISSING_FROM_LATEST_ANDROID_JAR;
+    }
+
+    public static MethodAnnotation createMissingInMinApi(int api) {
+      return new MethodAnnotation(false, false, false, true, api, api);
+    }
+
+    public boolean isUnsupportedInMinApiRange() {
+      return unsupportedInMinApiRange;
+    }
+
+    public int getMinRange() {
+      return minRange;
+    }
+
+    public int getMaxRange() {
+      return maxRange;
+    }
+
+    public boolean isCovariantReturnSupported() {
+      return covariantReturnSupported;
+    }
+
+    public MethodAnnotation combine(MethodAnnotation other) {
+      if (this == getDefault()) {
+        return other;
+      }
+      if (other == getDefault()) {
+        return this;
+      }
+      int newMin, newMax;
+      if (!unsupportedInMinApiRange && !other.unsupportedInMinApiRange) {
+        newMin = newMax = -1;
+      } else if (!unsupportedInMinApiRange || !other.unsupportedInMinApiRange) {
+        newMin = unsupportedInMinApiRange ? minRange : other.minRange;
+        newMax = unsupportedInMinApiRange ? maxRange : other.maxRange;
+      } else {
+        // Merge ranges if contiguous or throw.
+        if (maxRange == other.minRange - 1) {
+          newMin = minRange;
+          newMax = other.maxRange;
+        } else if (other.maxRange == minRange - 1) {
+          newMin = other.minRange;
+          newMax = maxRange;
+        } else {
+          // 20 is missing, so if maxRange or minRange are 19 the following is 21.
+          if (maxRange == 19 && other.minRange == 21) {
+            newMin = minRange;
+            newMax = other.maxRange;
+          } else if (other.maxRange == 19 && minRange == 21) {
+            newMin = other.minRange;
+            newMax = maxRange;
+          } else {
+            throw new RuntimeException("Cannot merge ranges.");
+          }
+        }
+      }
+      return new MethodAnnotation(
+          parallelStreamMethod || other.parallelStreamMethod,
+          missingFromLatestAndroidJar || other.missingFromLatestAndroidJar,
+          covariantReturnSupported || other.covariantReturnSupported,
+          unsupportedInMinApiRange || other.unsupportedInMinApiRange,
+          newMin,
+          newMax);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedMethodsGenerator.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedMethodsGenerator.java
index c196fe9..752262f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedMethodsGenerator.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedMethodsGenerator.java
@@ -27,8 +27,8 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAmender;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedMethodsWithAnnotations.ClassAnnotation;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedMethodsWithAnnotations.MethodAnnotation;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.ClassAnnotation;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.MethodAnnotation;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
 import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
@@ -57,9 +57,9 @@
     this.options = options;
   }
 
-  public SupportedMethodsWithAnnotations run(
-      Collection<Path> desugaredLibraryImplementation, Path specification) throws IOException {
-    SupportedMethodsWithAnnotations.Builder builder = SupportedMethodsWithAnnotations.builder();
+  public SupportedClasses run(Collection<Path> desugaredLibraryImplementation, Path specification)
+      throws IOException {
+    SupportedClasses.Builder builder = SupportedClasses.builder();
     // First analyze everything which is supported when desugaring for api 1.
     collectSupportedMethodsInB(desugaredLibraryImplementation, specification, builder);
     // Second annotate all apis which are partially and/or fully supported.
@@ -77,7 +77,7 @@
   }
 
   private void annotateClasses(
-      SupportedMethodsWithAnnotations.Builder builder, DirectMappedDexApplication appForMax) {
+      SupportedClasses.Builder builder, DirectMappedDexApplication appForMax) {
 
     builder.forEachClassAndMethods(
         (clazz, methods) -> {
@@ -94,9 +94,8 @@
               missing.add(method.getReference());
               fullySupported = false;
             }
-            if (builder.annotatedMethods.containsKey(method.getReference())
-                && builder.annotatedMethods.get(method.getReference())
-                    != MethodAnnotation.getCovariantReturnSupported()) {
+            MethodAnnotation methodAnnotation = builder.getMethodAnnotation(method.getReference());
+            if (methodAnnotation != null && !methodAnnotation.isCovariantReturnSupported()) {
               fullySupported = false;
             }
           }
@@ -105,7 +104,7 @@
   }
 
   private void annotatePartialDesugaringMethods(
-      SupportedMethodsWithAnnotations.Builder builder, Path specification) throws IOException {
+      SupportedClasses.Builder builder, Path specification) throws IOException {
     for (int api = AndroidApiLevel.K.getLevel();
         api <= MAX_TESTED_ANDROID_API_LEVEL.getLevel();
         api++) {
@@ -129,6 +128,7 @@
           getMachineSpecification(androidApiLevel, specification);
 
       options.setMinApiLevel(androidApiLevel);
+      options.resetDesugaredLibrarySpecificationForTesting();
       options.setDesugaredLibrarySpecification(machineSpecification);
       List<DexMethod> backports =
           BackportedMethodRewriter.generateListOfBackportedMethods(
@@ -166,14 +166,14 @@
     }
   }
 
-  private void annotateParallelMethods(SupportedMethodsWithAnnotations.Builder builder) {
+  private void annotateParallelMethods(SupportedClasses.Builder builder) {
     for (DexMethod parallelMethod : getParallelMethods()) {
-      builder.annotateMethod(parallelMethod, MethodAnnotation.getParallelStreamMethod());
+      builder.annotateMethodIfPresent(parallelMethod, MethodAnnotation.getParallelStreamMethod());
     }
   }
 
   private void annotateMethodsNotOnLatestAndroidJar(
-      DirectMappedDexApplication appForMax, SupportedMethodsWithAnnotations.Builder builder) {
+      DirectMappedDexApplication appForMax, SupportedClasses.Builder builder) {
     builder.forEachClassAndMethod(
         (clazz, method) -> {
           DexClass dexClass = appForMax.definitionFor(clazz.type);
@@ -196,7 +196,7 @@
   private void collectSupportedMethodsInB(
       Collection<Path> desugaredLibraryImplementation,
       Path specification,
-      SupportedMethodsWithAnnotations.Builder builder)
+      SupportedClasses.Builder builder)
       throws IOException {
 
     AndroidApp implementation =
@@ -215,6 +215,7 @@
         getMachineSpecification(AndroidApiLevel.B, specification);
 
     options.setMinApiLevel(AndroidApiLevel.B);
+    options.resetDesugaredLibrarySpecificationForTesting();
     options.setDesugaredLibrarySpecification(machineSpecification);
     List<DexMethod> backports =
         BackportedMethodRewriter.generateListOfBackportedMethods(
@@ -288,7 +289,7 @@
   private void addBackports(
       DexProgramClass clazz,
       List<DexMethod> backports,
-      SupportedMethodsWithAnnotations.Builder builder,
+      SupportedClasses.Builder builder,
       DirectMappedDexApplication amendedAppForMax) {
     for (DexMethod backport : backports) {
       if (clazz.type == backport.getHolderType()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedMethodsWithAnnotations.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedMethodsWithAnnotations.java
deleted file mode 100644
index 0cd6887..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedMethodsWithAnnotations.java
+++ /dev/null
@@ -1,215 +0,0 @@
-// Copyright (c) 2023, 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.ir.desugar.desugaredlibrary.lint;
-
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedMember;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSortedMap;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.function.BiConsumer;
-
-public class SupportedMethodsWithAnnotations {
-
-  public final Map<DexClass, List<DexEncodedMethod>> supportedMethods;
-  public final Map<DexMethod, MethodAnnotation> annotatedMethods;
-  // A fully supported class has no annotated methods, and all the methods from the latest
-  // android.jar are supported.
-  public final Map<DexType, ClassAnnotation> annotatedClasses;
-
-  SupportedMethodsWithAnnotations(
-      Map<DexClass, List<DexEncodedMethod>> supportedMethods,
-      Map<DexMethod, MethodAnnotation> annotatedMethods,
-      Map<DexType, ClassAnnotation> annotatedClasses) {
-    this.supportedMethods = supportedMethods;
-    this.annotatedMethods = annotatedMethods;
-    this.annotatedClasses = annotatedClasses;
-  }
-
-  static Builder builder() {
-    return new Builder();
-  }
-
-  static class Builder {
-
-    Map<DexClass, List<DexEncodedMethod>> supportedMethods = new IdentityHashMap<>();
-    Map<DexMethod, MethodAnnotation> annotatedMethods = new IdentityHashMap<>();
-    Map<DexType, ClassAnnotation> annotatedClasses = new IdentityHashMap<>();
-
-    void forEachClassAndMethods(BiConsumer<DexClass, List<DexEncodedMethod>> biConsumer) {
-      supportedMethods.forEach(biConsumer);
-    }
-
-    void forEachClassAndMethod(BiConsumer<DexClass, DexEncodedMethod> biConsumer) {
-      supportedMethods.forEach(
-          (clazz, methods) -> {
-            for (DexEncodedMethod method : methods) {
-              biConsumer.accept(clazz, method);
-            }
-          });
-    }
-
-    void addSupportedMethod(DexClass holder, DexEncodedMethod method) {
-      List<DexEncodedMethod> methods =
-          supportedMethods.computeIfAbsent(holder, f -> new ArrayList<>());
-      methods.add(method);
-    }
-
-    void annotateClass(DexType type, ClassAnnotation annotation) {
-      annotatedClasses.put(type, annotation);
-    }
-
-    void annotateMethod(DexMethod method, MethodAnnotation annotation) {
-      MethodAnnotation prev = annotatedMethods.getOrDefault(method, MethodAnnotation.getDefault());
-      annotatedMethods.put(method, annotation.combine(prev));
-    }
-
-    SupportedMethodsWithAnnotations build() {
-      supportedMethods.forEach(
-          (k, v) -> v.sort(Comparator.comparing(DexEncodedMember::getReference)));
-      return new SupportedMethodsWithAnnotations(
-          ImmutableSortedMap.copyOf(supportedMethods, Comparator.comparing(DexClass::getType)),
-          ImmutableMap.copyOf(annotatedMethods),
-          ImmutableMap.copyOf(annotatedClasses));
-    }
-  }
-
-  static class ClassAnnotation {
-
-    final boolean fullySupported;
-    // Methods in latest android.jar but unsupported.
-    final List<DexMethod> unsupportedMethods;
-
-    public ClassAnnotation(boolean fullySupported, List<DexMethod> unsupportedMethods) {
-      this.fullySupported = fullySupported;
-      unsupportedMethods.sort(Comparator.naturalOrder());
-      this.unsupportedMethods = ImmutableList.copyOf(unsupportedMethods);
-    }
-  }
-
-  public static class MethodAnnotation {
-
-    private static final MethodAnnotation COVARIANT_RETURN_SUPPORTED =
-        new MethodAnnotation(false, false, true, false, -1, -1);
-    private static final MethodAnnotation DEFAULT =
-        new MethodAnnotation(false, false, false, false, -1, -1);
-    private static final MethodAnnotation PARALLEL_STREAM_METHOD =
-        new MethodAnnotation(true, false, false, false, -1, -1);
-    private static final MethodAnnotation MISSING_FROM_LATEST_ANDROID_JAR =
-        new MethodAnnotation(false, true, false, false, -1, -1);
-
-    // ParallelStream methods are not supported when the runtime api level is strictly below 21.
-    final boolean parallelStreamMethod;
-    // Methods not in the latest android jar but still fully supported.
-    final boolean missingFromLatestAndroidJar;
-    // Methods not supported in a given min api range.
-    final boolean unsupportedInMinApiRange;
-    final boolean covariantReturnSupported;
-    final int minRange;
-    final int maxRange;
-
-    MethodAnnotation(
-        boolean parallelStreamMethod,
-        boolean missingFromLatestAndroidJar,
-        boolean covariantReturnSupported,
-        boolean unsupportedInMinApiRange,
-        int minRange,
-        int maxRange) {
-      this.parallelStreamMethod = parallelStreamMethod;
-      this.missingFromLatestAndroidJar = missingFromLatestAndroidJar;
-      this.covariantReturnSupported = covariantReturnSupported;
-      this.unsupportedInMinApiRange = unsupportedInMinApiRange;
-      this.minRange = minRange;
-      this.maxRange = maxRange;
-    }
-
-    public static MethodAnnotation getCovariantReturnSupported() {
-      return COVARIANT_RETURN_SUPPORTED;
-    }
-
-    public static MethodAnnotation getDefault() {
-      return DEFAULT;
-    }
-
-    public static MethodAnnotation getParallelStreamMethod() {
-      return PARALLEL_STREAM_METHOD;
-    }
-
-    public static MethodAnnotation getMissingFromLatestAndroidJar() {
-      return MISSING_FROM_LATEST_ANDROID_JAR;
-    }
-
-    public static MethodAnnotation createMissingInMinApi(int api) {
-      return new MethodAnnotation(false, false, false, true, api, api);
-    }
-
-    public boolean isUnsupportedInMinApiRange() {
-      return unsupportedInMinApiRange;
-    }
-
-    public int getMinRange() {
-      return minRange;
-    }
-
-    public int getMaxRange() {
-      return maxRange;
-    }
-
-    public boolean isCovariantReturnSupported() {
-      return covariantReturnSupported;
-    }
-
-    public MethodAnnotation combine(MethodAnnotation other) {
-      if (this == getDefault()) {
-        return other;
-      }
-      if (other == getDefault()) {
-        return this;
-      }
-      int newMin, newMax;
-      if (!unsupportedInMinApiRange && !other.unsupportedInMinApiRange) {
-        newMin = newMax = -1;
-      } else if (!unsupportedInMinApiRange || !other.unsupportedInMinApiRange) {
-        newMin = unsupportedInMinApiRange ? minRange : other.minRange;
-        newMax = unsupportedInMinApiRange ? maxRange : other.maxRange;
-      } else {
-        // Merge ranges if contiguous or throw.
-        if (maxRange == other.minRange - 1) {
-          newMin = minRange;
-          newMax = other.maxRange;
-        } else if (other.maxRange == minRange - 1) {
-          newMin = other.minRange;
-          newMax = maxRange;
-        } else {
-          // 20 is missing, so if maxRange or minRange are 19 the following is 21.
-          if (maxRange == 19 && other.minRange == 21) {
-            newMin = minRange;
-            newMax = other.maxRange;
-          } else if (other.maxRange == 19 && minRange == 21) {
-            newMin = other.minRange;
-            newMax = maxRange;
-          } else {
-            throw new RuntimeException("Cannot merge ranges.");
-          }
-        }
-      }
-      return new MethodAnnotation(
-          parallelStreamMethod || other.parallelStreamMethod,
-          missingFromLatestAndroidJar || other.missingFromLatestAndroidJar,
-          covariantReturnSupported || other.covariantReturnSupported,
-          unsupportedInMinApiRange || other.unsupportedInMinApiRange,
-          newMin,
-          newMax);
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 2c06cad..e7dfeee 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1012,6 +1012,11 @@
   // If non-null, configuration must be passed to the consumer.
   public StringConsumer configurationConsumer = null;
 
+  public void resetDesugaredLibrarySpecificationForTesting() {
+    loadMachineDesugaredLibrarySpecification = null;
+    machineDesugaredLibrarySpecification = MachineDesugaredLibrarySpecification.empty();
+  }
+
   public void setDesugaredLibrarySpecification(DesugaredLibrarySpecification specification) {
     if (specification.isEmpty()) {
       return;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java
index 2b52f07..7240267 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java
@@ -13,8 +13,8 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedMethodsGenerator;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedMethodsWithAnnotations;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
@@ -118,21 +118,23 @@
 
   @Test
   public void test() throws Exception {
-    SupportedMethodsWithAnnotations supportedMethods =
+    SupportedClasses supportedClasses =
         new SupportedMethodsGenerator(new InternalOptions())
             .run(librarySpecification.getDesugarJdkLibs(), librarySpecification.getSpecification());
 
     for (AndroidApiLevel api : getRelevantApiLevels()) {
       Set<DexMethod> localFailures = Sets.newIdentityHashSet();
-      supportedMethods.annotatedMethods.forEach(
-          (method, annotation) -> {
-            if (annotation.isUnsupportedInMinApiRange()) {
-              if (api.getLevel() >= annotation.getMinRange()
-                  && api.getLevel() <= annotation.getMaxRange()) {
-                localFailures.add(method);
-              }
-            }
-          });
+      supportedClasses.forEachClass(
+          supportedClass ->
+              supportedClass.forEachMethodAndAnnotation(
+                  (method, annotation) -> {
+                    if (annotation != null && annotation.isUnsupportedInMinApiRange()) {
+                      if (api.getLevel() >= annotation.getMinRange()
+                          && api.getLevel() <= annotation.getMaxRange()) {
+                        localFailures.add(method.getReference());
+                      }
+                    }
+                  }));
       Set<String> expectedFailures = getExpectedFailures(api);
       Set<String> apiFailuresString =
           localFailures.stream().map(DexMethod::toString).collect(Collectors.toSet());