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);