FastClassEmitter.java revision 674060f01e9090cd21b3c5656cc3204912ad17a6
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.reflect;
17
18import java.lang.reflect.*;
19import java.util.*;
20
21import org.mockito.asm.ClassVisitor;
22import org.mockito.asm.Label;
23import org.mockito.asm.Type;
24import org.mockito.cglib.core.*;
25
26class FastClassEmitter extends ClassEmitter {
27    private static final Signature CSTRUCT_CLASS =
28      TypeUtils.parseConstructor("Class");
29    private static final Signature METHOD_GET_INDEX =
30      TypeUtils.parseSignature("int getIndex(String, Class[])");
31    private static final Signature SIGNATURE_GET_INDEX =
32      new Signature("getIndex", Type.INT_TYPE, new Type[]{ Constants.TYPE_SIGNATURE });
33    private static final Signature TO_STRING =
34      TypeUtils.parseSignature("String toString()");
35    private static final Signature CONSTRUCTOR_GET_INDEX =
36      TypeUtils.parseSignature("int getIndex(Class[])");
37    private static final Signature INVOKE =
38      TypeUtils.parseSignature("Object invoke(int, Object, Object[])");
39    private static final Signature NEW_INSTANCE =
40      TypeUtils.parseSignature("Object newInstance(int, Object[])");
41    private static final Signature GET_MAX_INDEX =
42      TypeUtils.parseSignature("int getMaxIndex()");
43    private static final Signature GET_SIGNATURE_WITHOUT_RETURN_TYPE =
44      TypeUtils.parseSignature("String getSignatureWithoutReturnType(String, Class[])");
45    private static final Type FAST_CLASS =
46      TypeUtils.parseType("org.mockito.cglib.reflect.FastClass");
47    private static final Type ILLEGAL_ARGUMENT_EXCEPTION =
48      TypeUtils.parseType("IllegalArgumentException");
49    private static final Type INVOCATION_TARGET_EXCEPTION =
50      TypeUtils.parseType("java.lang.reflect.InvocationTargetException");
51    private static final Type[] INVOCATION_TARGET_EXCEPTION_ARRAY = { INVOCATION_TARGET_EXCEPTION };
52
53    public FastClassEmitter(ClassVisitor v, String className, Class type) {
54        super(v);
55
56        Type base = Type.getType(type);
57        begin_class(Constants.V1_2, Constants.ACC_PUBLIC, className, FAST_CLASS, null, Constants.SOURCE_FILE);
58
59        // constructor
60        CodeEmitter e = begin_method(Constants.ACC_PUBLIC, CSTRUCT_CLASS, null);
61        e.load_this();
62        e.load_args();
63        e.super_invoke_constructor(CSTRUCT_CLASS);
64        e.return_value();
65        e.end_method();
66
67        VisibilityPredicate vp = new VisibilityPredicate(type, false);
68        List methods = ReflectUtils.addAllMethods(type, new ArrayList());
69        CollectionUtils.filter(methods, vp);
70        CollectionUtils.filter(methods, new DuplicatesPredicate());
71        List constructors = new ArrayList(Arrays.asList(type.getDeclaredConstructors()));
72        CollectionUtils.filter(constructors, vp);
73
74        // getIndex(String)
75        emitIndexBySignature(methods);
76
77        // getIndex(String, Class[])
78        emitIndexByClassArray(methods);
79
80        // getIndex(Class[])
81        e = begin_method(Constants.ACC_PUBLIC, CONSTRUCTOR_GET_INDEX, null);
82        e.load_args();
83        List info = CollectionUtils.transform(constructors, MethodInfoTransformer.getInstance());
84        EmitUtils.constructor_switch(e, info, new GetIndexCallback(e, info));
85        e.end_method();
86
87        // invoke(int, Object, Object[])
88        e = begin_method(Constants.ACC_PUBLIC, INVOKE, INVOCATION_TARGET_EXCEPTION_ARRAY);
89        e.load_arg(1);
90        e.checkcast(base);
91        e.load_arg(0);
92        invokeSwitchHelper(e, methods, 2, base);
93        e.end_method();
94
95        // newInstance(int, Object[])
96        e = begin_method(Constants.ACC_PUBLIC, NEW_INSTANCE, INVOCATION_TARGET_EXCEPTION_ARRAY);
97        e.new_instance(base);
98        e.dup();
99        e.load_arg(0);
100        invokeSwitchHelper(e, constructors, 1, base);
101        e.end_method();
102
103        // getMaxIndex()
104        e = begin_method(Constants.ACC_PUBLIC, GET_MAX_INDEX, null);
105        e.push(methods.size() - 1);
106        e.return_value();
107        e.end_method();
108
109        end_class();
110    }
111
112    // TODO: support constructor indices ("<init>")
113    private void emitIndexBySignature(List methods) {
114        CodeEmitter e = begin_method(Constants.ACC_PUBLIC, SIGNATURE_GET_INDEX, null);
115        List signatures = CollectionUtils.transform(methods, new Transformer() {
116            public Object transform(Object obj) {
117                return ReflectUtils.getSignature((Method)obj).toString();
118            }
119        });
120        e.load_arg(0);
121        e.invoke_virtual(Constants.TYPE_OBJECT, TO_STRING);
122        signatureSwitchHelper(e, signatures);
123        e.end_method();
124    }
125
126    private static final int TOO_MANY_METHODS = 100; // TODO
127    private void emitIndexByClassArray(List methods) {
128        CodeEmitter e = begin_method(Constants.ACC_PUBLIC, METHOD_GET_INDEX, null);
129        if (methods.size() > TOO_MANY_METHODS) {
130            // hack for big classes
131            List signatures = CollectionUtils.transform(methods, new Transformer() {
132                public Object transform(Object obj) {
133                    String s = ReflectUtils.getSignature((Method)obj).toString();
134                    return s.substring(0, s.lastIndexOf(')') + 1);
135                }
136            });
137            e.load_args();
138            e.invoke_static(FAST_CLASS, GET_SIGNATURE_WITHOUT_RETURN_TYPE);
139            signatureSwitchHelper(e, signatures);
140        } else {
141            e.load_args();
142            List info = CollectionUtils.transform(methods, MethodInfoTransformer.getInstance());
143            EmitUtils.method_switch(e, info, new GetIndexCallback(e, info));
144        }
145        e.end_method();
146    }
147
148    private void signatureSwitchHelper(final CodeEmitter e, final List signatures) {
149        ObjectSwitchCallback callback = new ObjectSwitchCallback() {
150            public void processCase(Object key, Label end) {
151                // TODO: remove linear indexOf
152                e.push(signatures.indexOf(key));
153                e.return_value();
154            }
155            public void processDefault() {
156                e.push(-1);
157                e.return_value();
158            }
159        };
160        EmitUtils.string_switch(e,
161                                (String[])signatures.toArray(new String[signatures.size()]),
162                                Constants.SWITCH_STYLE_HASH,
163                                callback);
164    }
165
166    private static void invokeSwitchHelper(final CodeEmitter e, List members, final int arg, final Type base) {
167        final List info = CollectionUtils.transform(members, MethodInfoTransformer.getInstance());
168        final Label illegalArg = e.make_label();
169        Block block = e.begin_block();
170        e.process_switch(getIntRange(info.size()), new ProcessSwitchCallback() {
171            public void processCase(int key, Label end) {
172                MethodInfo method = (MethodInfo)info.get(key);
173                Type[] types = method.getSignature().getArgumentTypes();
174                for (int i = 0; i < types.length; i++) {
175                    e.load_arg(arg);
176                    e.aaload(i);
177                    e.unbox(types[i]);
178                }
179                // TODO: change method lookup process so MethodInfo will already reference base
180                // instead of superclass when superclass method is inaccessible
181                e.invoke(method, base);
182                if (!TypeUtils.isConstructor(method)) {
183                    e.box(method.getSignature().getReturnType());
184                }
185                e.return_value();
186            }
187            public void processDefault() {
188                e.goTo(illegalArg);
189            }
190        });
191        block.end();
192        EmitUtils.wrap_throwable(block, INVOCATION_TARGET_EXCEPTION);
193        e.mark(illegalArg);
194        e.throw_exception(ILLEGAL_ARGUMENT_EXCEPTION, "Cannot find matching method/constructor");
195    }
196
197    private static class GetIndexCallback implements ObjectSwitchCallback {
198        private CodeEmitter e;
199        private Map indexes = new HashMap();
200
201        public GetIndexCallback(CodeEmitter e, List methods) {
202            this.e = e;
203            int index = 0;
204            for (Iterator it = methods.iterator(); it.hasNext();) {
205                indexes.put(it.next(), new Integer(index++));
206            }
207        }
208
209        public void processCase(Object key, Label end) {
210            e.push(((Integer)indexes.get(key)).intValue());
211            e.return_value();
212        }
213
214        public void processDefault() {
215            e.push(-1);
216            e.return_value();
217        }
218    }
219
220    private static int[] getIntRange(int length) {
221        int[] range = new int[length];
222        for (int i = 0; i < length; i++) {
223            range[i] = i;
224        }
225        return range;
226    }
227}
228