[ApiModel] Redefine interface for android api database

This is a precursor for changing the internals of the database

Bug: 199934316
Change-Id: I0efa182f63c09fb211fb37533dd3bce6d8310868
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabase.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabase.java
new file mode 100644
index 0000000..5dda285
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabase.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.androidapi;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.AndroidApiLevel;
+
+public interface AndroidApiLevelDatabase {
+
+  AndroidApiLevel getTypeApiLevel(DexType type);
+
+  AndroidApiLevel getMethodApiLevel(DexMethod method);
+
+  AndroidApiLevel getFieldApiLevel(DexField field);
+}
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseImpl.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseImpl.java
new file mode 100644
index 0000000..d98013e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseImpl.java
@@ -0,0 +1,120 @@
+// 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.apimodel.AndroidApiDatabaseBuilder;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+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.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.TraversalContinuation;
+import java.util.HashMap;
+import java.util.function.BiFunction;
+
+public class AndroidApiLevelDatabaseImpl implements AndroidApiLevelDatabase {
+
+  private final HashMap<DexType, AndroidApiClass> predefinedApiTypeLookup;
+
+  private final AndroidApiClass SENTINEL =
+      new AndroidApiClass(null) {
+
+        @Override
+        public AndroidApiLevel getApiLevel() {
+          return null;
+        }
+
+        @Override
+        public int getMemberCount() {
+          return 0;
+        }
+
+        @Override
+        protected TraversalContinuation visitFields(
+            BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor,
+            ClassReference holder,
+            int minApiClass) {
+          return null;
+        }
+
+        @Override
+        protected TraversalContinuation visitMethods(
+            BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor,
+            ClassReference holder,
+            int minApiClass) {
+          return null;
+        }
+      };
+
+  public AndroidApiLevelDatabaseImpl(HashMap<DexType, AndroidApiClass> predefinedApiTypeLookup) {
+    this.predefinedApiTypeLookup = predefinedApiTypeLookup;
+  }
+
+  @Override
+  public AndroidApiLevel getTypeApiLevel(DexType type) {
+    return lookupDefinedApiLevel(type);
+  }
+
+  @Override
+  public AndroidApiLevel getMethodApiLevel(DexMethod method) {
+    return lookupDefinedApiLevel(method);
+  }
+
+  @Override
+  public AndroidApiLevel getFieldApiLevel(DexField field) {
+    return lookupDefinedApiLevel(field);
+  }
+
+  private AndroidApiLevel lookupDefinedApiLevel(DexReference reference) {
+    AndroidApiClass foundClass =
+        predefinedApiTypeLookup.getOrDefault(reference.getContextType(), SENTINEL);
+    if (foundClass == null) {
+      return AndroidApiLevel.UNKNOWN;
+    }
+    AndroidApiClass androidApiClass;
+    if (foundClass == SENTINEL) {
+      androidApiClass =
+          AndroidApiDatabaseBuilder.buildClass(reference.getContextType().asClassReference());
+      if (androidApiClass == null) {
+        predefinedApiTypeLookup.put(reference.getContextType(), null);
+        return AndroidApiLevel.UNKNOWN;
+      }
+    } else {
+      androidApiClass = foundClass;
+    }
+    return reference.apply(
+        type -> androidApiClass.getApiLevel(),
+        field -> {
+          FieldReference fieldReference = field.asFieldReference();
+          Box<AndroidApiLevel> apiLevelBox = new Box<>(AndroidApiLevel.UNKNOWN);
+          androidApiClass.visitFields(
+              (fieldRef, apiLevel) -> {
+                if (fieldReference.equals(fieldRef)) {
+                  apiLevelBox.set(apiLevel);
+                  return TraversalContinuation.BREAK;
+                }
+                return TraversalContinuation.CONTINUE;
+              });
+          return apiLevelBox.get();
+        },
+        method -> {
+          MethodReference methodReference = method.asMethodReference();
+          Box<AndroidApiLevel> apiLevelBox = new Box<>(AndroidApiLevel.UNKNOWN);
+          androidApiClass.visitMethods(
+              (methodRef, apiLevel) -> {
+                if (methodReference.equals(methodRef)) {
+                  apiLevelBox.set(apiLevel);
+                  return TraversalContinuation.BREAK;
+                }
+                return TraversalContinuation.CONTINUE;
+              });
+          return apiLevelBox.get();
+        });
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
index e3fa030..8ca1b59 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
@@ -4,37 +4,28 @@
 
 package com.android.tools.r8.androidapi;
 
-import com.android.tools.r8.apimodel.AndroidApiDatabaseBuilder;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.Box;
-import com.android.tools.r8.utils.TraversalContinuation;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.HashMap;
 
 public class AndroidApiReferenceLevelCache {
 
-  private static final int BUILD_CACHE_TRESHOLD = 20;
-
-  private final ConcurrentHashMap<DexType, AndroidApiClass> apiTypeLookup;
-  private final ConcurrentHashMap<DexReference, AndroidApiLevel> apiMemberLookup =
-      new ConcurrentHashMap<>();
   private final DesugaredLibraryConfiguration desugaredLibraryConfiguration;
+  private final AndroidApiLevelDatabase androidApiLevelDatabase;
   private final AppView<?> appView;
 
   private AndroidApiReferenceLevelCache(AppView<?> appView) {
-    this(appView, new ConcurrentHashMap<>());
+    this(appView, new HashMap<>());
   }
 
   private AndroidApiReferenceLevelCache(
-      AppView<?> appView, ConcurrentHashMap<DexType, AndroidApiClass> apiTypeLookup) {
+      AppView<?> appView, HashMap<DexType, AndroidApiClass> predefinedApiTypeLookup) {
     this.appView = appView;
-    this.apiTypeLookup = apiTypeLookup;
+    androidApiLevelDatabase = new AndroidApiLevelDatabaseImpl(predefinedApiTypeLookup);
     desugaredLibraryConfiguration = appView.options().desugaredLibraryConfiguration;
   }
 
@@ -51,16 +42,16 @@
     }
     // The apiTypeLookup is build lazily except for the mocked api types that we define in tests
     // externally.
-    ConcurrentHashMap<DexType, AndroidApiClass> apiTypeLookup = new ConcurrentHashMap<>();
+    HashMap<DexType, AndroidApiClass> predefinedApiTypeLookup = new HashMap<>();
     appView
         .options()
         .apiModelingOptions()
         .visitMockedApiReferences(
             (classReference, androidApiClass) ->
-                apiTypeLookup.put(
+                predefinedApiTypeLookup.put(
                     appView.dexItemFactory().createType(classReference.getDescriptor()),
                     androidApiClass));
-    return new AndroidApiReferenceLevelCache(appView, apiTypeLookup);
+    return new AndroidApiReferenceLevelCache(appView, predefinedApiTypeLookup);
   }
 
   public AndroidApiLevel lookupMax(DexReference reference, AndroidApiLevel minApiLevel) {
@@ -87,73 +78,9 @@
       // of the program.
       return appView.options().minApiLevel;
     }
-    AndroidApiClass androidApiClass =
-        apiTypeLookup.computeIfAbsent(
-            contextType, type -> AndroidApiDatabaseBuilder.buildClass(type.asClassReference()));
-    if (androidApiClass == null) {
-      // This is a library class but we have no api model for it. This happens if using an older
-      // version of R8 to compile a new target. We simply have to disallow inlining of methods
-      // that has such references.
-      return AndroidApiLevel.UNKNOWN;
-    }
-    if (reference.isDexType()) {
-      return androidApiClass.getApiLevel();
-    }
-    return androidApiClass.getMemberCount() > BUILD_CACHE_TRESHOLD
-        ? findMemberByCaching(reference, androidApiClass)
-        : findMemberByIteration(reference.asDexMember(), androidApiClass);
-  }
-
-  private AndroidApiLevel findMemberByIteration(
-      DexMember<?, ?> reference, AndroidApiClass apiClass) {
-    DexItemFactory factory = appView.dexItemFactory();
-    // Similar to the case for api classes we are unable to find, if the member
-    // is unknown we have to be conservative.
-    Box<AndroidApiLevel> apiLevelBox = new Box<>(AndroidApiLevel.UNKNOWN);
-    reference.apply(
-        field ->
-            apiClass.visitFields(
-                (fieldReference, apiLevel) -> {
-                  if (factory.createField(fieldReference) == field) {
-                    apiLevelBox.set(apiLevel);
-                    return TraversalContinuation.BREAK;
-                  }
-                  return TraversalContinuation.CONTINUE;
-                }),
-        method ->
-            apiClass.visitMethods(
-                (methodReference, apiLevel) -> {
-                  if (factory.createMethod(methodReference) == method) {
-                    apiLevelBox.set(apiLevel);
-                    return TraversalContinuation.BREAK;
-                  }
-                  return TraversalContinuation.CONTINUE;
-                }));
-    return apiLevelBox.get();
-  }
-
-  private AndroidApiLevel findMemberByCaching(DexReference reference, AndroidApiClass apiClass) {
-    buildCacheForMembers(reference.getContextType(), apiClass);
-    return apiMemberLookup.getOrDefault(reference, AndroidApiLevel.UNKNOWN);
-  }
-
-  private void buildCacheForMembers(DexType context, AndroidApiClass apiClass) {
-    assert apiClass.getMemberCount() > BUILD_CACHE_TRESHOLD;
-    // Use the context type as a token for us having build a cache for it.
-    if (apiMemberLookup.containsKey(context)) {
-      return;
-    }
-    DexItemFactory factory = appView.dexItemFactory();
-    apiClass.visitFields(
-        (fieldReference, apiLevel) -> {
-          apiMemberLookup.put(factory.createField(fieldReference), apiLevel);
-          return TraversalContinuation.CONTINUE;
-        });
-    apiClass.visitMethods(
-        (methodReference, apiLevel) -> {
-          apiMemberLookup.put(factory.createMethod(methodReference), apiLevel);
-          return TraversalContinuation.CONTINUE;
-        });
-    apiMemberLookup.put(context, AndroidApiLevel.UNKNOWN);
+    return reference.apply(
+        androidApiLevelDatabase::getTypeApiLevel,
+        androidApiLevelDatabase::getFieldApiLevel,
+        androidApiLevelDatabase::getMethodApiLevel);
   }
 }