Native mockito support. This depends on a few things not yet available in Mockito's HEAD.
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/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..4621d93
--- /dev/null
+++ b/src/mockito/java/com/google/dexmaker/mockito/DexmakerMockMaker.java
@@ -0,0 +1,98 @@
+/*
+ * 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 org.mockito.exceptions.base.MockitoException;
+import org.mockito.plugins.MockMaker;
+import org.mockito.plugins.MockSettingsInfo;
+import org.mockito.plugins.MockitoInvocationHandler;
+
+/**
+ * Generates mock instances on Android's runtime.
+ */
+public final class DexmakerMockMaker implements MockMaker {
+ private final UnsafeAllocator unsafeAllocator = UnsafeAllocator.create();
+
+ public <T> T createMock(Class<T> typeToMock, Class<?>[] extraInterfaces,
+ MockitoInvocationHandler handler, MockSettingsInfo settings) {
+ // TODO
+ System.setProperty("dexmaker.dexcache", "/data/vogar"); // TODO
+ // TODO
+
+ 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, MockitoInvocationHandler newHandler, MockSettingsInfo settings) {
+ InvocationHandlerAdapter adapter = getInvocationHandlerAdapter(mock);
+ adapter.setHandler(newHandler);
+ }
+
+ public MockitoInvocationHandler 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..c1e4720
--- /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.Invocation;
+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.plugins.MockitoInvocationHandler;
+
+/**
+ * Handles proxy method invocations to dexmaker's InvocationHandler by calling
+ * a MockitoInvocationHandler.
+ */
+final class InvocationHandlerAdapter implements InvocationHandler {
+ private MockitoInvocationHandler handler;
+ private final ObjectMethodsGuru objectMethodsGuru = new ObjectMethodsGuru();
+
+ public InvocationHandlerAdapter(MockitoInvocationHandler 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 Invocation(proxy, proxiedMethod, args, SequenceNumber.next(),
+ proxiedMethod));
+ }
+
+ public MockitoInvocationHandler getHandler() {
+ return handler;
+ }
+
+ public void setHandler(MockitoInvocationHandler 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/META-INF/services/org.mockito.plugins.MockMaker b/src/mockito/resources/META-INF/services/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..9f6df44
--- /dev/null
+++ b/src/mockito/resources/META-INF/services/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+com.google.dexmaker.mockito.DexmakerMockMaker