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.Method;
19import java.util.*;
20
21import org.mockito.asm.Label;
22import org.mockito.asm.Type;
23import org.mockito.cglib.core.*;
24
25class MethodInterceptorGenerator
26implements CallbackGenerator
27{
28    public static final MethodInterceptorGenerator INSTANCE = new MethodInterceptorGenerator();
29
30    static final String EMPTY_ARGS_NAME = "CGLIB$emptyArgs";
31    static final String FIND_PROXY_NAME = "CGLIB$findMethodProxy";
32    static final Class[] FIND_PROXY_TYPES = { Signature.class };
33
34    private static final Type ABSTRACT_METHOD_ERROR =
35      TypeUtils.parseType("AbstractMethodError");
36    private static final Type METHOD =
37      TypeUtils.parseType("java.lang.reflect.Method");
38    private static final Type REFLECT_UTILS =
39      TypeUtils.parseType("org.mockito.cglib.core.ReflectUtils");
40    private static final Type METHOD_PROXY =
41      TypeUtils.parseType("org.mockito.cglib.proxy.MethodProxy");
42    private static final Type METHOD_INTERCEPTOR =
43      TypeUtils.parseType("org.mockito.cglib.proxy.MethodInterceptor");
44    private static final Signature GET_DECLARED_METHODS =
45      TypeUtils.parseSignature("java.lang.reflect.Method[] getDeclaredMethods()");
46    private static final Signature GET_DECLARING_CLASS =
47      TypeUtils.parseSignature("Class getDeclaringClass()");
48    private static final Signature FIND_METHODS =
49      TypeUtils.parseSignature("java.lang.reflect.Method[] findMethods(String[], java.lang.reflect.Method[])");
50    private static final Signature MAKE_PROXY =
51      new Signature("create", METHOD_PROXY, new Type[]{
52          Constants.TYPE_CLASS,
53          Constants.TYPE_CLASS,
54          Constants.TYPE_STRING,
55          Constants.TYPE_STRING,
56          Constants.TYPE_STRING
57      });
58    private static final Signature INTERCEPT =
59      new Signature("intercept", Constants.TYPE_OBJECT, new Type[]{
60          Constants.TYPE_OBJECT,
61          METHOD,
62          Constants.TYPE_OBJECT_ARRAY,
63          METHOD_PROXY
64      });
65    private static final Signature FIND_PROXY =
66      new Signature(FIND_PROXY_NAME, METHOD_PROXY, new Type[]{ Constants.TYPE_SIGNATURE });
67    private static final Signature TO_STRING =
68      TypeUtils.parseSignature("String toString()");
69    private static final Transformer METHOD_TO_CLASS = new Transformer(){
70        public Object transform(Object value) {
71            return ((MethodInfo)value).getClassInfo();
72        }
73    };
74    private static final Signature CSTRUCT_SIGNATURE =
75        TypeUtils.parseConstructor("String, String");
76
77    private String getMethodField(Signature impl) {
78        return impl.getName() + "$Method";
79    }
80    private String getMethodProxyField(Signature impl) {
81        return impl.getName() + "$Proxy";
82    }
83
84    public void generate(ClassEmitter ce, Context context, List methods) {
85        Map sigMap = new HashMap();
86        for (Iterator it = methods.iterator(); it.hasNext();) {
87            MethodInfo method = (MethodInfo)it.next();
88            Signature sig = method.getSignature();
89            Signature impl = context.getImplSignature(method);
90
91            String methodField = getMethodField(impl);
92            String methodProxyField = getMethodProxyField(impl);
93
94            sigMap.put(sig.toString(), methodProxyField);
95            ce.declare_field(Constants.PRIVATE_FINAL_STATIC, methodField, METHOD, null);
96            ce.declare_field(Constants.PRIVATE_FINAL_STATIC, methodProxyField, METHOD_PROXY, null);
97            ce.declare_field(Constants.PRIVATE_FINAL_STATIC, EMPTY_ARGS_NAME, Constants.TYPE_OBJECT_ARRAY, null);
98            CodeEmitter e;
99
100            // access method
101            e = ce.begin_method(Constants.ACC_FINAL,
102                                impl,
103                                method.getExceptionTypes());
104            superHelper(e, method);
105            e.return_value();
106            e.end_method();
107
108            // around method
109            e = context.beginMethod(ce, method);
110            Label nullInterceptor = e.make_label();
111            context.emitCallback(e, context.getIndex(method));
112            e.dup();
113            e.ifnull(nullInterceptor);
114
115            e.load_this();
116            e.getfield(methodField);
117
118            if (sig.getArgumentTypes().length == 0) {
119                e.getfield(EMPTY_ARGS_NAME);
120            } else {
121                e.create_arg_array();
122            }
123
124            e.getfield(methodProxyField);
125            e.invoke_interface(METHOD_INTERCEPTOR, INTERCEPT);
126            e.unbox_or_zero(sig.getReturnType());
127            e.return_value();
128
129            e.mark(nullInterceptor);
130            superHelper(e, method);
131            e.return_value();
132            e.end_method();
133        }
134        generateFindProxy(ce, sigMap);
135    }
136
137    private static void superHelper(CodeEmitter e, MethodInfo method)
138    {
139        if (TypeUtils.isAbstract(method.getModifiers())) {
140            e.throw_exception(ABSTRACT_METHOD_ERROR, method.toString() + " is abstract" );
141        } else {
142            e.load_this();
143            e.load_args();
144            e.super_invoke(method.getSignature());
145        }
146    }
147
148    public void generateStatic(CodeEmitter e, Context context, List methods) throws Exception {
149        /* generates:
150           static {
151             Class thisClass = Class.forName("NameOfThisClass");
152             Class cls = Class.forName("java.lang.Object");
153             String[] sigs = new String[]{ "toString", "()Ljava/lang/String;", ... };
154             Method[] methods = cls.getDeclaredMethods();
155             methods = ReflectUtils.findMethods(sigs, methods);
156             METHOD_0 = methods[0];
157             CGLIB$ACCESS_0 = MethodProxy.create(cls, thisClass, "()Ljava/lang/String;", "toString", "CGLIB$ACCESS_0");
158             ...
159           }
160        */
161
162        e.push(0);
163        e.newarray();
164        e.putfield(EMPTY_ARGS_NAME);
165
166        Local thisclass = e.make_local();
167        Local declaringclass = e.make_local();
168        EmitUtils.load_class_this(e);
169        e.store_local(thisclass);
170
171        Map methodsByClass = CollectionUtils.bucket(methods, METHOD_TO_CLASS);
172        for (Iterator i = methodsByClass.keySet().iterator(); i.hasNext();) {
173            ClassInfo classInfo = (ClassInfo)i.next();
174
175            List classMethods = (List)methodsByClass.get(classInfo);
176            e.push(2 * classMethods.size());
177            e.newarray(Constants.TYPE_STRING);
178            for (int index = 0; index < classMethods.size(); index++) {
179                MethodInfo method = (MethodInfo)classMethods.get(index);
180                Signature sig = method.getSignature();
181                e.dup();
182                e.push(2 * index);
183                e.push(sig.getName());
184                e.aastore();
185                e.dup();
186                e.push(2 * index + 1);
187                e.push(sig.getDescriptor());
188                e.aastore();
189            }
190
191            EmitUtils.load_class(e, classInfo.getType());
192            e.dup();
193            e.store_local(declaringclass);
194            e.invoke_virtual(Constants.TYPE_CLASS, GET_DECLARED_METHODS);
195            e.invoke_static(REFLECT_UTILS, FIND_METHODS);
196
197            for (int index = 0; index < classMethods.size(); index++) {
198                MethodInfo method = (MethodInfo)classMethods.get(index);
199                Signature sig = method.getSignature();
200                Signature impl = context.getImplSignature(method);
201                e.dup();
202                e.push(index);
203                e.array_load(METHOD);
204                e.putfield(getMethodField(impl));
205
206                e.load_local(declaringclass);
207                e.load_local(thisclass);
208                e.push(sig.getDescriptor());
209                e.push(sig.getName());
210                e.push(impl.getName());
211                e.invoke_static(METHOD_PROXY, MAKE_PROXY);
212                e.putfield(getMethodProxyField(impl));
213            }
214            e.pop();
215        }
216    }
217
218    public void generateFindProxy(ClassEmitter ce, final Map sigMap) {
219        final CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC | Constants.ACC_STATIC,
220                                              FIND_PROXY,
221                                              null);
222        e.load_arg(0);
223        e.invoke_virtual(Constants.TYPE_OBJECT, TO_STRING);
224        ObjectSwitchCallback callback = new ObjectSwitchCallback() {
225            public void processCase(Object key, Label end) {
226                e.getfield((String)sigMap.get(key));
227                e.return_value();
228            }
229            public void processDefault() {
230                e.aconst_null();
231                e.return_value();
232            }
233        };
234        EmitUtils.string_switch(e,
235                                (String[])sigMap.keySet().toArray(new String[0]),
236                                Constants.SWITCH_STYLE_HASH,
237                                callback);
238        e.end_method();
239    }
240}
241