Import upstream master

Change-Id: I678e1ff829ee3a05091c1487c90f29734dd93077
diff --git a/build.xml b/build.xml
index af28222..2a571f9 100644
--- a/build.xml
+++ b/build.xml
@@ -13,6 +13,16 @@
             <compilerarg value="-Xlint"/>
             <classpath>
                 <pathelement location="build/dx/classes" />
+                <pathelement location="lib/mockito-core-1.9.1-SNAPSHOT.jar" />
+            </classpath>
+        </javac>
+        <mkdir dir="build/mockito/classes"/>
+        <javac srcdir="src/mockito/java" includes="**" destdir="build/mockito/classes"
+               debug="on" source="1.5" target="1.5" includeantruntime="false">
+            <compilerarg value="-Xlint"/>
+            <classpath>
+                <pathelement location="build/main/classes" />
+                <pathelement location="lib/mockito-core-1.9.1-SNAPSHOT.jar" />
             </classpath>
         </javac>
         <mkdir dir="build/test/classes"/>
@@ -32,6 +42,8 @@
         <jarjar jarfile="build/dexmaker.jar">
             <fileset dir="build/dx/classes"/>
             <fileset dir="build/main/classes"/>
+            <fileset dir="build/mockito/classes"/>
+            <fileset dir="src/mockito/resources"/>
             <rule pattern="com.android.dx.**" result="com.google.dexmaker.dx.@1"/>
         </jarjar>
     </target>
diff --git a/lib/mockito-core-1.9.1-SNAPSHOT.jar b/lib/mockito-core-1.9.1-SNAPSHOT.jar
new file mode 100644
index 0000000..7d05e5e
--- /dev/null
+++ b/lib/mockito-core-1.9.1-SNAPSHOT.jar
Binary files differ
diff --git a/src/main/java/com/google/dexmaker/AppDataDirGuesser.java b/src/main/java/com/google/dexmaker/AppDataDirGuesser.java
index 2492ea0..b59670e 100644
--- a/src/main/java/com/google/dexmaker/AppDataDirGuesser.java
+++ b/src/main/java/com/google/dexmaker/AppDataDirGuesser.java
@@ -17,6 +17,7 @@
 package com.google.dexmaker;
 
 import java.io.File;
+import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -31,7 +32,7 @@
             Class<?> clazz = Class.forName("dalvik.system.PathClassLoader");
             clazz.cast(classLoader);
             // Use the toString() method to calculate the data directory.
-            String pathFromThisClassLoader = getPathFromThisClassLoader(classLoader);
+            String pathFromThisClassLoader = getPathFromThisClassLoader(classLoader, clazz);
             File[] results = guessPath(pathFromThisClassLoader);
             if (results.length > 0) {
                 return results[0];
@@ -46,7 +47,19 @@
         return AppDataDirGuesser.class.getClassLoader();
     }
 
-    private String getPathFromThisClassLoader(ClassLoader classLoader) {
+    private String getPathFromThisClassLoader(ClassLoader classLoader,
+            Class<?> pathClassLoaderClass) {
+        // Prior to ICS, we can simply read the "path" field of the
+        // PathClassLoader.
+        try {
+            Field pathField = pathClassLoaderClass.getDeclaredField("path");
+            pathField.setAccessible(true);
+            return (String) pathField.get(classLoader);
+        } catch (NoSuchFieldException ignored) {
+        } catch (IllegalAccessException ignored) {
+        } catch (ClassCastException ignored) {
+        }
+
         // Parsing toString() method: yuck.  But no other way to get the path.
         // Strip out the bit between angle brackets, that's our path.
         String result = classLoader.toString();
@@ -58,6 +71,12 @@
 
     File[] guessPath(String input) {
         List<File> results = new ArrayList<File>();
+        // Post JB, the system property is set to the applications private data cache
+        // directory.
+        File tmpDir = new File(System.getProperty("java.io.tmpdir"));
+        if (isWriteableDirectory(tmpDir)) {
+            results.add(tmpDir);
+        }
         for (String potential : input.split(":")) {
             if (!potential.startsWith("/data/app/")) {
                 continue;
@@ -71,14 +90,25 @@
             if (dash != -1) {
                 end = dash;
             }
-            File file = new File("/data/data/" + potential.substring(start, end) + "/cache");
-            if (isWriteableDirectory(file)) {
-                results.add(file);
+            String packageName = potential.substring(start, end);
+            File dataDir = new File("/data/data/" + packageName);
+            if (isWriteableDirectory(dataDir)) {
+                File cacheDir = new File(dataDir, "cache");
+                // The cache directory might not exist -- create if necessary
+                if (fileOrDirExists(cacheDir) || cacheDir.mkdir()) {
+                    if (isWriteableDirectory(cacheDir)) {
+                        results.add(cacheDir);
+                    }
+                }
             }
         }
         return results.toArray(new File[results.size()]);
     }
 
+    boolean fileOrDirExists(File file) {
+        return file.exists();
+    }
+
     boolean isWriteableDirectory(File file) {
         return file.isDirectory() && file.canWrite();
     }
diff --git a/src/mockito/java/com/google/dexmaker/mockito/DexmakerMockMaker.java b/src/mockito/java/com/google/dexmaker/mockito/DexmakerMockMaker.java
new file mode 100644
index 0000000..bf33342
--- /dev/null
+++ b/src/mockito/java/com/google/dexmaker/mockito/DexmakerMockMaker.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.dexmaker.mockito;
+
+import com.google.dexmaker.stock.ProxyBuilder;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Proxy;
+import java.util.Set;
+import org.mockito.exceptions.base.MockitoException;
+import org.mockito.invocation.MockHandler;
+import org.mockito.mock.MockCreationSettings;
+import org.mockito.plugins.MockMaker;
+
+/**
+ * Generates mock instances on Android's runtime.
+ */
+public final class DexmakerMockMaker implements MockMaker {
+    private final UnsafeAllocator unsafeAllocator = UnsafeAllocator.create();
+
+    public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {
+        Class<T> typeToMock = settings.getTypeToMock();
+        Set<Class> interfacesSet = settings.getExtraInterfaces();
+        Class<?>[] extraInterfaces = interfacesSet.toArray(new Class[interfacesSet.size()]);
+        InvocationHandler invocationHandler = new InvocationHandlerAdapter(handler);
+
+        if (typeToMock.isInterface()) {
+            // support interfaces via java.lang.reflect.Proxy
+            Class[] classesToMock = new Class[extraInterfaces.length + 1];
+            classesToMock[0] = typeToMock;
+            System.arraycopy(extraInterfaces, 0, classesToMock, 1, extraInterfaces.length);
+            @SuppressWarnings("unchecked") // newProxyInstance returns the type of typeToMock
+            T mock = (T) Proxy.newProxyInstance(typeToMock.getClassLoader(),
+                    classesToMock, invocationHandler);
+            return mock;
+
+        } else {
+            // support concrete classes via dexmaker's ProxyBuilder
+            try {
+                Class<? extends T> proxyClass = ProxyBuilder.forClass(typeToMock)
+                        .implementing(extraInterfaces)
+                        .buildProxyClass();
+                T mock = unsafeAllocator.newInstance(proxyClass);
+                Field handlerField = proxyClass.getDeclaredField("$__handler");
+                handlerField.setAccessible(true);
+                handlerField.set(mock, invocationHandler);
+                return mock;
+            } catch (RuntimeException e) {
+                throw e;
+            } catch (Exception e) {
+                throw new MockitoException("Failed to mock " + typeToMock, e);
+            }
+        }
+    }
+
+    public void resetMock(Object mock, MockHandler newHandler, MockCreationSettings settings) {
+        InvocationHandlerAdapter adapter = getInvocationHandlerAdapter(mock);
+        adapter.setHandler(newHandler);
+    }
+
+    public MockHandler getHandler(Object mock) {
+        InvocationHandlerAdapter adapter = getInvocationHandlerAdapter(mock);
+        return adapter != null ? adapter.getHandler() : null;
+    }
+
+    private InvocationHandlerAdapter getInvocationHandlerAdapter(Object mock) {
+        if (Proxy.isProxyClass(mock.getClass())) {
+            InvocationHandler invocationHandler = Proxy.getInvocationHandler(mock);
+            return invocationHandler instanceof InvocationHandlerAdapter
+                    ? (InvocationHandlerAdapter) invocationHandler
+                    : null;
+        }
+
+        if (ProxyBuilder.isProxyClass(mock.getClass())) {
+            InvocationHandler invocationHandler = ProxyBuilder.getInvocationHandler(mock);
+            return invocationHandler instanceof InvocationHandlerAdapter
+                    ? (InvocationHandlerAdapter) invocationHandler
+                    : null;
+        }
+
+        return null;
+    }
+}
diff --git a/src/mockito/java/com/google/dexmaker/mockito/InvocationHandlerAdapter.java b/src/mockito/java/com/google/dexmaker/mockito/InvocationHandlerAdapter.java
new file mode 100644
index 0000000..268f2fd
--- /dev/null
+++ b/src/mockito/java/com/google/dexmaker/mockito/InvocationHandlerAdapter.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.dexmaker.mockito;
+
+import com.google.dexmaker.stock.ProxyBuilder;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import org.mockito.internal.invocation.InvocationImpl;
+import org.mockito.internal.invocation.MockitoMethod;
+import org.mockito.internal.invocation.realmethod.RealMethod;
+import org.mockito.internal.progress.SequenceNumber;
+import org.mockito.internal.util.ObjectMethodsGuru;
+import org.mockito.invocation.MockHandler;
+
+/**
+ * Handles proxy method invocations to dexmaker's InvocationHandler by calling
+ * a MockitoInvocationHandler.
+ */
+final class InvocationHandlerAdapter implements InvocationHandler {
+    private MockHandler handler;
+    private final ObjectMethodsGuru objectMethodsGuru = new ObjectMethodsGuru();
+
+    public InvocationHandlerAdapter(MockHandler handler) {
+        this.handler = handler;
+    }
+
+    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+        if (objectMethodsGuru.isEqualsMethod(method)) {
+            return proxy == args[0];
+        } else if (objectMethodsGuru.isHashCodeMethod(method)) {
+            return System.identityHashCode(proxy);
+        }
+
+        if (args == null) {
+            throw new IllegalArgumentException();
+        }
+
+
+        ProxiedMethod proxiedMethod = new ProxiedMethod(method);
+        return handler.handle(new InvocationImpl(proxy, proxiedMethod, args, SequenceNumber.next(),
+                proxiedMethod));
+    }
+
+    public MockHandler getHandler() {
+        return handler;
+    }
+
+    public void setHandler(MockHandler handler) {
+        this.handler = handler;
+    }
+
+    private static class ProxiedMethod implements MockitoMethod, RealMethod {
+        private final Method method;
+
+        public ProxiedMethod(Method method) {
+            this.method = method;
+        }
+
+        public String getName() {
+            return method.getName();
+        }
+
+        public Class<?> getReturnType() {
+            return method.getReturnType();
+        }
+
+        public Class<?>[] getParameterTypes() {
+            return method.getParameterTypes();
+        }
+
+        public Class<?>[] getExceptionTypes() {
+            return method.getExceptionTypes();
+        }
+
+        public boolean isVarArgs() {
+            return method.isVarArgs();
+        }
+
+        public Method getJavaMethod() {
+            return method;
+        }
+
+        public Object invoke(Object target, Object[] arguments) throws Throwable {
+            return ProxyBuilder.callSuper(target, method, arguments);
+        }
+    }
+}
diff --git a/src/mockito/java/com/google/dexmaker/mockito/UnsafeAllocator.java b/src/mockito/java/com/google/dexmaker/mockito/UnsafeAllocator.java
new file mode 100644
index 0000000..2e10b41
--- /dev/null
+++ b/src/mockito/java/com/google/dexmaker/mockito/UnsafeAllocator.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.dexmaker.mockito;
+
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamClass;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+/**
+ * Do sneaky things to allocate objects without invoking their constructors.
+ * This is like objenesis but it works on Android. Derived from Gson's
+ * UnsafeAllocator.
+ */
+abstract class UnsafeAllocator {
+    public abstract <T> T newInstance(Class<T> c) throws Exception;
+
+    public static UnsafeAllocator create() {
+        // try JVM
+        // public class Unsafe {
+        //   public Object allocateInstance(Class<?> type);
+        // }
+        try {
+            Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
+            Field f = unsafeClass.getDeclaredField("theUnsafe");
+            f.setAccessible(true);
+            final Object unsafe = f.get(null);
+            final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class);
+            return new UnsafeAllocator() {
+                @Override
+                @SuppressWarnings("unchecked")
+                public <T> T newInstance(Class<T> c) throws Exception {
+                    return (T) allocateInstance.invoke(unsafe, c);
+                }
+            };
+        } catch (Exception ignored) {
+        }
+
+        // try dalvikvm, pre-gingerbread
+        // public class ObjectInputStream {
+        //   private static native Object newInstance(
+        //     Class<?> instantiationClass, Class<?> constructorClass);
+        // }
+        try {
+            final Method newInstance = ObjectInputStream.class
+                    .getDeclaredMethod("newInstance", Class.class, Class.class);
+            newInstance.setAccessible(true);
+            return new UnsafeAllocator() {
+                @Override
+                @SuppressWarnings("unchecked")
+                public <T> T newInstance(Class<T> c) throws Exception {
+                    return (T) newInstance.invoke(null, c, Object.class);
+                }
+            };
+        } catch (Exception ignored) {
+        }
+
+        // try dalvikvm, post-gingerbread
+        // public class ObjectStreamClass {
+        //   private static native int getConstructorId(Class<?> c);
+        //   private static native Object newInstance(Class<?> instantiationClass, int methodId);
+        // }
+        try {
+            Method getConstructorId = ObjectStreamClass.class
+                    .getDeclaredMethod("getConstructorId", Class.class);
+            getConstructorId.setAccessible(true);
+            final int constructorId = (Integer) getConstructorId.invoke(null, Object.class);
+            final Method newInstance = ObjectStreamClass.class
+                    .getDeclaredMethod("newInstance", Class.class, int.class);
+            newInstance.setAccessible(true);
+            return new UnsafeAllocator() {
+                @Override
+                @SuppressWarnings("unchecked")
+                public <T> T newInstance(Class<T> c) throws Exception {
+                    return (T) newInstance.invoke(null, c, constructorId);
+                }
+            };
+        } catch (Exception ignored) {
+        }
+
+        // give up
+        return new UnsafeAllocator() {
+            @Override
+            public <T> T newInstance(Class<T> c) {
+                throw new UnsupportedOperationException("Cannot allocate " + c);
+            }
+        };
+    }
+}
\ No newline at end of file
diff --git a/src/mockito/resources/mockito-extensions/org.mockito.plugins.MockMaker b/src/mockito/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..9f6df44
--- /dev/null
+++ b/src/mockito/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+com.google.dexmaker.mockito.DexmakerMockMaker
diff --git a/src/test/java/com/google/dexmaker/AppDataDirGuesserTest.java b/src/test/java/com/google/dexmaker/AppDataDirGuesserTest.java
index 5c92f34..36ac383 100644
--- a/src/test/java/com/google/dexmaker/AppDataDirGuesserTest.java
+++ b/src/test/java/com/google/dexmaker/AppDataDirGuesserTest.java
@@ -80,6 +80,10 @@
                     public boolean isWriteableDirectory(File file) {
                         return !notWriteable.contains(file.getAbsolutePath());
                     }
+                    @Override
+                    boolean fileOrDirExists(File file) {
+                        return true;
+                    }
                 };
                 File[] results = guesser.guessPath(path);
                 assertNotNull("Null results for " + path, results);