1/*
2 * Copyright 2003,2004 The Apache Software Foundation
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 */
16package org.mockito.cglib.proxy;
17
18import java.lang.reflect.InvocationTargetException;
19import java.lang.reflect.Method;
20
21import org.mockito.cglib.core.AbstractClassGenerator;
22import org.mockito.cglib.core.CodeGenerationException;
23import org.mockito.cglib.core.GeneratorStrategy;
24import org.mockito.cglib.core.NamingPolicy;
25import org.mockito.cglib.core.Signature;
26import org.mockito.cglib.reflect.FastClass;
27
28
29/**
30 * Classes generated by {@link Enhancer} pass this object to the
31 * registered {@link MethodInterceptor} objects when an intercepted method is invoked. It can
32 * be used to either invoke the original method, or call the same method on a different
33 * object of the same type.
34 * @version $Id: MethodProxy.java,v 1.14 2008/05/26 04:05:50 herbyderby Exp $
35 */
36public class MethodProxy {
37    private Signature sig1;
38    private Signature sig2;
39    private CreateInfo createInfo;
40
41    private final Object initLock = new Object();
42    private volatile FastClassInfo fastClassInfo;
43
44    /**
45     * For internal use by {@link Enhancer} only; see the {@link org.mockito.cglib.reflect.FastMethod} class
46     * for similar functionality.
47     */
48    public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
49        MethodProxy proxy = new MethodProxy();
50        proxy.sig1 = new Signature(name1, desc);
51        proxy.sig2 = new Signature(name2, desc);
52        proxy.createInfo = new CreateInfo(c1, c2);
53        return proxy;
54    }
55
56    private void init()
57    {
58        /*
59         * Using a volatile invariant allows us to initialize the FastClass and
60         * method index pairs atomically.
61         *
62         * Double-checked locking is safe with volatile in Java 5.  Before 1.5 this
63         * code could allow fastClassInfo to be instantiated more than once, which
64         * appears to be benign.
65         */
66        if (fastClassInfo == null)
67        {
68            synchronized (initLock)
69            {
70                if (fastClassInfo == null)
71                {
72                    CreateInfo ci = createInfo;
73
74                    FastClassInfo fci = new FastClassInfo();
75                    fci.f1 = helper(ci, ci.c1);
76                    fci.f2 = helper(ci, ci.c2);
77                    fci.i1 = fci.f1.getIndex(sig1);
78                    fci.i2 = fci.f2.getIndex(sig2);
79                    fastClassInfo = fci;
80                }
81            }
82        }
83    }
84
85    private static class FastClassInfo
86    {
87        FastClass f1;
88        FastClass f2;
89        int i1;
90        int i2;
91    }
92
93    private static class CreateInfo
94    {
95        Class c1;
96        Class c2;
97        NamingPolicy namingPolicy;
98        GeneratorStrategy strategy;
99        boolean attemptLoad;
100
101        public CreateInfo(Class c1, Class c2)
102        {
103            this.c1 = c1;
104            this.c2 = c2;
105            AbstractClassGenerator fromEnhancer = AbstractClassGenerator.getCurrent();
106            if (fromEnhancer != null) {
107                namingPolicy = fromEnhancer.getNamingPolicy();
108                strategy = fromEnhancer.getStrategy();
109                attemptLoad = fromEnhancer.getAttemptLoad();
110            }
111        }
112    }
113
114    private static FastClass helper(CreateInfo ci, Class type) {
115        FastClass.Generator g = new FastClass.Generator();
116        g.setType(type);
117        g.setClassLoader(ci.c2.getClassLoader());
118        g.setNamingPolicy(ci.namingPolicy);
119        g.setStrategy(ci.strategy);
120        g.setAttemptLoad(ci.attemptLoad);
121        return g.create();
122    }
123
124    private MethodProxy() {
125    }
126
127    /**
128     * Return the signature of the proxied method.
129     */
130    public Signature getSignature() {
131        return sig1;
132    }
133
134    /**
135     * Return the name of the synthetic method created by CGLIB which is
136     * used by {@link #invokeSuper} to invoke the superclass
137     * (non-intercepted) method implementation. The parameter types are
138     * the same as the proxied method.
139     */
140    public String getSuperName() {
141        return sig2.getName();
142    }
143
144    /**
145     * Return the {@link org.mockito.cglib.reflect.FastClass} method index
146     * for the method used by {@link #invokeSuper}. This index uniquely
147     * identifies the method within the generated proxy, and therefore
148     * can be useful to reference external metadata.
149     * @see #getSuperName
150     */
151    public int getSuperIndex() {
152        init();
153        return fastClassInfo.i2;
154    }
155
156    /**
157     * Return the <code>MethodProxy</code> used when intercepting the method
158     * matching the given signature.
159     * @param type the class generated by Enhancer
160     * @param sig the signature to match
161     * @return the MethodProxy instance, or null if no applicable matching method is found
162     * @throws IllegalArgumentException if the Class was not created by Enhancer or does not use a MethodInterceptor
163     */
164    public static MethodProxy find(Class type, Signature sig) {
165        try {
166            Method m = type.getDeclaredMethod(MethodInterceptorGenerator.FIND_PROXY_NAME,
167                                              MethodInterceptorGenerator.FIND_PROXY_TYPES);
168            return (MethodProxy)m.invoke(null, new Object[]{ sig });
169        } catch (NoSuchMethodException e) {
170            throw new IllegalArgumentException("Class " + type + " does not use a MethodInterceptor");
171        } catch (IllegalAccessException e) {
172            throw new CodeGenerationException(e);
173        } catch (InvocationTargetException e) {
174            throw new CodeGenerationException(e);
175        }
176    }
177
178    /**
179     * Invoke the original method, on a different object of the same type.
180     * @param obj the compatible object; recursion will result if you use the object passed as the first
181     * argument to the MethodInterceptor (usually not what you want)
182     * @param args the arguments passed to the intercepted method; you may substitute a different
183     * argument array as long as the types are compatible
184     * @see MethodInterceptor#intercept
185     * @throws Throwable the bare exceptions thrown by the called method are passed through
186     * without wrapping in an <code>InvocationTargetException</code>
187     */
188    public Object invoke(Object obj, Object[] args) throws Throwable {
189        try {
190            init();
191            FastClassInfo fci = fastClassInfo;
192            return fci.f1.invoke(fci.i1, obj, args);
193        } catch (InvocationTargetException e) {
194            throw e.getTargetException();
195        } catch (IllegalArgumentException e) {
196            if (fastClassInfo.i1 < 0)
197                throw new IllegalArgumentException("Protected method: " + sig1);
198            throw e;
199        }
200    }
201
202    /**
203     * Invoke the original (super) method on the specified object.
204     * @param obj the enhanced object, must be the object passed as the first
205     * argument to the MethodInterceptor
206     * @param args the arguments passed to the intercepted method; you may substitute a different
207     * argument array as long as the types are compatible
208     * @see MethodInterceptor#intercept
209     * @throws Throwable the bare exceptions thrown by the called method are passed through
210     * without wrapping in an <code>InvocationTargetException</code>
211     */
212    public Object invokeSuper(Object obj, Object[] args) throws Throwable {
213        try {
214            init();
215            FastClassInfo fci = fastClassInfo;
216            return fci.f2.invoke(fci.i2, obj, args);
217        } catch (InvocationTargetException e) {
218            throw e.getTargetException();
219        }
220    }
221}
222