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
17package com.android.dx.mockito.inline;
18
19import com.android.dx.stock.ProxyBuilder;
20
21import org.mockito.Mockito;
22import org.mockito.invocation.InvocationFactory.RealMethodBehavior;
23import org.mockito.invocation.MockHandler;
24import org.mockito.mock.MockCreationSettings;
25
26import java.lang.reflect.InvocationHandler;
27import java.lang.reflect.Method;
28
29import static org.mockito.Mockito.withSettings;
30
31/**
32 * Handles proxy and entry hook method invocations added by
33 * {@link InlineDexmakerMockMaker#createMock(MockCreationSettings, MockHandler)}
34 */
35final class InvocationHandlerAdapter implements InvocationHandler {
36    private MockHandler handler;
37
38    InvocationHandlerAdapter(MockHandler handler) {
39        this.handler = handler;
40    }
41
42    private static boolean isEqualsMethod(Method method) {
43        return method.getName().equals("equals")
44                && method.getParameterTypes().length == 1
45                && method.getParameterTypes()[0] == Object.class;
46    }
47
48    private static boolean isHashCodeMethod(Method method) {
49        return method.getName().equals("hashCode")
50                && method.getParameterTypes().length == 0;
51    }
52
53    /**
54     * Intercept a method call. Called <u>before</u> a method is called by the method entry hook.
55     *
56     * <p>This does the same as {@link #invoke(Object, Method, Object[])} but this handles methods
57     * that got and entry hook.
58     *
59     * @param mock mocked object
60     * @param method method that was called
61     * @param rawArgs arguments to the method
62     * @param superMethod The super method
63     *
64     * @return mocked result
65     * @throws Throwable An exception if thrown
66     */
67    Object interceptEntryHook(final Object mock, final Method method, final Object[] rawArgs,
68                              final SuperMethod superMethod) throws Throwable {
69        // args can be null if the method invoked has no arguments, but Mockito expects a non-null
70        Object[] args = rawArgs;
71        if (rawArgs == null) {
72            args = new Object[0];
73        }
74
75        return handler.handle(Mockito.framework().getInvocationFactory().createInvocation(mock,
76                withSettings().build(mock.getClass()), method, new RealMethodBehavior() {
77                    @Override
78                    public Object call() throws Throwable {
79                        return superMethod.invoke();
80                    }
81                }, args));
82    }
83
84    /**
85     * Intercept a method call. Called <u>before</u> a method is called by the proxied method.
86     *
87     * <p>This does the same as {@link #interceptEntryHook(Object, Method, Object[], SuperMethod)}
88     * but this handles proxied methods. We only proxy abstract methods.
89     *
90     * @param proxy proxies object
91     * @param method method that was called
92     * @param rawArgs arguments to the method
93     *
94     * @return mocked result
95     * @throws Throwable An exception if thrown
96     */
97    @Override
98    public Object invoke(final Object proxy, final Method method, final Object[] rawArgs) throws
99            Throwable {
100        // args can be null if the method invoked has no arguments, but Mockito expects a non-null
101        Object[] args = rawArgs;
102        if (rawArgs == null) {
103            args = new Object[0];
104        }
105
106        if (isEqualsMethod(method)) {
107            return proxy == args[0];
108        } else if (isHashCodeMethod(method)) {
109            return System.identityHashCode(proxy);
110        }
111
112        return handler.handle(Mockito.framework().getInvocationFactory().createInvocation(proxy,
113                withSettings().build(proxy.getClass().getSuperclass()), method,
114                new RealMethodBehavior() {
115                    @Override
116                    public Object call() throws Throwable {
117                        return ProxyBuilder.callSuper(proxy, method, rawArgs);
118                    }
119                }, args));
120    }
121
122    /**
123     * Get the handler registered with this adapter.
124     *
125     * @return handler
126     */
127    MockHandler getHandler() {
128        return handler;
129    }
130
131    /**
132     * Set a new handler for this adapter.
133     */
134    void setHandler(MockHandler handler) {
135        this.handler = handler;
136    }
137
138    /**
139     * Interface used to describe a supermethod that can be called.
140     */
141    interface SuperMethod {
142        Object invoke() throws Throwable;
143    }
144}
145