EmitUtils.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.core;
17
18import java.math.BigDecimal;
19import java.math.BigInteger;
20import java.util.*;
21
22import org.mockito.asm.Label;
23import org.mockito.asm.Type;
24
25public class EmitUtils {
26    private static final Signature CSTRUCT_NULL =
27      TypeUtils.parseConstructor("");
28    private static final Signature CSTRUCT_THROWABLE =
29      TypeUtils.parseConstructor("Throwable");
30
31    private static final Signature GET_NAME =
32      TypeUtils.parseSignature("String getName()");
33    private static final Signature HASH_CODE =
34      TypeUtils.parseSignature("int hashCode()");
35    private static final Signature EQUALS =
36      TypeUtils.parseSignature("boolean equals(Object)");
37    private static final Signature STRING_LENGTH =
38      TypeUtils.parseSignature("int length()");
39    private static final Signature STRING_CHAR_AT =
40      TypeUtils.parseSignature("char charAt(int)");
41    private static final Signature FOR_NAME =
42      TypeUtils.parseSignature("Class forName(String)");
43    private static final Signature DOUBLE_TO_LONG_BITS =
44      TypeUtils.parseSignature("long doubleToLongBits(double)");
45    private static final Signature FLOAT_TO_INT_BITS =
46      TypeUtils.parseSignature("int floatToIntBits(float)");
47    private static final Signature TO_STRING =
48      TypeUtils.parseSignature("String toString()");
49    private static final Signature APPEND_STRING =
50      TypeUtils.parseSignature("StringBuffer append(String)");
51    private static final Signature APPEND_INT =
52      TypeUtils.parseSignature("StringBuffer append(int)");
53    private static final Signature APPEND_DOUBLE =
54      TypeUtils.parseSignature("StringBuffer append(double)");
55    private static final Signature APPEND_FLOAT =
56      TypeUtils.parseSignature("StringBuffer append(float)");
57    private static final Signature APPEND_CHAR =
58      TypeUtils.parseSignature("StringBuffer append(char)");
59    private static final Signature APPEND_LONG =
60      TypeUtils.parseSignature("StringBuffer append(long)");
61    private static final Signature APPEND_BOOLEAN =
62      TypeUtils.parseSignature("StringBuffer append(boolean)");
63    private static final Signature LENGTH =
64      TypeUtils.parseSignature("int length()");
65    private static final Signature SET_LENGTH =
66      TypeUtils.parseSignature("void setLength(int)");
67    private static final Signature GET_DECLARED_METHOD =
68      TypeUtils.parseSignature("java.lang.reflect.Method getDeclaredMethod(String, Class[])");
69
70
71
72    public static final ArrayDelimiters DEFAULT_DELIMITERS = new ArrayDelimiters("{", ", ", "}");
73
74    private EmitUtils() {
75    }
76
77    public static void factory_method(ClassEmitter ce, Signature sig) {
78        CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, sig, null);
79        e.new_instance_this();
80        e.dup();
81        e.load_args();
82        e.invoke_constructor_this(TypeUtils.parseConstructor(sig.getArgumentTypes()));
83        e.return_value();
84        e.end_method();
85    }
86
87    public static void null_constructor(ClassEmitter ce) {
88        CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, CSTRUCT_NULL, null);
89        e.load_this();
90        e.super_invoke_constructor();
91        e.return_value();
92        e.end_method();
93    }
94
95    /**
96     * Process an array on the stack. Assumes the top item on the stack
97     * is an array of the specified type. For each element in the array,
98     * puts the element on the stack and triggers the callback.
99     * @param type the type of the array (type.isArray() must be true)
100     * @param callback the callback triggered for each element
101     */
102    public static void process_array(CodeEmitter e, Type type, ProcessArrayCallback callback) {
103        Type componentType = TypeUtils.getComponentType(type);
104        Local array = e.make_local();
105        Local loopvar = e.make_local(Type.INT_TYPE);
106        Label loopbody = e.make_label();
107        Label checkloop = e.make_label();
108        e.store_local(array);
109        e.push(0);
110        e.store_local(loopvar);
111        e.goTo(checkloop);
112
113        e.mark(loopbody);
114        e.load_local(array);
115        e.load_local(loopvar);
116        e.array_load(componentType);
117        callback.processElement(componentType);
118        e.iinc(loopvar, 1);
119
120        e.mark(checkloop);
121        e.load_local(loopvar);
122        e.load_local(array);
123        e.arraylength();
124        e.if_icmp(e.LT, loopbody);
125    }
126
127    /**
128     * Process two arrays on the stack in parallel. Assumes the top two items on the stack
129     * are arrays of the specified class. The arrays must be the same length. For each pair
130     * of elements in the arrays, puts the pair on the stack and triggers the callback.
131     * @param type the type of the arrays (type.isArray() must be true)
132     * @param callback the callback triggered for each pair of elements
133     */
134    public static void process_arrays(CodeEmitter e, Type type, ProcessArrayCallback callback) {
135        Type componentType = TypeUtils.getComponentType(type);
136        Local array1 = e.make_local();
137        Local array2 = e.make_local();
138        Local loopvar = e.make_local(Type.INT_TYPE);
139        Label loopbody = e.make_label();
140        Label checkloop = e.make_label();
141        e.store_local(array1);
142        e.store_local(array2);
143        e.push(0);
144        e.store_local(loopvar);
145        e.goTo(checkloop);
146
147        e.mark(loopbody);
148        e.load_local(array1);
149        e.load_local(loopvar);
150        e.array_load(componentType);
151        e.load_local(array2);
152        e.load_local(loopvar);
153        e.array_load(componentType);
154        callback.processElement(componentType);
155        e.iinc(loopvar, 1);
156
157        e.mark(checkloop);
158        e.load_local(loopvar);
159        e.load_local(array1);
160        e.arraylength();
161        e.if_icmp(e.LT, loopbody);
162    }
163
164    public static void string_switch(CodeEmitter e, String[] strings, int switchStyle, ObjectSwitchCallback callback) {
165        try {
166            switch (switchStyle) {
167            case Constants.SWITCH_STYLE_TRIE:
168                string_switch_trie(e, strings, callback);
169                break;
170            case Constants.SWITCH_STYLE_HASH:
171                string_switch_hash(e, strings, callback, false);
172                break;
173            case Constants.SWITCH_STYLE_HASHONLY:
174                string_switch_hash(e, strings, callback, true);
175                break;
176            default:
177                throw new IllegalArgumentException("unknown switch style " + switchStyle);
178            }
179        } catch (RuntimeException ex) {
180            throw ex;
181        } catch (Error ex) {
182            throw ex;
183        } catch (Exception ex) {
184            throw new CodeGenerationException(ex);
185        }
186    }
187
188    private static void string_switch_trie(final CodeEmitter e,
189                                           String[] strings,
190                                           final ObjectSwitchCallback callback) throws Exception {
191        final Label def = e.make_label();
192        final Label end = e.make_label();
193        final Map buckets = CollectionUtils.bucket(Arrays.asList(strings), new Transformer() {
194            public Object transform(Object value) {
195                return new Integer(((String)value).length());
196            }
197        });
198        e.dup();
199        e.invoke_virtual(Constants.TYPE_STRING, STRING_LENGTH);
200        e.process_switch(getSwitchKeys(buckets), new ProcessSwitchCallback() {
201                public void processCase(int key, Label ignore_end) throws Exception {
202                    List bucket = (List)buckets.get(new Integer(key));
203                    stringSwitchHelper(e, bucket, callback, def, end, 0);
204                }
205                public void processDefault() {
206                    e.goTo(def);
207                }
208            });
209        e.mark(def);
210        e.pop();
211        callback.processDefault();
212        e.mark(end);
213    }
214
215    private static void stringSwitchHelper(final CodeEmitter e,
216                                           List strings,
217                                           final ObjectSwitchCallback callback,
218                                           final Label def,
219                                           final Label end,
220                                           final int index) throws Exception {
221        final int len = ((String)strings.get(0)).length();
222        final Map buckets = CollectionUtils.bucket(strings, new Transformer() {
223            public Object transform(Object value) {
224                return new Integer(((String)value).charAt(index));
225            }
226        });
227        e.dup();
228        e.push(index);
229        e.invoke_virtual(Constants.TYPE_STRING, STRING_CHAR_AT);
230        e.process_switch(getSwitchKeys(buckets), new ProcessSwitchCallback() {
231                public void processCase(int key, Label ignore_end) throws Exception {
232                    List bucket = (List)buckets.get(new Integer(key));
233                    if (index + 1 == len) {
234                        e.pop();
235                        callback.processCase(bucket.get(0), end);
236                    } else {
237                        stringSwitchHelper(e, bucket, callback, def, end, index + 1);
238                    }
239                }
240                public void processDefault() {
241                    e.goTo(def);
242                }
243            });
244    }
245
246    static int[] getSwitchKeys(Map buckets) {
247        int[] keys = new int[buckets.size()];
248        int index = 0;
249        for (Iterator it = buckets.keySet().iterator(); it.hasNext();) {
250            keys[index++] = ((Integer)it.next()).intValue();
251        }
252        Arrays.sort(keys);
253        return keys;
254    }
255
256    private static void string_switch_hash(final CodeEmitter e,
257                                           final String[] strings,
258                                           final ObjectSwitchCallback callback,
259                                           final boolean skipEquals) throws Exception {
260        final Map buckets = CollectionUtils.bucket(Arrays.asList(strings), new Transformer() {
261            public Object transform(Object value) {
262                return new Integer(value.hashCode());
263            }
264        });
265        final Label def = e.make_label();
266        final Label end = e.make_label();
267        e.dup();
268        e.invoke_virtual(Constants.TYPE_OBJECT, HASH_CODE);
269        e.process_switch(getSwitchKeys(buckets), new ProcessSwitchCallback() {
270            public void processCase(int key, Label ignore_end) throws Exception {
271                List bucket = (List)buckets.get(new Integer(key));
272                Label next = null;
273                if (skipEquals && bucket.size() == 1) {
274                    if (skipEquals)
275                        e.pop();
276                    callback.processCase((String)bucket.get(0), end);
277                } else {
278                    for (Iterator it = bucket.iterator(); it.hasNext();) {
279                        String string = (String)it.next();
280                        if (next != null) {
281                            e.mark(next);
282                        }
283                        if (it.hasNext()) {
284                            e.dup();
285                        }
286                        e.push(string);
287                        e.invoke_virtual(Constants.TYPE_OBJECT, EQUALS);
288                        if (it.hasNext()) {
289                            e.if_jump(e.EQ, next = e.make_label());
290                            e.pop();
291                        } else {
292                            e.if_jump(e.EQ, def);
293                        }
294                        callback.processCase(string, end);
295                    }
296                }
297            }
298            public void processDefault() {
299                e.pop();
300            }
301        });
302        e.mark(def);
303        callback.processDefault();
304        e.mark(end);
305    }
306
307    public static void load_class_this(CodeEmitter e) {
308        load_class_helper(e, e.getClassEmitter().getClassType());
309    }
310
311    public static void load_class(CodeEmitter e, Type type) {
312        if (TypeUtils.isPrimitive(type)) {
313            if (type == Type.VOID_TYPE) {
314                throw new IllegalArgumentException("cannot load void type");
315            }
316            e.getstatic(TypeUtils.getBoxedType(type), "TYPE", Constants.TYPE_CLASS);
317        } else {
318            load_class_helper(e, type);
319        }
320    }
321
322    private static void load_class_helper(CodeEmitter e, final Type type) {
323        if (e.isStaticHook()) {
324            // have to fall back on non-optimized load
325            e.push(TypeUtils.emulateClassGetName(type));
326            e.invoke_static(Constants.TYPE_CLASS, FOR_NAME);
327        } else {
328            ClassEmitter ce = e.getClassEmitter();
329            String typeName = TypeUtils.emulateClassGetName(type);
330
331            // TODO: can end up with duplicated field names when using chained transformers; incorporate static hook # somehow
332            String fieldName = "CGLIB$load_class$" + TypeUtils.escapeType(typeName);
333            if (!ce.isFieldDeclared(fieldName)) {
334                ce.declare_field(Constants.PRIVATE_FINAL_STATIC, fieldName, Constants.TYPE_CLASS, null);
335                CodeEmitter hook = ce.getStaticHook();
336                hook.push(typeName);
337                hook.invoke_static(Constants.TYPE_CLASS, FOR_NAME);
338                hook.putstatic(ce.getClassType(), fieldName, Constants.TYPE_CLASS);
339            }
340            e.getfield(fieldName);
341        }
342    }
343
344    public static void push_array(CodeEmitter e, Object[] array) {
345        e.push(array.length);
346        e.newarray(Type.getType(remapComponentType(array.getClass().getComponentType())));
347        for (int i = 0; i < array.length; i++) {
348            e.dup();
349            e.push(i);
350            push_object(e, array[i]);
351            e.aastore();
352        }
353    }
354
355    private static Class remapComponentType(Class componentType) {
356        if (componentType.equals(Type.class))
357            return Class.class;
358        return componentType;
359    }
360
361    public static void push_object(CodeEmitter e, Object obj) {
362        if (obj == null) {
363            e.aconst_null();
364        } else {
365            Class type = obj.getClass();
366            if (type.isArray()) {
367                push_array(e, (Object[])obj);
368            } else if (obj instanceof String) {
369                e.push((String)obj);
370            } else if (obj instanceof Type) {
371                load_class(e, (Type)obj);
372            } else if (obj instanceof Class) {
373                load_class(e, Type.getType((Class)obj));
374            } else if (obj instanceof BigInteger) {
375                e.new_instance(Constants.TYPE_BIG_INTEGER);
376                e.dup();
377                e.push(obj.toString());
378                e.invoke_constructor(Constants.TYPE_BIG_INTEGER);
379            } else if (obj instanceof BigDecimal) {
380                e.new_instance(Constants.TYPE_BIG_DECIMAL);
381                e.dup();
382                e.push(obj.toString());
383                e.invoke_constructor(Constants.TYPE_BIG_DECIMAL);
384            } else {
385                throw new IllegalArgumentException("unknown type: " + obj.getClass());
386            }
387        }
388    }
389
390    public static void hash_code(CodeEmitter e, Type type, int multiplier, Customizer customizer) {
391        if (TypeUtils.isArray(type)) {
392            hash_array(e, type, multiplier, customizer);
393        } else {
394            e.swap(Type.INT_TYPE, type);
395            e.push(multiplier);
396            e.math(e.MUL, Type.INT_TYPE);
397            e.swap(type, Type.INT_TYPE);
398            if (TypeUtils.isPrimitive(type)) {
399                hash_primitive(e, type);
400            } else {
401                hash_object(e, type, customizer);
402            }
403            e.math(e.ADD, Type.INT_TYPE);
404        }
405    }
406
407    private static void hash_array(final CodeEmitter e, Type type, final int multiplier, final Customizer customizer) {
408        Label skip = e.make_label();
409        Label end = e.make_label();
410        e.dup();
411        e.ifnull(skip);
412        EmitUtils.process_array(e, type, new ProcessArrayCallback() {
413            public void processElement(Type type) {
414                hash_code(e, type, multiplier, customizer);
415            }
416        });
417        e.goTo(end);
418        e.mark(skip);
419        e.pop();
420        e.mark(end);
421    }
422
423    private static void hash_object(CodeEmitter e, Type type, Customizer customizer) {
424        // (f == null) ? 0 : f.hashCode();
425        Label skip = e.make_label();
426        Label end = e.make_label();
427        e.dup();
428        e.ifnull(skip);
429        if (customizer != null) {
430            customizer.customize(e, type);
431        }
432        e.invoke_virtual(Constants.TYPE_OBJECT, HASH_CODE);
433        e.goTo(end);
434        e.mark(skip);
435        e.pop();
436        e.push(0);
437        e.mark(end);
438    }
439
440    private static void hash_primitive(CodeEmitter e, Type type) {
441        switch (type.getSort()) {
442        case Type.BOOLEAN:
443            // f ? 0 : 1
444            e.push(1);
445            e.math(e.XOR, Type.INT_TYPE);
446            break;
447        case Type.FLOAT:
448            // Float.floatToIntBits(f)
449            e.invoke_static(Constants.TYPE_FLOAT, FLOAT_TO_INT_BITS);
450            break;
451        case Type.DOUBLE:
452            // Double.doubleToLongBits(f), hash_code(Long.TYPE)
453            e.invoke_static(Constants.TYPE_DOUBLE, DOUBLE_TO_LONG_BITS);
454            // fall through
455        case Type.LONG:
456            hash_long(e);
457        }
458    }
459
460    private static void hash_long(CodeEmitter e) {
461        // (int)(f ^ (f >>> 32))
462        e.dup2();
463        e.push(32);
464        e.math(e.USHR, Type.LONG_TYPE);
465        e.math(e.XOR, Type.LONG_TYPE);
466        e.cast_numeric(Type.LONG_TYPE, Type.INT_TYPE);
467    }
468
469//     public static void not_equals(CodeEmitter e, Type type, Label notEquals) {
470//         not_equals(e, type, notEquals, null);
471//     }
472
473    /**
474     * Branches to the specified label if the top two items on the stack
475     * are not equal. The items must both be of the specified
476     * class. Equality is determined by comparing primitive values
477     * directly and by invoking the <code>equals</code> method for
478     * Objects. Arrays are recursively processed in the same manner.
479     */
480    public static void not_equals(final CodeEmitter e, Type type, final Label notEquals, final Customizer customizer) {
481        (new ProcessArrayCallback() {
482            public void processElement(Type type) {
483                not_equals_helper(e, type, notEquals, customizer, this);
484            }
485        }).processElement(type);
486    }
487
488    private static void not_equals_helper(CodeEmitter e,
489                                          Type type,
490                                          Label notEquals,
491                                          Customizer customizer,
492                                          ProcessArrayCallback callback) {
493        if (TypeUtils.isPrimitive(type)) {
494            e.if_cmp(type, e.NE, notEquals);
495        } else {
496            Label end = e.make_label();
497            nullcmp(e, notEquals, end);
498            if (TypeUtils.isArray(type)) {
499                Label checkContents = e.make_label();
500                e.dup2();
501                e.arraylength();
502                e.swap();
503                e.arraylength();
504                e.if_icmp(e.EQ, checkContents);
505                e.pop2();
506                e.goTo(notEquals);
507                e.mark(checkContents);
508                EmitUtils.process_arrays(e, type, callback);
509            } else {
510                if (customizer != null) {
511                    customizer.customize(e, type);
512                    e.swap();
513                    customizer.customize(e, type);
514                }
515                e.invoke_virtual(Constants.TYPE_OBJECT, EQUALS);
516                e.if_jump(e.EQ, notEquals);
517            }
518            e.mark(end);
519        }
520    }
521
522    /**
523     * If both objects on the top of the stack are non-null, does nothing.
524     * If one is null, or both are null, both are popped off and execution
525     * branches to the respective label.
526     * @param oneNull label to branch to if only one of the objects is null
527     * @param bothNull label to branch to if both of the objects are null
528     */
529    private static void nullcmp(CodeEmitter e, Label oneNull, Label bothNull) {
530        e.dup2();
531        Label nonNull = e.make_label();
532        Label oneNullHelper = e.make_label();
533        Label end = e.make_label();
534        e.ifnonnull(nonNull);
535        e.ifnonnull(oneNullHelper);
536        e.pop2();
537        e.goTo(bothNull);
538
539        e.mark(nonNull);
540        e.ifnull(oneNullHelper);
541        e.goTo(end);
542
543        e.mark(oneNullHelper);
544        e.pop2();
545        e.goTo(oneNull);
546
547        e.mark(end);
548    }
549
550    /*
551    public static void to_string(CodeEmitter e,
552                                 Type type,
553                                 ArrayDelimiters delims,
554                                 Customizer customizer) {
555        e.new_instance(Constants.TYPE_STRING_BUFFER);
556        e.dup();
557        e.invoke_constructor(Constants.TYPE_STRING_BUFFER);
558        e.swap();
559        append_string(e, type, delims, customizer);
560        e.invoke_virtual(Constants.TYPE_STRING_BUFFER, TO_STRING);
561    }
562    */
563
564    public static void append_string(final CodeEmitter e,
565                                     Type type,
566                                     final ArrayDelimiters delims,
567                                     final Customizer customizer) {
568        final ArrayDelimiters d = (delims != null) ? delims : DEFAULT_DELIMITERS;
569        ProcessArrayCallback callback = new ProcessArrayCallback() {
570            public void processElement(Type type) {
571                append_string_helper(e, type, d, customizer, this);
572                e.push(d.inside);
573                e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_STRING);
574            }
575        };
576        append_string_helper(e, type, d, customizer, callback);
577    }
578
579    private static void append_string_helper(CodeEmitter e,
580                                             Type type,
581                                             ArrayDelimiters delims,
582                                             Customizer customizer,
583                                             ProcessArrayCallback callback) {
584        Label skip = e.make_label();
585        Label end = e.make_label();
586        if (TypeUtils.isPrimitive(type)) {
587            switch (type.getSort()) {
588            case Type.INT:
589            case Type.SHORT:
590            case Type.BYTE:
591                e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_INT);
592                break;
593            case Type.DOUBLE:
594                e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_DOUBLE);
595                break;
596            case Type.FLOAT:
597                e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_FLOAT);
598                break;
599            case Type.LONG:
600                e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_LONG);
601                break;
602            case Type.BOOLEAN:
603                e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_BOOLEAN);
604                break;
605            case Type.CHAR:
606                e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_CHAR);
607                break;
608            }
609        } else if (TypeUtils.isArray(type)) {
610            e.dup();
611            e.ifnull(skip);
612            e.swap();
613            if (delims != null && delims.before != null && !"".equals(delims.before)) {
614                e.push(delims.before);
615                e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_STRING);
616                e.swap();
617            }
618            EmitUtils.process_array(e, type, callback);
619            shrinkStringBuffer(e, 2);
620            if (delims != null && delims.after != null && !"".equals(delims.after)) {
621                e.push(delims.after);
622                e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_STRING);
623            }
624        } else {
625            e.dup();
626            e.ifnull(skip);
627            if (customizer != null) {
628                customizer.customize(e, type);
629            }
630            e.invoke_virtual(Constants.TYPE_OBJECT, TO_STRING);
631            e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_STRING);
632        }
633        e.goTo(end);
634        e.mark(skip);
635        e.pop();
636        e.push("null");
637        e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_STRING);
638        e.mark(end);
639    }
640
641    private static void shrinkStringBuffer(CodeEmitter e, int amt) {
642        e.dup();
643        e.dup();
644        e.invoke_virtual(Constants.TYPE_STRING_BUFFER, LENGTH);
645        e.push(amt);
646        e.math(e.SUB, Type.INT_TYPE);
647        e.invoke_virtual(Constants.TYPE_STRING_BUFFER, SET_LENGTH);
648    }
649
650    public static class ArrayDelimiters {
651        private String before;
652        private String inside;
653        private String after;
654
655        public ArrayDelimiters(String before, String inside, String after) {
656            this.before = before;
657            this.inside = inside;
658            this.after = after;
659        }
660    }
661
662    public static void load_method(CodeEmitter e, MethodInfo method) {
663        load_class(e, method.getClassInfo().getType());
664        e.push(method.getSignature().getName());
665        push_object(e, method.getSignature().getArgumentTypes());
666        e.invoke_virtual(Constants.TYPE_CLASS, GET_DECLARED_METHOD);
667    }
668
669    private interface ParameterTyper {
670        Type[] getParameterTypes(MethodInfo member);
671    }
672
673    public static void method_switch(CodeEmitter e,
674                                     List methods,
675                                     ObjectSwitchCallback callback) {
676        member_switch_helper(e, methods, callback, true);
677    }
678
679    public static void constructor_switch(CodeEmitter e,
680                                          List constructors,
681                                          ObjectSwitchCallback callback) {
682        member_switch_helper(e, constructors, callback, false);
683    }
684
685    private static void member_switch_helper(final CodeEmitter e,
686                                             List members,
687                                             final ObjectSwitchCallback callback,
688                                             boolean useName) {
689        try {
690            final Map cache = new HashMap();
691            final ParameterTyper cached = new ParameterTyper() {
692                    public Type[] getParameterTypes(MethodInfo member) {
693                        Type[] types = (Type[])cache.get(member);
694                        if (types == null) {
695                            cache.put(member, types = member.getSignature().getArgumentTypes());
696                        }
697                        return types;
698                    }
699                };
700            final Label def = e.make_label();
701            final Label end = e.make_label();
702            if (useName) {
703                e.swap();
704                final Map buckets = CollectionUtils.bucket(members, new Transformer() {
705                        public Object transform(Object value) {
706                            return ((MethodInfo)value).getSignature().getName();
707                        }
708                    });
709                String[] names = (String[])buckets.keySet().toArray(new String[buckets.size()]);
710                EmitUtils.string_switch(e, names, Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() {
711                        public void processCase(Object key, Label dontUseEnd) throws Exception {
712                            member_helper_size(e, (List)buckets.get(key), callback, cached, def, end);
713                        }
714                        public void processDefault() throws Exception {
715                            e.goTo(def);
716                        }
717                    });
718            } else {
719                member_helper_size(e, members, callback, cached, def, end);
720            }
721            e.mark(def);
722            e.pop();
723            callback.processDefault();
724            e.mark(end);
725        } catch (RuntimeException ex) {
726            throw ex;
727        } catch (Error ex) {
728            throw ex;
729        } catch (Exception ex) {
730            throw new CodeGenerationException(ex);
731        }
732    }
733
734    private static void member_helper_size(final CodeEmitter e,
735                                           List members,
736                                           final ObjectSwitchCallback callback,
737                                           final ParameterTyper typer,
738                                           final Label def,
739                                           final Label end) throws Exception {
740        final Map buckets = CollectionUtils.bucket(members, new Transformer() {
741            public Object transform(Object value) {
742                return new Integer(typer.getParameterTypes((MethodInfo)value).length);
743            }
744        });
745        e.dup();
746        e.arraylength();
747        e.process_switch(EmitUtils.getSwitchKeys(buckets), new ProcessSwitchCallback() {
748            public void processCase(int key, Label dontUseEnd) throws Exception {
749                List bucket = (List)buckets.get(new Integer(key));
750                member_helper_type(e, bucket, callback, typer, def, end, new BitSet());
751            }
752            public void processDefault() throws Exception {
753                e.goTo(def);
754            }
755        });
756    }
757
758    private static void member_helper_type(final CodeEmitter e,
759                                           List members,
760                                           final ObjectSwitchCallback callback,
761                                           final ParameterTyper typer,
762                                           final Label def,
763                                           final Label end,
764                                           final BitSet checked) throws Exception {
765        if (members.size() == 1) {
766            MethodInfo member = (MethodInfo)members.get(0);
767            Type[] types = typer.getParameterTypes(member);
768            // need to check classes that have not already been checked via switches
769            for (int i = 0; i < types.length; i++) {
770                if (checked == null || !checked.get(i)) {
771                    e.dup();
772                    e.aaload(i);
773                    e.invoke_virtual(Constants.TYPE_CLASS, GET_NAME);
774                    e.push(TypeUtils.emulateClassGetName(types[i]));
775                    e.invoke_virtual(Constants.TYPE_OBJECT, EQUALS);
776                    e.if_jump(e.EQ, def);
777                }
778            }
779            e.pop();
780            callback.processCase(member, end);
781        } else {
782            // choose the index that has the best chance of uniquely identifying member
783            Type[] example = typer.getParameterTypes((MethodInfo)members.get(0));
784            Map buckets = null;
785            int index = -1;
786            for (int i = 0; i < example.length; i++) {
787                final int j = i;
788                Map test = CollectionUtils.bucket(members, new Transformer() {
789                    public Object transform(Object value) {
790                        return TypeUtils.emulateClassGetName(typer.getParameterTypes((MethodInfo)value)[j]);
791                    }
792                });
793                if (buckets == null || test.size() > buckets.size()) {
794                    buckets = test;
795                    index = i;
796                }
797            }
798            if (buckets == null || buckets.size() == 1) {
799                // TODO: switch by returnType
800                // must have two methods with same name, types, and different return types
801                e.goTo(def);
802            } else {
803                checked.set(index);
804
805                e.dup();
806                e.aaload(index);
807                e.invoke_virtual(Constants.TYPE_CLASS, GET_NAME);
808
809                final Map fbuckets = buckets;
810                String[] names = (String[])buckets.keySet().toArray(new String[buckets.size()]);
811                EmitUtils.string_switch(e, names, Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() {
812                    public void processCase(Object key, Label dontUseEnd) throws Exception {
813                        member_helper_type(e, (List)fbuckets.get(key), callback, typer, def, end, checked);
814                    }
815                    public void processDefault() throws Exception {
816                        e.goTo(def);
817                    }
818                });
819            }
820        }
821    }
822
823    public static void wrap_throwable(Block block, Type wrapper) {
824        CodeEmitter e = block.getCodeEmitter();
825        e.catch_exception(block, Constants.TYPE_THROWABLE);
826        e.new_instance(wrapper);
827        e.dup_x1();
828        e.swap();
829        e.invoke_constructor(wrapper, CSTRUCT_THROWABLE);
830        e.athrow();
831    }
832
833    public static void add_properties(ClassEmitter ce, String[] names, Type[] types) {
834        for (int i = 0; i < names.length; i++) {
835            String fieldName = "$cglib_prop_" + names[i];
836            ce.declare_field(Constants.ACC_PRIVATE, fieldName, types[i], null);
837            EmitUtils.add_property(ce, names[i], types[i], fieldName);
838        }
839    }
840
841    public static void add_property(ClassEmitter ce, String name, Type type, String fieldName) {
842        String property = TypeUtils.upperFirst(name);
843        CodeEmitter e;
844        e = ce.begin_method(Constants.ACC_PUBLIC,
845                            new Signature("get" + property,
846                                          type,
847                                          Constants.TYPES_EMPTY),
848                            null);
849        e.load_this();
850        e.getfield(fieldName);
851        e.return_value();
852        e.end_method();
853
854        e = ce.begin_method(Constants.ACC_PUBLIC,
855                            new Signature("set" + property,
856                                          Type.VOID_TYPE,
857                                          new Type[]{ type }),
858                            null);
859        e.load_this();
860        e.load_arg(0);
861        e.putfield(fieldName);
862        e.return_value();
863        e.end_method();
864    }
865
866    /* generates:
867       } catch (RuntimeException e) {
868         throw e;
869       } catch (Error e) {
870         throw e;
871       } catch (<DeclaredException> e) {
872         throw e;
873       } catch (Throwable e) {
874         throw new <Wrapper>(e);
875       }
876    */
877    public static void wrap_undeclared_throwable(CodeEmitter e, Block handler, Type[] exceptions, Type wrapper) {
878        Set set = (exceptions == null) ? Collections.EMPTY_SET : new HashSet(Arrays.asList(exceptions));
879
880        if (set.contains(Constants.TYPE_THROWABLE))
881            return;
882
883        boolean needThrow = exceptions != null;
884        if (!set.contains(Constants.TYPE_RUNTIME_EXCEPTION)) {
885            e.catch_exception(handler, Constants.TYPE_RUNTIME_EXCEPTION);
886            needThrow = true;
887        }
888        if (!set.contains(Constants.TYPE_ERROR)) {
889            e.catch_exception(handler, Constants.TYPE_ERROR);
890            needThrow = true;
891        }
892        if (exceptions != null) {
893            for (int i = 0; i < exceptions.length; i++) {
894                e.catch_exception(handler, exceptions[i]);
895            }
896        }
897        if (needThrow) {
898            e.athrow();
899        }
900        // e -> eo -> oeo -> ooe -> o
901        e.catch_exception(handler, Constants.TYPE_THROWABLE);
902        e.new_instance(wrapper);
903        e.dup_x1();
904        e.swap();
905        e.invoke_constructor(wrapper, CSTRUCT_THROWABLE);
906        e.athrow();
907    }
908
909    public static CodeEmitter begin_method(ClassEmitter e, MethodInfo method) {
910        return begin_method(e, method, method.getModifiers());
911    }
912
913    public static CodeEmitter begin_method(ClassEmitter e, MethodInfo method, int access) {
914        return e.begin_method(access,
915                              method.getSignature(),
916                              method.getExceptionTypes());
917    }
918}
919