1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.dexmaker.mockito;
18 
19 import com.google.dexmaker.stock.ProxyBuilder;
20 import java.lang.reflect.Field;
21 import java.lang.reflect.InvocationHandler;
22 import java.lang.reflect.Proxy;
23 import java.util.Set;
24 import org.mockito.exceptions.base.MockitoException;
25 import org.mockito.exceptions.stacktrace.StackTraceCleaner;
26 import org.mockito.invocation.MockHandler;
27 import org.mockito.mock.MockCreationSettings;
28 import org.mockito.plugins.MockMaker;
29 import org.mockito.plugins.StackTraceCleanerProvider;
30 
31 /**
32  * Generates mock instances on Android's runtime.
33  */
34 public final class DexmakerMockMaker implements MockMaker, StackTraceCleanerProvider {
35     private final UnsafeAllocator unsafeAllocator = UnsafeAllocator.create();
36 
createMock(MockCreationSettings<T> settings, MockHandler handler)37     public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {
38         Class<T> typeToMock = settings.getTypeToMock();
39         Set<Class> interfacesSet = settings.getExtraInterfaces();
40         Class<?>[] extraInterfaces = interfacesSet.toArray(new Class[interfacesSet.size()]);
41         InvocationHandler invocationHandler = new InvocationHandlerAdapter(handler);
42 
43         if (typeToMock.isInterface()) {
44             // support interfaces via java.lang.reflect.Proxy
45             Class[] classesToMock = new Class[extraInterfaces.length + 1];
46             classesToMock[0] = typeToMock;
47             System.arraycopy(extraInterfaces, 0, classesToMock, 1, extraInterfaces.length);
48             ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
49             @SuppressWarnings("unchecked") // newProxyInstance returns the type of typeToMock
50             T mock = (T) Proxy.newProxyInstance(contextClassLoader, classesToMock,
51                     invocationHandler);
52             return mock;
53 
54         } else {
55             // support concrete classes via dexmaker's ProxyBuilder
56             try {
57                 Class<? extends T> proxyClass = ProxyBuilder.forClass(typeToMock)
58                         .implementing(extraInterfaces)
59                         .buildProxyClass();
60                 T mock = unsafeAllocator.newInstance(proxyClass);
61                 Field handlerField = proxyClass.getDeclaredField("$__handler");
62                 handlerField.setAccessible(true);
63                 handlerField.set(mock, invocationHandler);
64                 return mock;
65             } catch (RuntimeException e) {
66                 throw e;
67             } catch (Exception e) {
68                 throw new MockitoException("Failed to mock " + typeToMock, e);
69             }
70         }
71     }
72 
resetMock(Object mock, MockHandler newHandler, MockCreationSettings settings)73     public void resetMock(Object mock, MockHandler newHandler, MockCreationSettings settings) {
74         InvocationHandlerAdapter adapter = getInvocationHandlerAdapter(mock);
75         adapter.setHandler(newHandler);
76     }
77 
getHandler(Object mock)78     public MockHandler getHandler(Object mock) {
79         InvocationHandlerAdapter adapter = getInvocationHandlerAdapter(mock);
80         return adapter != null ? adapter.getHandler() : null;
81     }
82 
getInvocationHandlerAdapter(Object mock)83     private InvocationHandlerAdapter getInvocationHandlerAdapter(Object mock) {
84         if (Proxy.isProxyClass(mock.getClass())) {
85             InvocationHandler invocationHandler = Proxy.getInvocationHandler(mock);
86             return invocationHandler instanceof InvocationHandlerAdapter
87                     ? (InvocationHandlerAdapter) invocationHandler
88                     : null;
89         }
90 
91         if (ProxyBuilder.isProxyClass(mock.getClass())) {
92             InvocationHandler invocationHandler = ProxyBuilder.getInvocationHandler(mock);
93             return invocationHandler instanceof InvocationHandlerAdapter
94                     ? (InvocationHandlerAdapter) invocationHandler
95                     : null;
96         }
97 
98         return null;
99     }
100 
getStackTraceCleaner(final StackTraceCleaner defaultCleaner)101     public StackTraceCleaner getStackTraceCleaner(final StackTraceCleaner defaultCleaner) {
102         return new StackTraceCleaner() {
103             public boolean isOut(StackTraceElement candidate) {
104                 return defaultCleaner.isOut(candidate)
105                         || candidate.getClassName().endsWith("_Proxy") // dexmaker class proxies
106                         || candidate.getClassName().startsWith("$Proxy") // dalvik interface proxies
107                         || candidate.getClassName().startsWith("com.google.dexmaker.mockito.");
108             }
109         };
110     }
111 }
112