Add android api database builders and initial tests for generated jar

Bug: 138781768
Bug: 190368382
Bug: 190187790
Bug: 188388130
Change-Id: I1d88285cb25b3515fcc059c7ec5ed21be4d9014a
diff --git a/build.gradle b/build.gradle
index cc51ea6..9032bc4 100644
--- a/build.gradle
+++ b/build.gradle
@@ -315,6 +315,7 @@
                 "android_jar/lib-v29",
                 "android_jar/lib-v30",
                 "android_jar/lib-v31",
+                "android_jar/api-versions",
                 "api-outlining/simple-app-dump",
                 "core-lambda-stubs",
                 "dart-sdk",
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java
new file mode 100644
index 0000000..4a9c41a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2021, 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.androidapi;
+
+import com.android.tools.r8.KeepForSubclassing;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.TraversalContinuation;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiFunction;
+
+/** This is a base class for all generated classes from api-versions.xml. */
+@KeepForSubclassing
+public abstract class AndroidApiClass {
+
+  private final ClassReference classReference;
+
+  public AndroidApiClass(ClassReference classReference) {
+    this.classReference = classReference;
+  }
+
+  public abstract AndroidApiLevel getApiLevel();
+
+  public abstract TraversalContinuation visitFields(
+      BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor);
+
+  public abstract TraversalContinuation visitMethods(
+      BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor);
+
+  protected TraversalContinuation visitField(
+      String name,
+      String typeDescriptor,
+      int apiLevel,
+      BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor) {
+    return visitor.apply(
+        Reference.field(classReference, name, Reference.typeFromDescriptor(typeDescriptor)),
+        AndroidApiLevel.getAndroidApiLevel(apiLevel));
+  }
+
+  protected TraversalContinuation visitMethod(
+      String name,
+      String[] formalTypeDescriptors,
+      String returnType,
+      int apiLevel,
+      BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor) {
+    List<TypeReference> typeReferenceList = new ArrayList<>(formalTypeDescriptors.length);
+    for (String formalTypeDescriptor : formalTypeDescriptors) {
+      typeReferenceList.add(Reference.typeFromDescriptor(formalTypeDescriptor));
+    }
+    return visitor.apply(
+        Reference.method(
+            classReference,
+            name,
+            typeReferenceList,
+            returnType == null ? null : Reference.returnTypeFromDescriptor(returnType)),
+        AndroidApiLevel.getAndroidApiLevel(apiLevel));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/references/Reference.java b/src/main/java/com/android/tools/r8/references/Reference.java
index 795c3bb..e6a829b 100644
--- a/src/main/java/com/android/tools/r8/references/Reference.java
+++ b/src/main/java/com/android/tools/r8/references/Reference.java
@@ -161,6 +161,22 @@
         returnTypeDescriptor.equals("V") ? null : typeFromDescriptor(returnTypeDescriptor));
   }
 
+  /** Get a method reference from class reference, method name and signature. */
+  public static MethodReference methodFromDescriptor(
+      ClassReference classReference, String methodName, String methodDescriptor) {
+    ImmutableList.Builder<TypeReference> builder = ImmutableList.builder();
+    for (String parameterTypeDescriptor :
+        DescriptorUtils.getArgumentTypeDescriptors(methodDescriptor)) {
+      builder.add(typeFromDescriptor(parameterTypeDescriptor));
+    }
+    String returnTypeDescriptor = DescriptorUtils.getReturnTypeDescriptor(methodDescriptor);
+    return method(
+        classReference,
+        methodName,
+        builder.build(),
+        returnTypeDescriptor.equals("V") ? null : typeFromDescriptor(returnTypeDescriptor));
+  }
+
   public static MethodReference classConstructor(ClassReference type) {
     return method(type, "<clinit>", Collections.emptyList(), null);
   }
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
index 6ea2f40..a85a657 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
@@ -3,12 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
+import com.android.tools.r8.Keep;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.utils.structural.Ordered;
 import java.util.Arrays;
 import java.util.List;
 
 /** Android API level description */
+@Keep
 public enum AndroidApiLevel implements Ordered<AndroidApiLevel> {
   B(1),
   B_1_1(2),
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
index 2616514..9a863be 100644
--- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -171,4 +171,8 @@
     newArray[ts.length] = element;
     return newArray;
   }
+
+  public static <T> T first(T[] ts) {
+    return ts[0];
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java b/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java
index 294a210..e3b1753 100644
--- a/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java
+++ b/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java
@@ -3,7 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
+import com.android.tools.r8.Keep;
+
 /** Two value continuation value to indicate the continuation of a loop/traversal. */
+/* This class is used for building up api class member traversals. */
+@Keep
 public enum TraversalContinuation {
   CONTINUE,
   BREAK;
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 19c77fb..e51cf99 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1708,6 +1708,21 @@
     return DescriptorUtils.javaTypeToDescriptor(typeName(clazz));
   }
 
+  public static String methodDescriptor(Method method) {
+    return Reference.methodFromMethod(method).getMethodDescriptor();
+  }
+
+  public static String methodDescriptor(Class<?> returnType, Class<?>... parameters) {
+    StringBuilder sb = new StringBuilder();
+    sb.append("(");
+    for (Class<?> parameter : parameters) {
+      sb.append(descriptor(parameter));
+    }
+    sb.append(")");
+    sb.append(returnType == null ? "V" : descriptor(returnType));
+    return sb.toString();
+  }
+
   public static PathOrigin getOrigin(Class<?> clazz) {
     return new PathOrigin(ToolHelper.getClassFileForTestClass(clazz));
   }
diff --git a/src/test/java/com/android/tools/r8/TestParametersBuilder.java b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
index e3570be..fddd0d4 100644
--- a/src/test/java/com/android/tools/r8/TestParametersBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
@@ -62,6 +62,10 @@
     return withCfRuntimeFilter(vm -> vm == runtime);
   }
 
+  public TestParametersBuilder withSystemRuntime() {
+    return withCfRuntimeFilter(TestParametersBuilder::isSystemJdk);
+  }
+
   /** Add all available CF runtimes. */
   public TestParametersBuilder withCfRuntimes() {
     return withCfRuntimeFilter(vm -> true);
diff --git a/src/test/java/com/android/tools/r8/TestRuntime.java b/src/test/java/com/android/tools/r8/TestRuntime.java
index 8b99ca8..a51fcf0 100644
--- a/src/test/java/com/android/tools/r8/TestRuntime.java
+++ b/src/test/java/com/android/tools/r8/TestRuntime.java
@@ -159,7 +159,7 @@
         .build();
   }
 
-  public static TestRuntime getSystemRuntime() {
+  public static CfRuntime getSystemRuntime() {
     String version = System.getProperty("java.version");
     String home = System.getProperty("java.home");
     if (version == null || version.isEmpty() || home == null || home.isEmpty()) {
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 50f5346..f3108e5 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -165,6 +165,8 @@
   public static final Path D8_JAR = Paths.get(LIBS_DIR, "d8.jar");
   public static final Path R8_JAR = Paths.get(LIBS_DIR, "r8.jar");
   public static final Path R8_WITH_DEPS_JAR = Paths.get(LIBS_DIR, "r8_with_deps.jar");
+  public static final Path R8_WITHOUT_DEPS_JAR =
+      Paths.get(LIBS_DIR, "r8_no_manifest_without_deps.jar");
   public static final Path R8_WITH_RELOCATED_DEPS_JAR =
       Paths.get(LIBS_DIR, "r8_with_relocated_deps.jar");
   public static final Path R8_WITH_DEPS_11_JAR = Paths.get(LIBS_DIR, "r8_with_deps_11.jar");
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java
new file mode 100644
index 0000000..5e75e09
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java
@@ -0,0 +1,468 @@
+// Copyright (c) 2021, 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.apimodel;
+
+import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
+import static org.objectweb.asm.Opcodes.AASTORE;
+import static org.objectweb.asm.Opcodes.ACONST_NULL;
+import static org.objectweb.asm.Opcodes.ALOAD;
+import static org.objectweb.asm.Opcodes.ANEWARRAY;
+import static org.objectweb.asm.Opcodes.ARETURN;
+import static org.objectweb.asm.Opcodes.DUP;
+import static org.objectweb.asm.Opcodes.F_SAME1;
+import static org.objectweb.asm.Opcodes.IFEQ;
+import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.INVOKESTATIC;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
+import static org.objectweb.asm.Opcodes.NEW;
+import static org.objectweb.asm.Opcodes.POP;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.androidapi.AndroidApiClass;
+import com.android.tools.r8.apimodel.AndroidApiVersionsXmlParser.ParsedApiClass;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.transformers.MethodTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.TraversalContinuation;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.Opcodes;
+
+public class AndroidApiDatabaseBuilderGenerator extends TestBase {
+
+  public static String generatedMainDescriptor() {
+    return descriptor(AndroidApiDatabaseBuilderTemplate.class).replace("Template", "");
+  }
+
+  /**
+   * Generate the classes needed for looking up api level of references in the android.jar.
+   *
+   * <p>For each api class we generate a from AndroidApiDatabaseClassTemplate, extending
+   * AndroidApiClass, such that all members can be traversed. Looking up the class reference
+   * directly in one method will generate to much code and would probably be inefficient so we first
+   * do a case on the package and then do a check on the simple name.
+   *
+   * <p>We therefore create a class file for each package, based on
+   * AndroidApiDatabasePackageTemplate, that will do the dispatch to create a new
+   * AndroidApiDatabaseClass for class in the package.
+   *
+   * <p>We then have a single entry-point, AndroidApiDatabaseBuilder, based on
+   * AndroidApiDatabaseBuilderTemplate, that will do the dispatch to AndroidApiDatabasePackage based
+   * on the package name.
+   */
+  public static void generate(List<ParsedApiClass> apiClasses, BiConsumer<String, byte[]> consumer)
+      throws Exception {
+    Map<String, List<ParsedApiClass>> packageToClassesMap = new HashMap<>();
+    List<String> packages =
+        apiClasses.stream()
+            .map(
+                apiClass -> {
+                  String packageName =
+                      DescriptorUtils.getPackageNameFromDescriptor(
+                          apiClass.getClassReference().getDescriptor());
+                  packageToClassesMap
+                      .computeIfAbsent(packageName, ignoreArgument(ArrayList::new))
+                      .add(apiClass);
+                  return packageName;
+                })
+            .sorted()
+            .distinct()
+            .collect(Collectors.toList());
+
+    for (ParsedApiClass apiClass : apiClasses) {
+      consumer.accept(
+          getApiClassDescriptor(apiClass),
+          transformer(AndroidApiDatabaseClassTemplate.class)
+              .setClassDescriptor(getApiClassDescriptor(apiClass))
+              .addMethodTransformer(getInitTransformer(apiClass))
+              .addMethodTransformer(getApiLevelTransformer(apiClass))
+              .addMethodTransformer(getVisitFieldsTransformer(apiClass))
+              .addMethodTransformer(getVisitMethodsTransformer(apiClass))
+              .removeMethods(MethodPredicate.onName("placeHolder"))
+              .removeMethods(MethodPredicate.onName("placeHolderForGetApiLevel"))
+              .removeMethods(MethodPredicate.onName("placeHolderForInit"))
+              .transform());
+    }
+
+    for (String pkg : packages) {
+      consumer.accept(
+          getPackageBuilderDescriptor(pkg),
+          transformer(AndroidApiDatabasePackageTemplate.class)
+              .setClassDescriptor(getPackageBuilderDescriptor(pkg))
+              .addMethodTransformer(getBuildClassTransformer(packageToClassesMap.get(pkg)))
+              .removeMethods(MethodPredicate.onName("placeHolder"))
+              .transform());
+    }
+
+    consumer.accept(
+        generatedMainDescriptor(),
+        transformer(AndroidApiDatabaseBuilderTemplate.class)
+            .setClassDescriptor(generatedMainDescriptor())
+            .addMethodTransformer(getVisitApiClassesTransformer(apiClasses))
+            .addMethodTransformer(getBuildPackageTransformer(packages))
+            .removeMethods(MethodPredicate.onName("placeHolder"))
+            .transform());
+  }
+
+  private static String getPackageBuilderDescriptor(String pkg) {
+    return DescriptorUtils.javaTypeToDescriptor(
+        AndroidApiDatabasePackageTemplate.class
+            .getTypeName()
+            .replace("Template", "ForPackage_" + pkg.replace(".", "_")));
+  }
+
+  private static String getApiClassDescriptor(ParsedApiClass apiClass) {
+    return DescriptorUtils.javaTypeToDescriptor(
+        AndroidApiDatabaseClassTemplate.class
+            .getTypeName()
+            .replace(
+                "Template",
+                "ForClass_" + apiClass.getClassReference().getTypeName().replace(".", "_")));
+  }
+
+  // The transformer below changes AndroidApiDatabaseClassTemplate.<init> from:
+  //     super(Reference.classFromDescriptor(placeHolderForInit()));
+  // into
+  //     super(Reference.classFromDescriptor("<class-descriptor>"));
+  private static MethodTransformer getInitTransformer(ParsedApiClass apiClass) {
+    return replaceCode(
+        "placeHolderForInit",
+        transformer -> {
+          transformer.visitLdcInsn(apiClass.getClassReference().getDescriptor());
+        });
+  }
+
+  // The transformer below changes AndroidApiDatabaseClassTemplate.getApiLevel from:
+  //     return placeHolderForGetApiLevel();
+  // into
+  //    return AndroidApiLevel.getAndroidApiLevel(<apiLevel>);
+  private static MethodTransformer getApiLevelTransformer(ParsedApiClass apiClass)
+      throws NoSuchMethodException {
+    Method getAndroidApiLevel = AndroidApiLevel.class.getMethod("getAndroidApiLevel", int.class);
+    return replaceCode(
+        "placeHolderForGetApiLevel",
+        transformer -> {
+          transformer.visitLdcInsn(apiClass.getApiLevel().getLevel());
+          transformer.visitMethodInsn(
+              INVOKESTATIC,
+              binaryName(AndroidApiLevel.class),
+              getAndroidApiLevel.getName(),
+              methodDescriptor(getAndroidApiLevel),
+              false);
+        });
+  }
+
+  // The transformer below changes AndroidApiDatabaseClassTemplate.visitFields from:
+  //     placeHolder();
+  //     return TraversalContinuation.CONTINUE;
+  // into
+  //    TraversalContinuation s1 = visitField("field1", "descriptor1", apiLevel1, visitor)
+  //    if (s1.shouldBreak()) {
+  //       return s1;
+  //    }
+  //    TraversalContinuation s2 = visitField("field2", "descriptor2", apiLevel2, visitor)
+  //    if (s2.shouldBreak()) {
+  //       return s2;
+  //    }
+  //    ...
+  //    return TraversalContinuation.CONTINUE;
+  private static MethodTransformer getVisitFieldsTransformer(ParsedApiClass apiClass)
+      throws NoSuchMethodException {
+    Method shouldBreak = TraversalContinuation.class.getMethod("shouldBreak");
+    return replaceCode(
+        "placeHolderForVisitFields",
+        transformer -> {
+          apiClass.visitFieldReferences(
+              (apiLevel, references) -> {
+                references.forEach(
+                    reference -> {
+                      transformer.visitVarInsn(ALOAD, 0);
+                      transformer.visitLdcInsn(reference.getFieldName());
+                      transformer.visitLdcInsn(reference.getFieldType().getDescriptor());
+                      transformer.visitLdcInsn(apiLevel.getLevel());
+                      transformer.visitVarInsn(ALOAD, 1);
+                      transformer.visitMethodInsn(
+                          INVOKEVIRTUAL,
+                          binaryName(AndroidApiClass.class),
+                          "visitField",
+                          methodDescriptor(
+                              TraversalContinuation.class,
+                              String.class,
+                              String.class,
+                              int.class,
+                              BiFunction.class),
+                          false);
+                      // Note that instead of storing the result here, we dup it on the stack.
+                      transformer.visitInsn(DUP);
+                      transformer.visitMethodInsn(
+                          INVOKEVIRTUAL,
+                          binaryName(TraversalContinuation.class),
+                          shouldBreak.getName(),
+                          methodDescriptor(shouldBreak),
+                          false);
+                      Label label = new Label();
+                      transformer.visitJumpInsn(IFEQ, label);
+                      transformer.visitInsn(ARETURN);
+                      transformer.visitLabel(label);
+                      transformer.visitFrame(
+                          F_SAME1,
+                          0,
+                          new Object[] {},
+                          1,
+                          new Object[] {binaryName(TraversalContinuation.class)});
+                      // The pop here is needed to remove the dupped value in the case we do not
+                      // return.
+                      transformer.visitInsn(POP);
+                    });
+              });
+        });
+  }
+
+  // The transformer below changes AndroidApiDatabaseClassTemplate.visitMethods from:
+  //     placeHolderForVisitMethods();
+  //     return TraversalContinuation.CONTINUE;
+  // into
+  //    TraversalContinuation s1 = visitMethod(
+  //      "method1", new String[] { "param11", ... , "param1X" }, null/return1, apiLevel1, visitor)
+  //    if (s1.shouldBreak()) {
+  //       return s1;
+  //    }
+  //    TraversalContinuation s1 = visitMethod(
+  //      "method2", new String[] { "param21", ... , "param2X" }, null/return2, apiLevel2, visitor)
+  //    if (s2.shouldBreak()) {
+  //       return s2;
+  //    }
+  //    ...
+  //    return TraversalContinuation.CONTINUE;
+  private static MethodTransformer getVisitMethodsTransformer(ParsedApiClass apiClass)
+      throws NoSuchMethodException {
+    Method shouldBreak = TraversalContinuation.class.getMethod("shouldBreak");
+    return replaceCode(
+        "placeHolderForVisitMethods",
+        transformer -> {
+          apiClass.visitMethodReferences(
+              (apiLevel, references) -> {
+                references.forEach(
+                    reference -> {
+                      transformer.visitVarInsn(ALOAD, 0);
+                      transformer.visitLdcInsn(reference.getMethodName());
+                      List<TypeReference> formalTypes = reference.getFormalTypes();
+                      transformer.visitLdcInsn(formalTypes.size());
+                      transformer.visitTypeInsn(ANEWARRAY, binaryName(String.class));
+                      for (int i = 0; i < formalTypes.size(); i++) {
+                        transformer.visitInsn(DUP);
+                        transformer.visitLdcInsn(i);
+                        transformer.visitLdcInsn(formalTypes.get(i).getDescriptor());
+                        transformer.visitInsn(AASTORE);
+                      }
+                      if (reference.getReturnType() != null) {
+                        transformer.visitLdcInsn(reference.getReturnType().getDescriptor());
+                      } else {
+                        transformer.visitInsn(ACONST_NULL);
+                      }
+                      transformer.visitLdcInsn(apiLevel.getLevel());
+                      transformer.visitVarInsn(ALOAD, 1);
+                      transformer.visitMethodInsn(
+                          INVOKEVIRTUAL,
+                          binaryName(AndroidApiClass.class),
+                          "visitMethod",
+                          methodDescriptor(
+                              TraversalContinuation.class,
+                              String.class,
+                              String[].class,
+                              String.class,
+                              int.class,
+                              BiFunction.class),
+                          false);
+                      // Note that instead of storing the result here, we dup it on the stack.
+                      transformer.visitInsn(DUP);
+                      transformer.visitMethodInsn(
+                          INVOKEVIRTUAL,
+                          binaryName(TraversalContinuation.class),
+                          shouldBreak.getName(),
+                          methodDescriptor(shouldBreak),
+                          false);
+                      Label label = new Label();
+                      transformer.visitJumpInsn(IFEQ, label);
+                      transformer.visitInsn(ARETURN);
+                      transformer.visitLabel(label);
+                      transformer.visitFrame(
+                          F_SAME1,
+                          0,
+                          new Object[] {},
+                          1,
+                          new Object[] {binaryName(TraversalContinuation.class)});
+                      // The pop here is needed to remove the dupped value in the case we do not
+                      // return.
+                      transformer.visitInsn(POP);
+                    });
+              });
+        });
+  }
+
+  // The transformer below changes AndroidApiDatabasePackageTemplate.buildClass from:
+  //    placeHolder();
+  //    return null;
+  // into
+  //    if ("<simple_class1>".equals(className)) {
+  //      return new AndroidApiClassForClass_class_name1();
+  //    }
+  //    if ("<simple_class2>".equals(className)) {
+  //      return new AndroidApiClassForClass_class_name2();
+  //    }
+  //    ...
+  //    return null;
+  private static MethodTransformer getBuildClassTransformer(List<ParsedApiClass> classesForPackage)
+      throws NoSuchMethodException {
+    Method equals = Object.class.getMethod("equals", Object.class);
+    return replaceCode(
+        "placeHolder",
+        transformer -> {
+          classesForPackage.forEach(
+              apiClass -> {
+                transformer.visitLdcInsn(
+                    DescriptorUtils.getSimpleClassNameFromDescriptor(
+                        apiClass.getClassReference().getDescriptor()));
+                transformer.visitVarInsn(ALOAD, 0);
+                transformer.visitMethodInsn(
+                    INVOKEVIRTUAL,
+                    binaryName(String.class),
+                    equals.getName(),
+                    methodDescriptor(equals),
+                    false);
+                Label label = new Label();
+                transformer.visitJumpInsn(IFEQ, label);
+                String binaryName =
+                    DescriptorUtils.getBinaryNameFromDescriptor(getApiClassDescriptor(apiClass));
+                transformer.visitTypeInsn(NEW, binaryName);
+                transformer.visitInsn(DUP);
+                transformer.visitMethodInsn(INVOKESPECIAL, binaryName, "<init>", "()V", false);
+                transformer.visitInsn(ARETURN);
+                transformer.visitLabel(label);
+                transformer.visitFrame(Opcodes.F_SAME, 0, new Object[] {}, 0, new Object[0]);
+              });
+        });
+  }
+
+  // The transformer below changes AndroidApiDatabaseBuilderTemplate.buildClass from:
+  //    String descriptor = classReference.getDescriptor();
+  //    String packageName = DescriptorUtils.getPackageNameFromDescriptor(descriptor);
+  //    String simpleClassName = DescriptorUtils.getSimpleClassNameFromDescriptor(descriptor);
+  //    placeHolderForBuildClass();
+  //    return null;
+  // into
+  //    String descriptor = classReference.getDescriptor();
+  //    String packageName = DescriptorUtils.getPackageNameFromDescriptor(descriptor);
+  //    String simpleClassName = DescriptorUtils.getSimpleClassNameFromDescriptor(descriptor);
+  //    if ("<package_name1>".equals(packageName)) {
+  //      return AndroidApiClassForPackage_package_name1(simpleClassName);
+  //    }
+  //    if ("<package_name2>".equals(simpleClassName)) {
+  //      return AndroidApiClassForPackage_package_name2(simpleClassName);
+  //    }
+  //    ...
+  //    return null;
+  private static MethodTransformer getBuildPackageTransformer(List<String> packages)
+      throws NoSuchMethodException {
+    Method equals = String.class.getMethod("equals", Object.class);
+    Method buildClass =
+        AndroidApiDatabasePackageTemplate.class.getMethod("buildClass", String.class);
+    return replaceCode(
+        "placeHolderForBuildClass",
+        transformer -> {
+          packages.forEach(
+              pkg -> {
+                transformer.visitLdcInsn(pkg);
+                transformer.visitVarInsn(ALOAD, 2);
+                transformer.visitMethodInsn(
+                    INVOKEVIRTUAL,
+                    binaryName(String.class),
+                    equals.getName(),
+                    methodDescriptor(equals),
+                    false);
+                Label label = new Label();
+                transformer.visitJumpInsn(IFEQ, label);
+                transformer.visitVarInsn(ALOAD, 3);
+                transformer.visitMethodInsn(
+                    INVOKESTATIC,
+                    DescriptorUtils.getBinaryNameFromDescriptor(getPackageBuilderDescriptor(pkg)),
+                    buildClass.getName(),
+                    methodDescriptor(buildClass),
+                    false);
+                transformer.visitInsn(ARETURN);
+                transformer.visitLabel(label);
+                transformer.visitFrame(
+                    Opcodes.F_FULL,
+                    4,
+                    new Object[] {
+                      binaryName(ClassReference.class),
+                      binaryName(String.class),
+                      binaryName(String.class),
+                      binaryName(String.class)
+                    },
+                    0,
+                    new Object[0]);
+              });
+        });
+  }
+
+  // The transformer below changes AndroidApiDatabaseBuilderTemplate.buildClass from:
+  //    placeHolderForVisitApiClasses();
+  // into
+  //    classDescriptorConsumer.accept("<descriptor_class_1>");
+  //    classDescriptorConsumer.accept("<descriptor_class_2>");
+  //    ...
+  //    return null;
+  private static MethodTransformer getVisitApiClassesTransformer(List<ParsedApiClass> apiClasses) {
+    return replaceCode(
+        "placeHolderForVisitApiClasses",
+        transformer -> {
+          apiClasses.forEach(
+              apiClass -> {
+                transformer.visitVarInsn(ALOAD, 0);
+                transformer.visitLdcInsn(apiClass.getClassReference().getDescriptor());
+                transformer.visitMethodInsn(
+                    INVOKEINTERFACE,
+                    binaryName(Consumer.class),
+                    "accept",
+                    "(Ljava/lang/Object;)V",
+                    true);
+              });
+        });
+  }
+
+  private static MethodTransformer replaceCode(
+      String placeholderName, Consumer<MethodTransformer> consumer) {
+    return new MethodTransformer() {
+
+      @Override
+      public void visitMaxs(int maxStack, int maxLocals) {
+        super.visitMaxs(-1, maxLocals);
+      }
+
+      @Override
+      public void visitMethodInsn(
+          int opcode, String owner, String name, String descriptor, boolean isInterface) {
+        if (name.equals(placeholderName)) {
+          consumer.accept(this);
+        } else {
+          super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+        }
+      }
+    };
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java
new file mode 100644
index 0000000..c47353e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java
@@ -0,0 +1,272 @@
+// Copyright (c) 2021, 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.apimodel;
+
+import static com.android.tools.r8.apimodel.AndroidApiDatabaseBuilderGenerator.generatedMainDescriptor;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.JvmTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.apimodel.AndroidApiVersionsXmlParser.ParsedApiClass;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.IntBox;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiConsumer;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AndroidApiDatabaseBuilderGeneratorTest extends TestBase {
+
+  protected final TestParameters parameters;
+  private static final Path API_VERSIONS_XML =
+      Paths.get(ToolHelper.THIRD_PARTY_DIR, "android_jar", "api-versions", "api-versions.xml");
+  private static final AndroidApiLevel API_LEVEL = AndroidApiLevel.R;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withSystemRuntime().build();
+  }
+
+  public AndroidApiDatabaseBuilderGeneratorTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private static Path generateJar() throws Exception {
+    return generateJar(
+        AndroidApiVersionsXmlParser.getParsedApiClasses(API_VERSIONS_XML.toFile(), API_LEVEL));
+  }
+
+  private static Path generateJar(List<ParsedApiClass> apiClasses) throws Exception {
+    TemporaryFolder temp = new TemporaryFolder();
+    temp.create();
+    ZipBuilder builder = ZipBuilder.builder(temp.newFile("out.jar").toPath());
+    AndroidApiDatabaseBuilderGenerator.generate(
+        apiClasses,
+        (descriptor, content) -> {
+          try {
+            String binaryName = DescriptorUtils.getBinaryNameFromDescriptor(descriptor) + ".class";
+            builder.addBytes(binaryName, content);
+          } catch (IOException exception) {
+            throw new RuntimeException(exception);
+          }
+        });
+    return builder.build();
+  }
+
+  public static void main(String[] args) throws Exception {
+    generateJar();
+  }
+
+  @Test
+  public void testDatabaseGenerationUpToDate() {
+    assumeTrue("b/190368382", false);
+  }
+
+  @Test
+  public void testCanParseApiVersionsXml() throws Exception {
+    // This tests makes a rudimentary check on the number of classes, fields and methods in
+    // api-versions.xml to ensure that the runtime tests do not vacuously succeed.
+    List<ParsedApiClass> parsedApiClasses =
+        AndroidApiVersionsXmlParser.getParsedApiClasses(API_VERSIONS_XML.toFile(), API_LEVEL);
+    IntBox numberOfFields = new IntBox(0);
+    IntBox numberOfMethods = new IntBox(0);
+    parsedApiClasses.forEach(
+        apiClass -> {
+          apiClass.visitFieldReferences(
+              ((apiLevel, fieldReferences) -> {
+                fieldReferences.forEach(field -> numberOfFields.increment());
+              }));
+          apiClass.visitMethodReferences(
+              ((apiLevel, methodReferences) -> {
+                methodReferences.forEach(field -> numberOfMethods.increment());
+              }));
+        });
+    // These numbers will change when updating api-versions.xml
+    assertEquals(4742, parsedApiClasses.size());
+    assertEquals(25144, numberOfFields.get());
+    assertEquals(38661, numberOfMethods.get());
+  }
+
+  @Test()
+  public void testGeneratedOutputForVisitClasses() throws Exception {
+    runTest(
+        TestGeneratedMainVisitClasses.class,
+        (parsedApiClasses, runResult) -> {
+          String expectedOutput =
+              StringUtils.lines(
+                  ListUtils.map(
+                      parsedApiClasses, apiClass -> apiClass.getClassReference().getDescriptor()));
+          runResult.assertSuccessWithOutput(expectedOutput);
+        });
+  }
+
+  @Test()
+  public void testBuildClassesContinue() throws Exception {
+    runTest(
+        TestBuildClassesContinue.class,
+        (parsedApiClasses, runResult) -> {
+          runResult.assertSuccessWithOutputLines(getExpected(parsedApiClasses, false));
+        });
+  }
+
+  @Test()
+  public void testBuildClassesBreak() throws Exception {
+    runTest(
+        TestBuildClassesBreak.class,
+        (parsedApiClasses, runResult) -> {
+          runResult.assertSuccessWithOutputLines(getExpected(parsedApiClasses, true));
+        });
+  }
+
+  private void runTest(
+      Class<?> testClass, BiConsumer<List<ParsedApiClass>, JvmTestRunResult> resultConsumer)
+      throws Exception {
+    List<ParsedApiClass> parsedApiClasses =
+        AndroidApiVersionsXmlParser.getParsedApiClasses(API_VERSIONS_XML.toFile(), API_LEVEL);
+    testForJvm()
+        .addProgramClassFileData(
+            transformer(testClass)
+                .replaceClassDescriptorInMethodInstructions(
+                    descriptor(AndroidApiDatabaseBuilderTemplate.class), generatedMainDescriptor())
+                .transform())
+        .addLibraryFiles(generateJar(parsedApiClasses))
+        // TODO(b/190368382): This will change when databasebuilder is included in deps.
+        .addLibraryFiles(ToolHelper.R8_WITHOUT_DEPS_JAR, ToolHelper.DEPS)
+        .addDefaultRuntimeLibrary(parameters)
+        .run(parameters.getRuntime(), testClass)
+        .apply(
+            result -> {
+              if (result.getExitCode() != 0) {
+                System.out.println(result.getStdErr());
+              }
+            })
+        .assertSuccess()
+        .apply(result -> resultConsumer.accept(parsedApiClasses, result));
+  }
+
+  private List<String> getExpected(List<ParsedApiClass> parsedApiClasses, boolean abort) {
+    List<String> expected = new ArrayList<>();
+    parsedApiClasses.forEach(
+        apiClass -> {
+          expected.add(apiClass.getClassReference().getDescriptor());
+          expected.add(apiClass.getApiLevel().getName());
+          BooleanBox added = new BooleanBox(false);
+          apiClass.visitFieldReferences(
+              (apiLevel, fieldReferences) -> {
+                fieldReferences.forEach(
+                    fieldReference -> {
+                      if (added.isTrue() && abort) {
+                        return;
+                      }
+                      added.set();
+                      expected.add(fieldReference.getFieldType().getDescriptor());
+                      expected.add(fieldReference.getFieldName());
+                      expected.add(apiLevel.getName());
+                    });
+              });
+          added.set(false);
+          apiClass.visitMethodReferences(
+              (apiLevel, methodReferences) -> {
+                methodReferences.forEach(
+                    methodReference -> {
+                      if (added.isTrue() && abort) {
+                        return;
+                      }
+                      added.set();
+                      expected.add(methodReference.getMethodDescriptor());
+                      expected.add(methodReference.getMethodName());
+                      expected.add(apiLevel.getName());
+                    });
+              });
+        });
+    return expected;
+  }
+
+  public static class TestGeneratedMainVisitClasses {
+
+    public static void main(String[] args) {
+      AndroidApiDatabaseBuilderTemplate.visitApiClasses(System.out::println);
+    }
+  }
+
+  public static class TestBuildClassesContinue {
+
+    public static void main(String[] args) {
+      AndroidApiDatabaseBuilderTemplate.visitApiClasses(
+          descriptor -> {
+            com.android.tools.r8.androidapi.AndroidApiClass apiClass =
+                AndroidApiDatabaseBuilderTemplate.buildClass(
+                    Reference.classFromDescriptor(descriptor));
+            if (apiClass != null) {
+              System.out.println(descriptor);
+              System.out.println(apiClass.getApiLevel().getName());
+              apiClass.visitFields(
+                  (reference, apiLevel) -> {
+                    System.out.println(reference.getFieldType().getDescriptor());
+                    System.out.println(reference.getFieldName());
+                    System.out.println(apiLevel.getName());
+                    return TraversalContinuation.CONTINUE;
+                  });
+              apiClass.visitMethods(
+                  (reference, apiLevel) -> {
+                    System.out.println(reference.getMethodDescriptor());
+                    System.out.println(reference.getMethodName());
+                    System.out.println(apiLevel.getName());
+                    return TraversalContinuation.CONTINUE;
+                  });
+            }
+          });
+    }
+  }
+
+  public static class TestBuildClassesBreak {
+
+    public static void main(String[] args) {
+      AndroidApiDatabaseBuilderTemplate.visitApiClasses(
+          descriptor -> {
+            com.android.tools.r8.androidapi.AndroidApiClass apiClass =
+                AndroidApiDatabaseBuilderTemplate.buildClass(
+                    Reference.classFromDescriptor(descriptor));
+            if (apiClass != null) {
+              System.out.println(descriptor);
+              System.out.println(apiClass.getApiLevel().getName());
+              apiClass.visitFields(
+                  (reference, apiLevel) -> {
+                    System.out.println(reference.getFieldType().getDescriptor());
+                    System.out.println(reference.getFieldName());
+                    System.out.println(apiLevel.getName());
+                    return TraversalContinuation.BREAK;
+                  });
+              apiClass.visitMethods(
+                  (reference, apiLevel) -> {
+                    System.out.println(reference.getMethodDescriptor());
+                    System.out.println(reference.getMethodName());
+                    System.out.println(apiLevel.getName());
+                    return TraversalContinuation.BREAK;
+                  });
+            }
+          });
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderTemplate.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderTemplate.java
new file mode 100644
index 0000000..4b21f6e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderTemplate.java
@@ -0,0 +1,32 @@
+// Copyright (c) 2021, 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.apimodel;
+
+import com.android.tools.r8.androidapi.AndroidApiClass;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.function.Consumer;
+
+/** This is a template for generating the AndroidApiDatabaseBuilder. */
+public class AndroidApiDatabaseBuilderTemplate /* AndroidApiDatabaseBuilder */ {
+
+  public static void visitApiClasses(Consumer<String> classDescriptorConsumer) {
+    // Code added dynamically in AndroidApiDatabaseBuilderGenerator.
+    placeHolderForVisitApiClasses();
+  }
+
+  public static AndroidApiClass buildClass(ClassReference classReference) {
+    String descriptor = classReference.getDescriptor();
+    String packageName = DescriptorUtils.getPackageNameFromDescriptor(descriptor);
+    String simpleClassName = DescriptorUtils.getSimpleClassNameFromDescriptor(descriptor);
+    // Code added dynamically in AndroidApiDatabaseBuilderGenerator.
+    placeHolderForBuildClass();
+    return null;
+  }
+
+  private static void placeHolderForVisitApiClasses() {}
+
+  private static void placeHolderForBuildClass() {}
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseClassTemplate.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseClassTemplate.java
new file mode 100644
index 0000000..2598898
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseClassTemplate.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2021, 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.apimodel;
+
+import com.android.tools.r8.androidapi.AndroidApiClass;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.TraversalContinuation;
+import java.util.function.BiFunction;
+
+/** This is a template for generating AndroidApiDatabaseClass extending AndroidApiClass */
+public class AndroidApiDatabaseClassTemplate extends AndroidApiClass {
+
+  protected AndroidApiDatabaseClassTemplate() {
+    // Code added dynamically in AndroidApiDatabaseBuilderGenerator.
+    super(Reference.classFromDescriptor(placeHolderForInit()));
+  }
+
+  @Override
+  public AndroidApiLevel getApiLevel() {
+    // Code added dynamically in AndroidApiDatabaseBuilderGenerator.
+    return placeHolderForGetApiLevel();
+  }
+
+  @Override
+  public TraversalContinuation visitFields(
+      BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor) {
+    // Code added dynamically in AndroidApiDatabaseBuilderGenerator.
+    placeHolderForVisitFields();
+    return TraversalContinuation.CONTINUE;
+  }
+
+  @Override
+  public TraversalContinuation visitMethods(
+      BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor) {
+    // Code added dynamically in AndroidApiDatabaseBuilderGenerator.
+    placeHolderForVisitMethods();
+    return TraversalContinuation.CONTINUE;
+  }
+
+  private static String placeHolderForInit() {
+    return null;
+  }
+
+  private static AndroidApiLevel placeHolderForGetApiLevel() {
+    return null;
+  }
+
+  private static void placeHolderForVisitFields() {}
+
+  private static void placeHolderForVisitMethods() {}
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabasePackageTemplate.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabasePackageTemplate.java
new file mode 100644
index 0000000..3919d11
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabasePackageTemplate.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2021, 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.apimodel;
+
+import com.android.tools.r8.androidapi.AndroidApiClass;
+
+/** This is a template for generating the AndroidApiDatabasePackage. */
+public class AndroidApiDatabasePackageTemplate {
+
+  public static AndroidApiClass buildClass(String className) {
+    // Code added dynamically in AndroidApiDatabaseBuilderGenerator.
+    placeHolder();
+    return null;
+  }
+
+  private static void placeHolder() {}
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
new file mode 100644
index 0000000..c904dff
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
@@ -0,0 +1,175 @@
+// Copyright (c) 2021, 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.apimodel;
+
+import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.function.BiConsumer;
+import javax.xml.parsers.DocumentBuilderFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+public class AndroidApiVersionsXmlParser {
+
+  private final List<ParsedApiClass> classes = new ArrayList<>();
+
+  private final File apiVersionsXml;
+  private final AndroidApiLevel maxApiLevel;
+
+  private AndroidApiVersionsXmlParser(File apiVersionsXml, AndroidApiLevel maxApiLevel) {
+    this.apiVersionsXml = apiVersionsXml;
+    this.maxApiLevel = maxApiLevel;
+  }
+
+  private ParsedApiClass register(ClassReference reference, AndroidApiLevel apiLevel) {
+    ParsedApiClass parsedApiClass = new ParsedApiClass(reference, apiLevel);
+    classes.add(parsedApiClass);
+    return parsedApiClass;
+  }
+
+  private void readApiVersionsXmlFile() throws Exception {
+    CodeInspector inspector = new CodeInspector(ToolHelper.getAndroidJar(maxApiLevel));
+    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+    Document document = factory.newDocumentBuilder().parse(apiVersionsXml);
+    NodeList classes = document.getElementsByTagName("class");
+    for (int i = 0; i < classes.getLength(); i++) {
+      Node node = classes.item(i);
+      assert node.getNodeType() == Node.ELEMENT_NODE;
+      AndroidApiLevel apiLevel = getMaxAndroidApiLevelFromNode(node, AndroidApiLevel.B);
+      String type = DescriptorUtils.getJavaTypeFromBinaryName(getName(node));
+      ClassSubject clazz = inspector.clazz(type);
+      if (!clazz.isPresent()) {
+        // TODO(b/190326408): Investigate why the class is not present.
+        continue;
+      }
+      ClassReference originalReference = clazz.getOriginalReference();
+      ParsedApiClass parsedApiClass = register(originalReference, apiLevel);
+      NodeList members = node.getChildNodes();
+      for (int j = 0; j < members.getLength(); j++) {
+        Node memberNode = members.item(j);
+        if (isMethod(memberNode)) {
+          // TODO(b/190326408): Check for existence.
+          parsedApiClass.register(
+              getMethodReference(originalReference, memberNode),
+              getMaxAndroidApiLevelFromNode(memberNode, apiLevel));
+        } else if (isField(memberNode)) {
+          // The field do not have descriptors and are supposed to be unique.
+          FieldSubject fieldSubject = clazz.uniqueFieldWithName(getName(memberNode));
+          if (!fieldSubject.isPresent()) {
+            // TODO(b/190326408): Investigate why the member is not present.
+            continue;
+          }
+          parsedApiClass.register(
+              fieldSubject.getOriginalReference(),
+              getMaxAndroidApiLevelFromNode(memberNode, apiLevel));
+        }
+      }
+    }
+  }
+
+  private boolean isMethod(Node node) {
+    return node.getNodeName().equals("method");
+  }
+
+  private String getName(Node node) {
+    return node.getAttributes().getNamedItem("name").getNodeValue();
+  }
+
+  private MethodReference getMethodReference(ClassReference classDescriptor, Node node) {
+    assert isMethod(node);
+    String name = getName(node);
+    int signatureStart = name.indexOf('(');
+    assert signatureStart > 0;
+    String parsedName = name.substring(0, signatureStart).replace("&lt;", "<");
+    assert !parsedName.contains("&");
+    return Reference.methodFromDescriptor(
+        classDescriptor.getDescriptor(), parsedName, name.substring(signatureStart));
+  }
+
+  private boolean isField(Node node) {
+    return node.getNodeName().equals("field");
+  }
+
+  private AndroidApiLevel getMaxAndroidApiLevelFromNode(Node node, AndroidApiLevel defaultValue) {
+    if (node == null) {
+      return defaultValue;
+    }
+    Node since = node.getAttributes().getNamedItem("since");
+    if (since == null) {
+      return defaultValue;
+    }
+    return defaultValue.max(
+        AndroidApiLevel.getAndroidApiLevel(Integer.parseInt(since.getNodeValue())));
+  }
+
+  public static List<ParsedApiClass> getParsedApiClasses(
+      File apiVersionsXml, AndroidApiLevel apiLevel) throws Exception {
+    AndroidApiVersionsXmlParser parser = new AndroidApiVersionsXmlParser(apiVersionsXml, apiLevel);
+    parser.readApiVersionsXmlFile();
+    return parser.classes;
+  }
+
+  public static class ParsedApiClass {
+
+    private final ClassReference classReference;
+    private final AndroidApiLevel apiLevel;
+    private final TreeMap<AndroidApiLevel, List<FieldReference>> fieldReferences = new TreeMap<>();
+    private final Map<AndroidApiLevel, List<MethodReference>> methodReferences = new TreeMap<>();
+
+    public ClassReference getClassReference() {
+      return classReference;
+    }
+
+    public AndroidApiLevel getApiLevel() {
+      return apiLevel;
+    }
+
+    private ParsedApiClass(ClassReference classReference, AndroidApiLevel apiLevel) {
+      this.classReference = classReference;
+      this.apiLevel = apiLevel;
+    }
+
+    private void register(FieldReference reference, AndroidApiLevel apiLevel) {
+      fieldReferences.computeIfAbsent(apiLevel, ignoreArgument(ArrayList::new)).add(reference);
+    }
+
+    private void register(MethodReference reference, AndroidApiLevel apiLevel) {
+      methodReferences.computeIfAbsent(apiLevel, ignoreArgument(ArrayList::new)).add(reference);
+    }
+
+    public void visitFieldReferences(BiConsumer<AndroidApiLevel, List<FieldReference>> consumer) {
+      fieldReferences.forEach(
+          (apiLevel, references) -> {
+            references.sort(Comparator.comparing(FieldReference::getFieldName));
+            consumer.accept(apiLevel, references);
+          });
+    }
+
+    public void visitMethodReferences(BiConsumer<AndroidApiLevel, List<MethodReference>> consumer) {
+      methodReferences.forEach(
+          (apiLevel, references) -> {
+            references.sort(Comparator.comparing(MethodReference::getMethodName));
+            consumer.accept(apiLevel, references);
+          });
+    }
+  }
+}