Refactor ClassPath to take ClassProviders

This allows for a more general mechanism for providing defined classes
diff --git a/baksmali/src/test/java/org/jf/baksmali/FieldGapOrderTest.java b/baksmali/src/test/java/org/jf/baksmali/FieldGapOrderTest.java
index 0684131..78fabc0 100644
--- a/baksmali/src/test/java/org/jf/baksmali/FieldGapOrderTest.java
+++ b/baksmali/src/test/java/org/jf/baksmali/FieldGapOrderTest.java
@@ -34,6 +34,7 @@
 import com.google.common.collect.Lists;
 import org.jf.dexlib2.analysis.ClassPath;
 import org.jf.dexlib2.analysis.ClassProto;
+import org.jf.dexlib2.analysis.DexClassProvider;
 import org.jf.dexlib2.iface.DexFile;
 import org.junit.Assert;
 import org.junit.Test;
@@ -44,7 +45,7 @@
         DexFile dexFile = getInputDexFile("FieldGapOrder", new baksmaliOptions());
         Assert.assertEquals(3, dexFile.getClasses().size());
 
-        ClassPath classPath = new ClassPath(Lists.newArrayList(dexFile), false, 66);
+        ClassPath classPath = new ClassPath(Lists.newArrayList(new DexClassProvider(dexFile)), false, 66);
         ClassProto classProto = (ClassProto)classPath.getClass("LGapOrder;");
         Assert.assertEquals("r1", classProto.getFieldByOffset(12).getName());
         Assert.assertEquals("r2", classProto.getFieldByOffset(16).getName());
@@ -58,7 +59,7 @@
         DexFile dexFile = getInputDexFile("FieldGapOrder", new baksmaliOptions());
         Assert.assertEquals(3, dexFile.getClasses().size());
 
-        ClassPath classPath = new ClassPath(Lists.newArrayList(dexFile), false, 67);
+        ClassPath classPath = new ClassPath(Lists.newArrayList(new DexClassProvider(dexFile)), false, 67);
         ClassProto classProto = (ClassProto)classPath.getClass("LGapOrder;");
         Assert.assertEquals("s", classProto.getFieldByOffset(10).getName());
         Assert.assertEquals("r1", classProto.getFieldByOffset(12).getName());
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java
index cf75dff..9f9e396 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java
@@ -36,7 +36,9 @@
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
-import com.google.common.collect.*;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
 import org.jf.dexlib2.DexFileFactory;
 import org.jf.dexlib2.DexFileFactory.DexFileNotFound;
 import org.jf.dexlib2.DexFileFactory.MultipleDexFilesException;
@@ -52,63 +54,41 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.Arrays;
 import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 public class ClassPath {
     @Nonnull private final TypeProto unknownClass;
-    @Nonnull private HashMap<String, ClassDef> availableClasses = Maps.newHashMap();
+    @Nonnull private List<ClassProvider> classProviders;
     private final boolean checkPackagePrivateAccess;
     public final int oatVersion;
 
     public static final int NOT_ART = -1;
 
     /**
-     * Creates a new ClassPath instance that can load classes from the given dex files
+     * Creates a new ClassPath instance that can load classes from the given providers
      *
-     * @param classPath An array of DexFile objects. When loading a class, these dex files will be searched in order
+     * @param classProviders An iterable of ClassProviders. When loading a class, these providers will be searched in
+     *                       order
      */
-    public ClassPath(DexFile... classPath) throws IOException {
-        this(Lists.newArrayList(classPath), 15);
+    public ClassPath(ClassProvider... classProviders) throws IOException {
+        this(Arrays.asList(classProviders), false, NOT_ART);
     }
 
     /**
-     * Creates a new ClassPath instance that can load classes from the given dex files
+     * Creates a new ClassPath instance that can load classes from the given providers
      *
-     * @param classPath An iterable of DexFile objects. When loading a class, these dex files will be searched in order
-     * @param api API level
-     */
-    public ClassPath(@Nonnull Iterable<? extends DexFile> classPath, int api) {
-        this(Lists.newArrayList(classPath), api == 17);
-    }
-
-    /**
-     * Creates a new ClassPath instance that can load classes from the given dex files
-     *
-     * @param classPath An iterable of DexFile objects. When loading a class, these dex files will be searched in order
-     * @param checkPackagePrivateAccess Whether checkPackagePrivateAccess is needed, enabled for ONLY early API 17 by
-     *                                  default
-     */
-    public ClassPath(@Nonnull Iterable<? extends DexFile> classPath, boolean checkPackagePrivateAccess) {
-        this(classPath, checkPackagePrivateAccess, NOT_ART);
-    }
-
-    /**
-     * Creates a new ClassPath instance that can load classes from the given dex files
-     *
-     * @param classPath An iterable of DexFile objects. When loading a class, these dex files will be searched in order
+     * @param classProviders An iterable of ClassProviders. When loading a class, these providers will be searched in
+     *                       order
      * @param checkPackagePrivateAccess Whether checkPackagePrivateAccess is needed, enabled for ONLY early API 17 by
      *                                  default
      * @param oatVersion The applicable oat version, or NOT_ART
      */
-    public ClassPath(@Nonnull Iterable<? extends DexFile> classPath, boolean checkPackagePrivateAccess,
+    public ClassPath(@Nonnull Iterable<? extends ClassProvider> classProviders, boolean checkPackagePrivateAccess,
                      int oatVersion) {
         // add fallbacks for certain special classes that must be present
-        Iterable<DexFile> dexFiles = Iterables.concat(classPath, Lists.newArrayList(getBasicClasses()));
-
         unknownClass = new UnknownClassProto(this);
         loadedClasses.put(unknownClass.getType(), unknownClass);
         this.checkPackagePrivateAccess = checkPackagePrivateAccess;
@@ -124,30 +104,23 @@
         loadPrimitiveType("D");
         loadPrimitiveType("L");
 
-        for (DexFile dexFile: dexFiles) {
-            for (ClassDef classDef: dexFile.getClasses()) {
-                ClassDef prev = availableClasses.get(classDef.getType());
-                if (prev == null) {
-                    availableClasses.put(classDef.getType(), classDef);
-                }
-            }
-        }
+        this.classProviders = Lists.newArrayList(classProviders);
+        this.classProviders.add(getBasicClasses());
     }
 
     private void loadPrimitiveType(String type) {
         loadedClasses.put(type, new PrimitiveProto(this, type));
     }
 
-    private static DexFile getBasicClasses() {
+    private static ClassProvider getBasicClasses() {
         // fallbacks for some special classes that we assume are present
-        return new ImmutableDexFile(Opcodes.forApi(19),
-                ImmutableSet.of(
-                        new ReflectionClassDef(Class.class),
-                        new ReflectionClassDef(Cloneable.class),
-                        new ReflectionClassDef(Object.class),
-                        new ReflectionClassDef(Serializable.class),
-                        new ReflectionClassDef(String.class),
-                        new ReflectionClassDef(Throwable.class)));
+        return new DexClassProvider(new ImmutableDexFile(Opcodes.forApi(19), ImmutableSet.of(
+                new ReflectionClassDef(Class.class),
+                new ReflectionClassDef(Cloneable.class),
+                new ReflectionClassDef(Object.class),
+                new ReflectionClassDef(Serializable.class),
+                new ReflectionClassDef(String.class),
+                new ReflectionClassDef(Throwable.class))));
     }
 
     public boolean isArt() {
@@ -173,11 +146,13 @@
 
     @Nonnull
     public ClassDef getClassDef(String type) {
-        ClassDef ret = availableClasses.get(type);
-        if (ret == null) {
-            throw new UnresolvedClassException("Could not resolve class %s", type);
+        for (ClassProvider provider: classProviders) {
+            ClassDef classDef = provider.getClassDef(type);
+            if (classDef != null) {
+                return classDef;
+            }
         }
-        return ret;
+        throw new UnresolvedClassException("Could not resolve class %s", type);
     }
 
     @Nonnull
@@ -198,7 +173,7 @@
     @Nonnull
     public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile,
                                           int api, boolean checkPackagePrivateAccess, boolean experimental) {
-        ArrayList<DexFile> dexFiles = Lists.newArrayList();
+        List<ClassProvider> providers = Lists.newArrayList();
 
         int oatVersion = NOT_ART;
 
@@ -213,23 +188,29 @@
                     }
                 }
             }
-            dexFiles.addAll(classPathDexFiles);
+            for (DexFile classPathDexFile: classPathDexFiles) {
+                providers.add(new DexClassProvider(classPathDexFile));
+            }
         }
-        dexFiles.add(dexFile);
-        return new ClassPath(dexFiles, checkPackagePrivateAccess, oatVersion);
+        providers.add(new DexClassProvider(dexFile));
+        return new ClassPath(providers, checkPackagePrivateAccess, oatVersion);
     }
 
     @Nonnull
     public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile,
                                           int api, boolean checkPackagePrivateAccess, boolean experimental,
                                           int oatVersion) {
-        ArrayList<DexFile> dexFiles = Lists.newArrayList();
+        List<ClassProvider> providers = Lists.newArrayList();
 
         for (String classPathEntry: classPath) {
-            dexFiles.addAll(loadClassPathEntry(classPathDirs, classPathEntry, api, experimental));
+            List<? extends DexFile> classPathDexFiles =
+                    loadClassPathEntry(classPathDirs, classPathEntry, api, experimental);
+            for (DexFile classPathDexFile: classPathDexFiles) {
+                providers.add(new DexClassProvider(classPathDexFile));
+            }
         }
-        dexFiles.add(dexFile);
-        return new ClassPath(dexFiles, checkPackagePrivateAccess, oatVersion);
+        providers.add(new DexClassProvider(dexFile));
+        return new ClassPath(providers, checkPackagePrivateAccess, oatVersion);
     }
 
     private static final Pattern dalvikCacheOdexPattern = Pattern.compile("@([^@]+)@classes.dex$");
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProvider.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProvider.java
new file mode 100644
index 0000000..7c823ff
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProvider.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.dexlib2.analysis;
+
+import org.jf.dexlib2.iface.ClassDef;
+
+import javax.annotation.Nullable;
+
+public interface ClassProvider {
+    @Nullable ClassDef getClassDef(String type);
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/DexClassProvider.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/DexClassProvider.java
new file mode 100644
index 0000000..c460cc3
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/DexClassProvider.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.dexlib2.analysis;
+
+import com.google.common.collect.Maps;
+import org.jf.dexlib2.iface.ClassDef;
+import org.jf.dexlib2.iface.DexFile;
+
+import javax.annotation.Nullable;
+import java.util.Map;
+
+public class DexClassProvider implements ClassProvider {
+    private final DexFile dexFile;
+    private Map<String, ClassDef> classMap = Maps.newHashMap();
+
+    public DexClassProvider(DexFile dexFile) {
+        this.dexFile = dexFile;
+
+        for (ClassDef classDef: dexFile.getClasses()) {
+            classMap.put(classDef.getType(), classDef);
+        }
+    }
+
+    @Nullable @Override public ClassDef getClassDef(String type) {
+        return classMap.get(type);
+    }
+}
diff --git a/dexlib2/src/test/java/org/jf/dexlib2/analysis/CommonSuperclassTest.java b/dexlib2/src/test/java/org/jf/dexlib2/analysis/CommonSuperclassTest.java
index 521290f..3f1ee56 100644
--- a/dexlib2/src/test/java/org/jf/dexlib2/analysis/CommonSuperclassTest.java
+++ b/dexlib2/src/test/java/org/jf/dexlib2/analysis/CommonSuperclassTest.java
@@ -54,7 +54,7 @@
     private final ClassPath classPath;
 
     public CommonSuperclassTest() throws IOException {
-        classPath = new ClassPath(new ImmutableDexFile(Opcodes.forApi(19),
+        classPath = new ClassPath(new DexClassProvider(new ImmutableDexFile(Opcodes.forApi(19),
                 ImmutableSet.of(
                         TestUtils.makeClassDef("Ljava/lang/Object;", null),
                         TestUtils.makeClassDef("Ltest/one;", "Ljava/lang/Object;"),
@@ -89,7 +89,7 @@
                         TestUtils.makeClassDef("Liface/classsubsub4;", "Liface/classsub4;"),
                         TestUtils.makeClassDef("Liface/classsub1234;", "Ljava/lang/Object;", "Liface/sub1;",
                                 "Liface/sub2;", "Liface/sub3;", "Liface/sub4;")
-        )));
+        ))));
     }
 
     public void superclassTest(String commonSuperclass,
diff --git a/dexlib2/src/test/java/org/jf/dexlib2/analysis/util/SuperclassChainTest.java b/dexlib2/src/test/java/org/jf/dexlib2/analysis/util/SuperclassChainTest.java
index c9a9af9..84cd284 100644
--- a/dexlib2/src/test/java/org/jf/dexlib2/analysis/util/SuperclassChainTest.java
+++ b/dexlib2/src/test/java/org/jf/dexlib2/analysis/util/SuperclassChainTest.java
@@ -36,6 +36,7 @@
 import junit.framework.Assert;
 import org.jf.dexlib2.Opcodes;
 import org.jf.dexlib2.analysis.ClassPath;
+import org.jf.dexlib2.analysis.DexClassProvider;
 import org.jf.dexlib2.analysis.TestUtils;
 import org.jf.dexlib2.analysis.TypeProto;
 import org.jf.dexlib2.iface.ClassDef;
@@ -46,7 +47,6 @@
 
 public class SuperclassChainTest {
 
-
     @Test
     public void testGetSuperclassChain() throws IOException {
         ClassDef objectClassDef = TestUtils.makeClassDef("Ljava/lang/Object;", null);
@@ -57,7 +57,7 @@
         ImmutableSet<ClassDef> classes = ImmutableSet.<ClassDef>of(
                 objectClassDef, oneClassDef, twoClassDef, threeClassDef);
 
-        ClassPath classPath = new ClassPath(new ImmutableDexFile(Opcodes.forApi(19), classes));
+        ClassPath classPath = new ClassPath(new DexClassProvider(new ImmutableDexFile(Opcodes.forApi(19), classes)));
 
         TypeProto objectClassProto = classPath.getClass("Ljava/lang/Object;");
         TypeProto oneClassProto = classPath.getClass("Ltest/one;");
@@ -88,7 +88,7 @@
         ClassDef twoClassDef = TestUtils.makeClassDef("Ltest/two;", "Ltest/one;");
         ClassDef threeClassDef = TestUtils.makeClassDef("Ltest/three;", "Ltest/two;");
         ImmutableSet<ClassDef> classes = ImmutableSet.<ClassDef>of(twoClassDef, threeClassDef);
-        ClassPath classPath = new ClassPath(new ImmutableDexFile(Opcodes.forApi(19), classes));
+        ClassPath classPath = new ClassPath(new DexClassProvider(new ImmutableDexFile(Opcodes.forApi(19), classes)));
 
         TypeProto unknownClassProto = classPath.getUnknownClass();
         TypeProto oneClassProto = classPath.getClass("Ltest/one;");