Implement a cache for proxy classes.
diff --git a/src/main/java/com/google/dexmaker/stock/ProxyBuilder.java b/src/main/java/com/google/dexmaker/stock/ProxyBuilder.java
index 2b40160..f5b9c39 100644
--- a/src/main/java/com/google/dexmaker/stock/ProxyBuilder.java
+++ b/src/main/java/com/google/dexmaker/stock/ProxyBuilder.java
@@ -37,6 +37,7 @@
 import static java.lang.reflect.Modifier.STATIC;
 import java.lang.reflect.UndeclaredThrowableException;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -116,6 +117,14 @@
     private static final String FIELD_NAME_HANDLER = "$__handler";
     private static final String FIELD_NAME_METHODS = "$__methodArray";
 
+    /**
+     * A cache of all proxy classes ever generated. At the time of writing,
+     * Android's runtime doesn't support class unloading so there's little
+     * value in using weak references.
+     */
+    private static final Map<Class<?>, Class<?>> generatedProxyClasses
+            = Collections.synchronizedMap(new HashMap<Class<?>, Class<?>>());
+
     private final Class<T> baseClass;
     // TODO: make DexMaker do the defaulting here
     private ClassLoader parentClassLoader = ProxyBuilder.class.getClassLoader();
@@ -177,31 +186,12 @@
         check(handler != null, "handler == null");
         check(constructorArgTypes.length == constructorArgValues.length,
                 "constructorArgValues.length != constructorArgTypes.length");
-        DexMaker dexMaker = new DexMaker();
-        String generatedName = getMethodNameForProxyOf(baseClass);
-        TypeId<? extends T> generatedType = TypeId.get("L" + generatedName + ";");
-        TypeId<T> superType = TypeId.get(baseClass);
-        generateConstructorsAndFields(dexMaker, generatedType, superType, baseClass);
-        Method[] methodsToProxy = getMethodsToProxy(baseClass);
-        generateCodeForAllMethods(dexMaker, generatedType, methodsToProxy, superType);
-        dexMaker.declare(generatedType, generatedName + ".generated", PUBLIC, superType);
-        ClassLoader classLoader = dexMaker.generateAndLoad(parentClassLoader, dexCache);
-        Class<? extends T> proxyClass;
-        try {
-            proxyClass = loadClass(classLoader, generatedName);
-        } catch (IllegalAccessError e) {
-            // Thrown when the base class is not accessible.
-            throw new UnsupportedOperationException("cannot proxy inaccessible classes", e);
-        } catch (ClassNotFoundException e) {
-            // Should not be thrown, we're sure to have generated this class.
-            throw new AssertionError(e);
-        }
-        setMethodsStaticField(proxyClass, methodsToProxy);
+        Class<? extends T> proxyClass = getProxyClass();
         Constructor<? extends T> constructor;
         try {
             constructor = proxyClass.getConstructor(constructorArgTypes);
         } catch (NoSuchMethodException e) {
-            // Thrown when the ctor to be called does not exist.
+            // Thrown when the constructor to be called does not exist.
             throw new IllegalArgumentException("could not find matching constructor", e);
         }
         T result;
@@ -214,13 +204,45 @@
             // Should not be thrown, the generated constructor is accessible.
             throw new AssertionError(e);
         } catch (InvocationTargetException e) {
-            // Thrown when the base class ctor throws an exception.
+            // Thrown when the base class constructor throws an exception.
             throw launderCause(e);
         }
         setHandlerInstanceField(result, handler);
         return result;
     }
 
+    private Class<? extends T> getProxyClass() throws IOException {
+        // try the cache to see if we've generated this one before
+        @SuppressWarnings("unchecked") // we only populate the map with matching types
+        Class<? extends T> proxyClass = (Class) generatedProxyClasses.get(baseClass);
+        if (proxyClass != null && proxyClass.getClassLoader().getParent() == parentClassLoader) {
+            return proxyClass; // cache hit!
+        }
+
+        // the cache missed; generate the class
+        DexMaker dexMaker = new DexMaker();
+        String generatedName = getMethodNameForProxyOf(baseClass);
+        TypeId<? extends T> generatedType = TypeId.get("L" + generatedName + ";");
+        TypeId<T> superType = TypeId.get(baseClass);
+        generateConstructorsAndFields(dexMaker, generatedType, superType, baseClass);
+        Method[] methodsToProxy = getMethodsToProxy(baseClass);
+        generateCodeForAllMethods(dexMaker, generatedType, methodsToProxy, superType);
+        dexMaker.declare(generatedType, generatedName + ".generated", PUBLIC, superType);
+        ClassLoader classLoader = dexMaker.generateAndLoad(parentClassLoader, dexCache);
+        try {
+            proxyClass = loadClass(classLoader, generatedName);
+        } catch (IllegalAccessError e) {
+            // Thrown when the base class is not accessible.
+            throw new UnsupportedOperationException("cannot proxy inaccessible classes", e);
+        } catch (ClassNotFoundException e) {
+            // Should not be thrown, we're sure to have generated this class.
+            throw new AssertionError(e);
+        }
+        setMethodsStaticField(proxyClass, methodsToProxy);
+        generatedProxyClasses.put(baseClass, proxyClass);
+        return proxyClass;
+    }
+
     // The type cast is safe: the generated type will extend the base class type.
     @SuppressWarnings("unchecked")
     private Class<? extends T> loadClass(ClassLoader classLoader, String generatedName)
diff --git a/src/test/java/com/google/dexmaker/stock/ProxyBuilderTest.java b/src/test/java/com/google/dexmaker/stock/ProxyBuilderTest.java
index 84937ae..ce2f9e4 100644
--- a/src/test/java/com/google/dexmaker/stock/ProxyBuilderTest.java
+++ b/src/test/java/com/google/dexmaker/stock/ProxyBuilderTest.java
@@ -398,12 +398,17 @@
 
     public void testIllegalCacheDirectory() throws Exception {
         try {
-          proxyFor(Object.class).dexCache(new File("//////")).build();
+          proxyFor(ProxyForIllegalCacheDirectory.class)
+                  .dexCache(new File("/poop/"))
+                  .build();
           fail();
         } catch (IOException expected) {
         }
     }
 
+    public static class ProxyForIllegalCacheDirectory {
+    }
+
     public void testInvalidConstructorSpecification() throws Exception {
         try {
             proxyFor(Object.class)
@@ -501,9 +506,18 @@
         } catch (ClassCastException expected) {}
     }
 
-    public void testCaching_ShouldWork() {
-        // TODO: We're not supporting caching yet.  But we should as soon as possible.
-        fail();
+    public void testCaching() throws Exception {
+        SimpleClass proxy1 = proxyFor(SimpleClass.class).build();
+        SimpleClass proxy2 = proxyFor(SimpleClass.class).build();
+        assertSame(proxy1.getClass(), proxy2.getClass());
+    }
+
+    public void testCachingWithMultipleConstructors() throws Exception {
+        fail("TODO");
+    }
+
+    public void testCachingWithDifferentParentClassLoaders() throws Exception {
+        fail("TODO");
     }
 
     public void testSubclassOfRandom() throws Exception {