• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
16 package org.mockito.cglib.core;
17 
18 import java.math.BigDecimal;
19 import java.math.BigInteger;
20 import java.util.*;
21 
22 import org.mockito.asm.Label;
23 import org.mockito.asm.Type;
24 
25 public 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 
EmitUtils()74     private EmitUtils() {
75     }
76 
factory_method(ClassEmitter ce, Signature sig)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 
null_constructor(ClassEmitter ce)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      */
process_array(CodeEmitter e, Type type, ProcessArrayCallback callback)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      */
process_arrays(CodeEmitter e, Type type, ProcessArrayCallback callback)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 
string_switch(CodeEmitter e, String[] strings, int switchStyle, ObjectSwitchCallback callback)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 
string_switch_trie(final CodeEmitter e, String[] strings, final ObjectSwitchCallback callback)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 
stringSwitchHelper(final CodeEmitter e, List strings, final ObjectSwitchCallback callback, final Label def, final Label end, final int index)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 
getSwitchKeys(Map buckets)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 
string_switch_hash(final CodeEmitter e, final String[] strings, final ObjectSwitchCallback callback, final boolean skipEquals)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 
load_class_this(CodeEmitter e)307     public static void load_class_this(CodeEmitter e) {
308         load_class_helper(e, e.getClassEmitter().getClassType());
309     }
310 
load_class(CodeEmitter e, Type type)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 
load_class_helper(CodeEmitter e, final Type type)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 
push_array(CodeEmitter e, Object[] array)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 
remapComponentType(Class componentType)355     private static Class remapComponentType(Class componentType) {
356         if (componentType.equals(Type.class))
357             return Class.class;
358         return componentType;
359     }
360 
push_object(CodeEmitter e, Object obj)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 
hash_code(CodeEmitter e, Type type, int multiplier, Customizer customizer)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 
hash_array(final CodeEmitter e, Type type, final int multiplier, final Customizer customizer)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 
hash_object(CodeEmitter e, Type type, Customizer customizer)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 
hash_primitive(CodeEmitter e, Type type)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 
hash_long(CodeEmitter e)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      */
not_equals(final CodeEmitter e, Type type, final Label notEquals, final Customizer customizer)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 
not_equals_helper(CodeEmitter e, Type type, Label notEquals, Customizer customizer, ProcessArrayCallback callback)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      */
nullcmp(CodeEmitter e, Label oneNull, Label bothNull)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 
append_string(final CodeEmitter e, Type type, final ArrayDelimiters delims, final Customizer customizer)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 
append_string_helper(CodeEmitter e, Type type, ArrayDelimiters delims, Customizer customizer, ProcessArrayCallback callback)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 
shrinkStringBuffer(CodeEmitter e, int amt)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 
ArrayDelimiters(String before, String inside, String after)655         public ArrayDelimiters(String before, String inside, String after) {
656             this.before = before;
657             this.inside = inside;
658             this.after = after;
659         }
660     }
661 
load_method(CodeEmitter e, MethodInfo method)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 {
getParameterTypes(MethodInfo member)670         Type[] getParameterTypes(MethodInfo member);
671     }
672 
method_switch(CodeEmitter e, List methods, ObjectSwitchCallback callback)673     public static void method_switch(CodeEmitter e,
674                                      List methods,
675                                      ObjectSwitchCallback callback) {
676         member_switch_helper(e, methods, callback, true);
677     }
678 
constructor_switch(CodeEmitter e, List constructors, ObjectSwitchCallback callback)679     public static void constructor_switch(CodeEmitter e,
680                                           List constructors,
681                                           ObjectSwitchCallback callback) {
682         member_switch_helper(e, constructors, callback, false);
683     }
684 
member_switch_helper(final CodeEmitter e, List members, final ObjectSwitchCallback callback, boolean useName)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 
member_helper_size(final CodeEmitter e, List members, final ObjectSwitchCallback callback, final ParameterTyper typer, final Label def, final Label end)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 
member_helper_type(final CodeEmitter e, List members, final ObjectSwitchCallback callback, final ParameterTyper typer, final Label def, final Label end, final BitSet checked)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 
wrap_throwable(Block block, Type wrapper)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 
add_properties(ClassEmitter ce, String[] names, Type[] types)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 
add_property(ClassEmitter ce, String name, Type type, String fieldName)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     */
wrap_undeclared_throwable(CodeEmitter e, Block handler, Type[] exceptions, Type wrapper)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 
begin_method(ClassEmitter e, MethodInfo method)909     public static CodeEmitter begin_method(ClassEmitter e, MethodInfo method) {
910         return begin_method(e, method, method.getModifiers());
911     }
912 
begin_method(ClassEmitter e, MethodInfo method, int access)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