View Javadoc
1   /*
2    * Copyright 2002-2014 the original author or authors.
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  
17  package org.springframework.expression.spel;
18  
19  import java.lang.reflect.Constructor;
20  import java.lang.reflect.Method;
21  import java.util.ArrayList;
22  import java.util.List;
23  import java.util.Stack;
24  
25  import org.springframework.asm.ClassWriter;
26  import org.springframework.asm.MethodVisitor;
27  import org.springframework.asm.Opcodes;
28  import org.springframework.util.Assert;
29  
30  /**
31   * Manages the class being generated by the compilation process. It records
32   * intermediate compilation state as the bytecode is generated. It also includes
33   * various bytecode generation helper functions.
34   *
35   * @author Andy Clement
36   * @since 4.1
37   */
38  public class CodeFlow implements Opcodes {
39  
40  	/**
41  	 * Record the type of what is on top of the bytecode stack (i.e. the type of the
42  	 * output from the previous expression component). New scopes are used to evaluate
43  	 * sub-expressions like the expressions for the argument values in a method invocation
44  	 * expression.
45  	 */
46  	private final Stack<ArrayList<String>> compilationScopes;
47  
48  	/**
49  	 * The current class being generated
50  	 */
51  	private ClassWriter cw;
52  
53  	/**
54  	 * As SpEL ast nodes are called to generate code for the main evaluation method
55  	 * they can register to add a field to this class. Any registered FieldAdders
56  	 * will be called after the main evaluation function has finished being generated.
57  	 */
58  	private List<FieldAdder> fieldAdders = null;
59  	
60  	/**
61  	 * As SpEL ast nodes are called to generate code for the main evaluation method
62  	 * they can register to add code to a static initializer in the class. Any
63  	 * registered ClinitAdders will be called after the main evaluation function
64  	 * has finished being generated.
65  	 */
66  	private List<ClinitAdder> clinitAdders = null;
67  	
68  	/**
69  	 * Name of the class being generated. Typically used when generating code
70  	 * that accesses freshly generated fields on the generated type.
71  	 */
72  	private String clazzName;
73  	
74  	/**
75  	 * When code generation requires holding a value in a class level field, this
76  	 * is used to track the next available field id (used as a name suffix).
77  	 */
78  	private int nextFieldId = 1;
79  	
80  	/**
81  	 * When code generation requires an intermediate variable within a method,
82  	 * this method records the next available variable (variable 0 is 'this').
83  	 */
84  	private int nextFreeVariableId = 1;
85  
86  	public CodeFlow(String clazzName, ClassWriter cw) {
87  		this.compilationScopes = new Stack<ArrayList<String>>();
88  		this.compilationScopes.add(new ArrayList<String>());
89  		this.cw = cw;
90  		this.clazzName = clazzName;
91  	}
92  
93  	/**
94  	 * Push the byte code to load the target (i.e. what was passed as the first argument
95  	 * to CompiledExpression.getValue(target, context))
96  	 * @param mv the visitor into which the load instruction should be inserted
97  	 */
98  	public void loadTarget(MethodVisitor mv) {
99  		mv.visitVarInsn(ALOAD, 1);
100 	}
101 
102 	/**
103 	 * Record the descriptor for the most recently evaluated expression element.
104 	 * @param descriptor type descriptor for most recently evaluated element
105 	 */
106 	public void pushDescriptor(String descriptor) {
107 		Assert.notNull(descriptor, "Descriptor must not be null");
108 		this.compilationScopes.peek().add(descriptor);
109 	}
110 
111 	/**
112 	 * Enter a new compilation scope, usually due to nested expression evaluation. For
113 	 * example when the arguments for a method invocation expression are being evaluated,
114 	 * each argument will be evaluated in a new scope.
115 	 */
116 	public void enterCompilationScope() {
117 		this.compilationScopes.push(new ArrayList<String>());
118 	}
119 
120 	/**
121 	 * Exit a compilation scope, usually after a nested expression has been evaluated. For
122 	 * example after an argument for a method invocation has been evaluated this method
123 	 * returns us to the previous (outer) scope.
124 	 */
125 	public void exitCompilationScope() {
126 		this.compilationScopes.pop();
127 	}
128 
129 	/**
130 	 * Return the descriptor for the item currently on top of the stack (in the current scope).
131 	 */
132 	public String lastDescriptor() {
133 		if (this.compilationScopes.peek().isEmpty()) {
134 			return null;
135 		}
136 		return this.compilationScopes.peek().get(this.compilationScopes.peek().size() - 1);
137 	}
138 
139 	/**
140 	 * If the codeflow shows the last expression evaluated to java.lang.Boolean then
141 	 * insert the necessary instructions to unbox that to a boolean primitive.
142 	 * @param mv the visitor into which new instructions should be inserted
143 	 */
144 	public void unboxBooleanIfNecessary(MethodVisitor mv) {
145 		if (lastDescriptor().equals("Ljava/lang/Boolean")) {
146 			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
147 		}
148 	}
149 
150 	/**
151 	 * Insert any necessary cast and value call to convert from a boxed type to a
152 	 * primitive value
153 	 * @param mv the method visitor into which instructions should be inserted
154 	 * @param ch the primitive type desired as output
155 	 * @param stackDescriptor the descriptor of the type on top of the stack
156 	 */
157 	public static void insertUnboxInsns(MethodVisitor mv, char ch, String stackDescriptor) {
158 		switch (ch) {
159 			case 'Z':
160 				if (!stackDescriptor.equals("Ljava/lang/Boolean")) {
161 					mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
162 				}
163 				mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
164 				break;
165 			case 'B':
166 				if (!stackDescriptor.equals("Ljava/lang/Byte")) {
167 					mv.visitTypeInsn(CHECKCAST, "java/lang/Byte");
168 				}
169 				mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false);
170 				break;
171 			case 'C':
172 				if (!stackDescriptor.equals("Ljava/lang/Character")) {
173 					mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
174 				}
175 				mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false);
176 				break;
177 			case 'D':
178 				if (!stackDescriptor.equals("Ljava/lang/Double")) {
179 					mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
180 				}
181 				mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false);
182 				break;
183 			case 'F':
184 				if (!stackDescriptor.equals("Ljava/lang/Float")) {
185 					mv.visitTypeInsn(CHECKCAST, "java/lang/Float");
186 				}
187 				mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false);
188 				break;
189 			case 'I':
190 				if (!stackDescriptor.equals("Ljava/lang/Integer")) {
191 					mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
192 				}
193 				mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
194 				break;
195 			case 'J':
196 				if (!stackDescriptor.equals("Ljava/lang/Long")) {
197 					mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
198 				}
199 				mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
200 				break;
201 			case 'S':
202 				if (!stackDescriptor.equals("Ljava/lang/Short")) {
203 					mv.visitTypeInsn(CHECKCAST, "java/lang/Short");
204 				}
205 				mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false);
206 				break;
207 			default:
208 				throw new IllegalArgumentException("Unboxing should not be attempted for descriptor '" + ch + "'");
209 		}
210 	}
211 
212 	/**
213 	 * Create the JVM signature descriptor for a method. This consists of the descriptors
214 	 * for the constructor parameters surrounded with parentheses, followed by the
215 	 * descriptor for the return type. Note the descriptors here are JVM descriptors,
216 	 * unlike the other descriptor forms the compiler is using which do not include the
217 	 * trailing semicolon.
218 	 * @param method the method
219 	 * @return a String signature descriptor (e.g. "(ILjava/lang/String;)V")
220 	 */
221 	public static String createSignatureDescriptor(Method method) {
222 		Class<?>[] params = method.getParameterTypes();
223 		StringBuilder sb = new StringBuilder();
224 		sb.append("(");
225 		for (Class<?> param : params) {
226 			sb.append(toJvmDescriptor(param));
227 		}
228 		sb.append(")");
229 		sb.append(toJvmDescriptor(method.getReturnType()));
230 		return sb.toString();
231 	}
232 
233 	/**
234 	 * Create the JVM signature descriptor for a constructor. This consists of the
235 	 * descriptors for the constructor parameters surrounded with parentheses. Note the
236 	 * descriptors here are JVM descriptors, unlike the other descriptor forms the
237 	 * compiler is using which do not include the trailing semicolon.
238 	 * @param ctor the constructor
239 	 * @return a String signature descriptor (e.g. "(ILjava/lang/String;)")
240 	 */
241 	public static String createSignatureDescriptor(Constructor<?> ctor) {
242 		Class<?>[] params = ctor.getParameterTypes();
243 		StringBuilder sb = new StringBuilder();
244 		sb.append("(");
245 		for (Class<?> param : params) {
246 			sb.append(toJvmDescriptor(param));
247 		}
248 		sb.append(")V");
249 		return sb.toString();
250 	}
251 
252 	/**
253 	 * Determine the JVM descriptor for a specified class. Unlike the other descriptors
254 	 * used in the compilation process, this is the one the JVM wants, so this one
255 	 * includes any necessary trailing semicolon (e.g. Ljava/lang/String; rather than
256 	 * Ljava/lang/String)
257 	 * @param clazz a class
258 	 * @return the JVM descriptor for the class
259 	 */
260 	public static String toJvmDescriptor(Class<?> clazz) {
261 		StringBuilder sb = new StringBuilder();
262 		if (clazz.isArray()) {
263 			while (clazz.isArray()) {
264 				sb.append("[");
265 				clazz = clazz.getComponentType();
266 			}
267 		}
268 		if (clazz.isPrimitive()) {
269 			if (clazz == Void.TYPE) {
270 				sb.append('V');
271 			}
272 			else if (clazz == Integer.TYPE) {
273 				sb.append('I');
274 			}
275 			else if (clazz == Boolean.TYPE) {
276 				sb.append('Z');
277 			}
278 			else if (clazz == Character.TYPE) {
279 				sb.append('C');
280 			}
281 			else if (clazz == Long.TYPE) {
282 				sb.append('J');
283 			}
284 			else if (clazz == Double.TYPE) {
285 				sb.append('D');
286 			}
287 			else if (clazz == Float.TYPE) {
288 				sb.append('F');
289 			}
290 			else if (clazz == Byte.TYPE) {
291 				sb.append('B');
292 			}
293 			else if (clazz == Short.TYPE) {
294 				sb.append('S');
295 			}
296 		}
297 		else {
298 			sb.append("L");
299 			sb.append(clazz.getName().replace('.', '/'));
300 			sb.append(";");
301 		}
302 		return sb.toString();
303 	}
304 
305 	/**
306 	 * Determine the descriptor for an object instance (or {@code null}).
307 	 * @param value an object (possibly {@code null})
308 	 * @return the type descriptor for the object
309 	 * (descriptor is "Ljava/lang/Object" for {@code null} value)
310 	 */
311 	public static String toDescriptorFromObject(Object value) {
312 		if (value == null) {
313 			return "Ljava/lang/Object";
314 		}
315 		else {
316 			return toDescriptor(value.getClass());
317 		}
318 	}
319 
320 	/**
321 	 * @param descriptor type descriptor
322 	 * @return {@code true} if the descriptor is for a boolean primitive or boolean reference type
323 	 */
324 	public static boolean isBooleanCompatible(String descriptor) {
325 		return (descriptor != null && (descriptor.equals("Z") || descriptor.equals("Ljava/lang/Boolean")));
326 	}
327 
328 	/**
329 	 * @param descriptor type descriptor
330 	 * @return {@code true} if the descriptor is for a primitive type
331 	 */
332 	public static boolean isPrimitive(String descriptor) {
333 		return (descriptor != null && descriptor.length() == 1);
334 	}
335 
336 	/**
337 	 * @param descriptor the descriptor for a possible primitive array
338 	 * @return {@code true} if the descriptor is for a primitive array (e.g. "[[I")
339 	 */
340 	public static boolean isPrimitiveArray(String descriptor) {
341 		boolean primitive = true;
342 		for (int i = 0, max = descriptor.length(); i < max; i++) {
343 			char ch = descriptor.charAt(i);
344 			if (ch == '[') {
345 				continue;
346 			}
347 			primitive = (ch != 'L');
348 			break;
349 		}
350 		return primitive;
351 	}
352 
353 	/**
354 	 * Determine if boxing/unboxing can get from one type to the other. Assumes at least
355 	 * one of the types is in boxed form (i.e. single char descriptor).
356 	 * @return {@code true} if it is possible to get (via boxing) from one descriptor to the other
357 	 */
358 	public static boolean areBoxingCompatible(String desc1, String desc2) {
359 		if (desc1.equals(desc2)) {
360 			return true;
361 		}
362 		if (desc1.length() == 1) {
363 			if (desc1.equals("Z")) {
364 				return desc2.equals("Ljava/lang/Boolean");
365 			}
366 			else if (desc1.equals("D")) {
367 				return desc2.equals("Ljava/lang/Double");
368 			}
369 			else if (desc1.equals("F")) {
370 				return desc2.equals("Ljava/lang/Float");
371 			}
372 			else if (desc1.equals("I")) {
373 				return desc2.equals("Ljava/lang/Integer");
374 			}
375 			else if (desc1.equals("J")) {
376 				return desc2.equals("Ljava/lang/Long");
377 			}
378 		}
379 		else if (desc2.length() == 1) {
380 			if (desc2.equals("Z")) {
381 				return desc1.equals("Ljava/lang/Boolean");
382 			}
383 			else if (desc2.equals("D")) {
384 				return desc1.equals("Ljava/lang/Double");
385 			}
386 			else if (desc2.equals("F")) {
387 				return desc1.equals("Ljava/lang/Float");
388 			}
389 			else if (desc2.equals("I")) {
390 				return desc1.equals("Ljava/lang/Integer");
391 			}
392 			else if (desc2.equals("J")) {
393 				return desc1.equals("Ljava/lang/Long");
394 			}
395 		}
396 		return false;
397 	}
398 
399 	/**
400 	 * Determine if the supplied descriptor is for a supported number type or boolean. The
401 	 * compilation process only (currently) supports certain number types. These are
402 	 * double, float, long and int.
403 	 * @param descriptor the descriptor for a type
404 	 * @return {@code true} if the descriptor is for a supported numeric type or boolean
405 	 */
406 	public static boolean isPrimitiveOrUnboxableSupportedNumberOrBoolean(String descriptor) {
407 		if (descriptor == null) {
408 			return false;
409 		}
410 		if (isPrimitiveOrUnboxableSupportedNumber(descriptor)) {
411 			return true;
412 		}
413 		return ("Z".equals(descriptor) || descriptor.equals("Ljava/lang/Boolean"));
414 	}
415 
416 	/**
417 	 * Determine if the supplied descriptor is for a supported number. The compilation
418 	 * process only (currently) supports certain number types. These are double, float,
419 	 * long and int.
420 	 * @param descriptor the descriptor for a type
421 	 * @return {@code true} if the descriptor is for a supported numeric type
422 	 */
423 	public static boolean isPrimitiveOrUnboxableSupportedNumber(String descriptor) {
424 		if (descriptor == null) {
425 			return false;
426 		}
427 		if (descriptor.length() == 1) {
428 			return "DFIJ".contains(descriptor);
429 		}
430 		if (descriptor.startsWith("Ljava/lang/")) {
431 			String name = descriptor.substring("Ljava/lang/".length());
432 			if (name.equals("Double") || name.equals("Float") || name.equals("Integer") || name.equals("Long")) {
433 				return true;
434 			}
435 		}
436 		return false;
437 	}
438 
439 	/**
440 	 * Determine whether the given number is to be considered as an integer
441 	 * for the purposes of a numeric operation at the bytecode level.
442 	 * @param number the number to check
443 	 * @return {@code true} if it is an {@link Integer}, {@link Short} or {@link Byte}
444 	 */
445 	public static boolean isIntegerForNumericOp(Number number) {
446 		return (number instanceof Integer || number instanceof Short || number instanceof Byte);
447 	}
448 
449 	/**
450 	 * @param descriptor a descriptor for a type that should have a primitive representation
451 	 * @return the single character descriptor for a primitive input descriptor
452 	 */
453 	public static char toPrimitiveTargetDesc(String descriptor) {
454 		if (descriptor.length() == 1) {
455 			return descriptor.charAt(0);
456 		}
457 		else if (descriptor.equals("Ljava/lang/Boolean")) {
458 			return 'Z';
459 		}
460 		else if (descriptor.equals("Ljava/lang/Byte")) {
461 			return 'B';
462 		}
463 		else if (descriptor.equals("Ljava/lang/Character")) {
464 			return 'C';
465 		}
466 		else if (descriptor.equals("Ljava/lang/Double")) {
467 			return 'D';
468 		}
469 		else if (descriptor.equals("Ljava/lang/Float")) {
470 			return 'F';
471 		}
472 		else if (descriptor.equals("Ljava/lang/Integer")) {
473 			return 'I';
474 		}
475 		else if (descriptor.equals("Ljava/lang/Long")) {
476 			return 'J';
477 		}
478 		else if (descriptor.equals("Ljava/lang/Short")) {
479 			return 'S';
480 		}
481 		else {
482 			throw new IllegalStateException("No primitive for '" + descriptor + "'");
483 		}
484 	}
485 
486 	/**
487 	 * Insert the appropriate CHECKCAST instruction for the supplied descriptor.
488 	 * @param mv the target visitor into which the instruction should be inserted
489 	 * @param descriptor the descriptor of the type to cast to
490 	 */
491 	public static void insertCheckCast(MethodVisitor mv, String descriptor) {
492 		if (descriptor.length() != 1) {
493 			if (descriptor.charAt(0) == '[') {
494 				if (isPrimitiveArray(descriptor)) {
495 					mv.visitTypeInsn(CHECKCAST, descriptor);
496 				}
497 				else {
498 					mv.visitTypeInsn(CHECKCAST, descriptor + ";");
499 				}
500 			}
501 			else {
502 				if (!descriptor.equals("Ljava/lang/Object")) {
503 					// This is chopping off the 'L' to leave us with "java/lang/String"
504 					mv.visitTypeInsn(CHECKCAST, descriptor.substring(1));
505 				}
506 			}
507 		}
508 	}
509 
510 	/**
511 	 * Determine the appropriate boxing instruction for a specific type (if it needs
512 	 * boxing) and insert the instruction into the supplied visitor.
513 	 * @param mv the target visitor for the new instructions
514 	 * @param descriptor the descriptor of a type that may or may not need boxing
515 	 */
516 	public static void insertBoxIfNecessary(MethodVisitor mv, String descriptor) {
517 		if (descriptor.length() == 1) {
518 			insertBoxIfNecessary(mv, descriptor.charAt(0));
519 		}
520 	}
521 
522 	/**
523 	 * Determine the appropriate boxing instruction for a specific type (if it needs
524 	 * boxing) and insert the instruction into the supplied visitor.
525 	 * @param mv the target visitor for the new instructions
526 	 * @param ch the descriptor of the type that might need boxing
527 	 */
528 	public static void insertBoxIfNecessary(MethodVisitor mv, char ch) {
529 		switch (ch) {
530 			case 'Z':
531 				mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
532 				break;
533 			case 'B':
534 				mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false);
535 				break;
536 			case 'C':
537 				mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", false);
538 				break;
539 			case 'D':
540 				mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
541 				break;
542 			case 'F':
543 				mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false);
544 				break;
545 			case 'I':
546 				mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
547 				break;
548 			case 'J':
549 				mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
550 				break;
551 			case 'S':
552 				mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false);
553 				break;
554 			case 'L':
555 			case 'V':
556 			case '[':
557 				// no box needed
558 				break;
559 			default:
560 				throw new IllegalArgumentException("Boxing should not be attempted for descriptor '" + ch + "'");
561 		}
562 	}
563 
564 	/**
565 	 * Deduce the descriptor for a type. Descriptors are like JVM type names but missing
566 	 * the trailing ';' so for Object the descriptor is "Ljava/lang/Object" for int it is
567 	 * "I".
568 	 * @param type the type (may be primitive) for which to determine the descriptor
569 	 * @return the descriptor
570 	 */
571 	public static String toDescriptor(Class<?> type) {
572 		String name = type.getName();
573 		if (type.isPrimitive()) {
574 			switch (name.length()) {
575 				case 3:
576 					return "I";
577 				case 4:
578 					if (name.equals("byte")) {
579 						return "B";
580 					}
581 					else if (name.equals("char")) {
582 						return "C";
583 					}
584 					else if (name.equals("long")) {
585 						return "J";
586 					}
587 					else if (name.equals("void")) {
588 						return "V";
589 					}
590 					break;
591 				case 5:
592 					if (name.equals("float")) {
593 						return "F";
594 					}
595 					else if (name.equals("short")) {
596 						return "S";
597 					}
598 					break;
599 				case 6:
600 					if (name.equals("double")) {
601 						return "D";
602 					}
603 					break;
604 				case 7:
605 					if (name.equals("boolean")) {
606 						return "Z";
607 					}
608 					break;
609 			}
610 		}
611 		else {
612 			if (name.charAt(0) != '[') {
613 				return "L" + type.getName().replace('.', '/');
614 			}
615 			else {
616 				if (name.endsWith(";")) {
617 					return name.substring(0, name.length() - 1).replace('.', '/');
618 				}
619 				else {
620 					return name;  // array has primitive component type
621 				}
622 			}
623 		}
624 		return null;
625 	}
626 
627 	/**
628 	 * Create an array of descriptors representing the parameter types for the supplied
629 	 * method. Returns a zero sized array if there are no parameters.
630 	 * @param method a Method
631 	 * @return a String array of descriptors, one entry for each method parameter
632 	 */
633 	public static String[] toParamDescriptors(Method method) {
634 		return toDescriptors(method.getParameterTypes());
635 	}
636 
637 	/**
638 	 * Create an array of descriptors representing the parameter types for the supplied
639 	 * constructor. Returns a zero sized array if there are no parameters.
640 	 * @param ctor a Constructor
641 	 * @return a String array of descriptors, one entry for each constructor parameter
642 	 */
643 	public static String[] toParamDescriptors(Constructor<?> ctor) {
644 		return toDescriptors(ctor.getParameterTypes());
645 	}
646 
647 	/**
648 	 * Create an array of descriptors from an array of classes.
649 	 * @param types the input array of classes
650 	 * @return an array of descriptors
651 	 */
652 	public static String[] toDescriptors(Class<?>[] types) {
653 		int typesCount = types.length;
654 		String[] descriptors = new String[typesCount];
655 		for (int p = 0; p < typesCount; p++) {
656 			descriptors[p] = toDescriptor(types[p]);
657 		}
658 		return descriptors;
659 	}
660 	
661 	/**
662 	 * Called after the main expression evaluation method has been generated, this
663 	 * method will callback any registered FieldAdders or ClinitAdders to add any
664 	 * extra information to the class representing the compiled expression.
665 	 */
666 	public void finish() {
667 		if (fieldAdders != null) {
668 			for (FieldAdder fieldAdder: fieldAdders) {
669 				fieldAdder.generateField(cw,this);
670 			}
671 		}
672 		if (clinitAdders != null) {
673 			MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "<clinit>", "()V", null, null);
674 			mv.visitCode();
675 			nextFreeVariableId = 0; // To 0 because there is no 'this' in a clinit
676 			for (ClinitAdder clinitAdder: clinitAdders) {
677 				clinitAdder.generateCode(mv, this);
678 			}
679 			mv.visitInsn(RETURN);
680 			mv.visitMaxs(0,0); // not supplied due to COMPUTE_MAXS
681 			mv.visitEnd();
682 		}
683 	}
684 
685 	/**
686 	 * Register a FieldAdder which will add a new field to the generated
687 	 * class to support the code produced by an ast nodes primary
688 	 * generateCode() method.
689 	 */
690 	public void registerNewField(FieldAdder fieldAdder) {
691 		if (fieldAdders == null) {
692 			fieldAdders = new ArrayList<FieldAdder>();
693 		}
694 		fieldAdders.add(fieldAdder);
695 	}
696 
697 	/**
698 	 * Register a ClinitAdder which will add code to the static 
699 	 * initializer in the generated class to support the code
700 	 * produced by an ast nodes primary generateCode() method.
701 	 */
702 	public void registerNewClinit(ClinitAdder clinitAdder) {
703 		if (clinitAdders == null) {
704 			clinitAdders = new ArrayList<ClinitAdder>();
705 		}
706 		clinitAdders.add(clinitAdder);
707 	}
708 
709 	public int nextFieldId() {
710 		return nextFieldId++;
711 	}
712 
713 	public int nextFreeVariableId() {
714 		return nextFreeVariableId++;
715 	}
716 
717 	public String getClassname() {
718 		return clazzName;
719 	}
720 	
721 	public interface FieldAdder {
722 		public void generateField(ClassWriter cw, CodeFlow codeflow);
723 	}
724 	
725 	public interface ClinitAdder {
726 		public void generateCode(MethodVisitor mv, CodeFlow codeflow);
727 	}
728 
729 	/**
730 	 * Create the optimal instruction for loading a number on the stack.
731 	 * @param mv where to insert the bytecode
732 	 * @param value the value to be loaded
733 	 */
734 	public static void insertOptimalLoad(MethodVisitor mv, int value) {
735 		if (value < 6) {
736 			mv.visitInsn(ICONST_0+value);
737 		}
738 		else if (value < Byte.MAX_VALUE) {
739 			mv.visitIntInsn(BIPUSH, value);
740 		}
741 		else if (value < Short.MAX_VALUE) {
742 			mv.visitIntInsn(SIPUSH, value);
743 		}
744 		else {
745 			mv.visitLdcInsn(value);
746 		}		
747 	}
748 
749 	/**
750 	 * Produce appropriate bytecode to store a stack item in an array. The 
751 	 * instruction to use varies depending on whether the type
752 	 * is a primitive or reference type.
753 	 * @param mv where to insert the bytecode
754 	 * @param arrayElementType the type of the array elements
755 	 */
756 	public static void insertArrayStore(MethodVisitor mv, String arrayElementType) {
757 		if (arrayElementType.length()==1) {
758 			switch (arrayElementType.charAt(0)) {
759 				case 'I': mv.visitInsn(IASTORE); break;
760 				case 'J': mv.visitInsn(LASTORE); break;
761 				case 'F': mv.visitInsn(FASTORE); break;
762 				case 'D': mv.visitInsn(DASTORE); break;
763 				case 'B': mv.visitInsn(BASTORE); break;
764 				case 'C': mv.visitInsn(CASTORE); break;
765 				case 'S': mv.visitInsn(SASTORE); break;
766 				case 'Z': mv.visitInsn(BASTORE); break;
767 				default:
768 					throw new IllegalArgumentException("Unexpected arraytype "+arrayElementType.charAt(0));
769 			}
770 		}
771 		else {
772 			mv.visitInsn(AASTORE);
773 		}
774 	}
775 
776 	/**
777 	 * Determine the appropriate T tag to use for the NEWARRAY bytecode.
778 	 * @param arraytype the array primitive component type
779 	 * @return the T tag to use for NEWARRAY
780 	 */
781 	public static int arrayCodeFor(String arraytype) {
782 		switch (arraytype.charAt(0)) {
783 			case 'I': return T_INT;
784 			case 'J': return T_LONG; 
785 			case 'F': return T_FLOAT;
786 			case 'D': return T_DOUBLE;
787 			case 'B': return T_BYTE;
788 			case 'C': return T_CHAR;
789 			case 'S': return T_SHORT;
790 			case 'Z': return T_BOOLEAN;
791 			default:
792 				throw new IllegalArgumentException("Unexpected arraytype "+arraytype.charAt(0));
793 		}
794 	}
795 
796 	/**
797 	 * @return true if the supplied array type has a core component reference type
798 	 */
799 	public static boolean isReferenceTypeArray(String arraytype) {
800 		int length = arraytype.length();
801 		for (int i=0;i<length;i++) {
802 			char ch = arraytype.charAt(i);
803 			if (ch == '[') continue;
804 			return ch=='L';
805 		}
806 		return false;
807 	}
808 	
809 	/**
810 	 * Produce the correct bytecode to build an array. The opcode to use and the 
811 	 * signature to pass along with the opcode can vary depending on the signature
812 	 * of the array type.
813 	 * @param mv the methodvisitor into which code should be inserted
814 	 * @param size the size of the array
815 	 * @param arraytype the type of the array
816 	 */
817 	public static void insertNewArrayCode(MethodVisitor mv, int size, String arraytype) {
818 		insertOptimalLoad(mv, size);
819 		if (arraytype.length() == 1) {
820 			mv.visitIntInsn(NEWARRAY, CodeFlow.arrayCodeFor(arraytype));
821 		}
822 		else {
823 			if (arraytype.charAt(0) == '[') {
824 				// Handling the nested array case here. If vararg
825 				// is [[I then we want [I and not [I;
826 				if (CodeFlow.isReferenceTypeArray(arraytype)) {
827 					mv.visitTypeInsn(ANEWARRAY, arraytype+";");
828 				} else {
829 					mv.visitTypeInsn(ANEWARRAY, arraytype);
830 				}
831 			}
832 			else {
833 				mv.visitTypeInsn(ANEWARRAY, arraytype.substring(1));
834 			}
835 		}
836 	}
837 
838 	
839 }