001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.expression.spel;
018
019import java.lang.reflect.Constructor;
020import java.lang.reflect.Method;
021import java.util.ArrayList;
022import java.util.List;
023import java.util.Stack;
024
025import org.springframework.asm.ClassWriter;
026import org.springframework.asm.MethodVisitor;
027import org.springframework.asm.Opcodes;
028import org.springframework.util.Assert;
029
030/**
031 * Manages the class being generated by the compilation process.
032 *
033 * <p>Records intermediate compilation state as the bytecode is generated.
034 * Also includes various bytecode generation helper functions.
035 *
036 * @author Andy Clement
037 * @author Juergen Hoeller
038 * @since 4.1
039 */
040public class CodeFlow implements Opcodes {
041
042        /**
043         * Name of the class being generated. Typically used when generating code
044         * that accesses freshly generated fields on the generated type.
045         */
046        private final String className;
047
048        /**
049         * The current class being generated.
050         */
051        private final ClassWriter classWriter;
052
053        /**
054         * Record the type of what is on top of the bytecode stack (i.e. the type of the
055         * output from the previous expression component). New scopes are used to evaluate
056         * sub-expressions like the expressions for the argument values in a method invocation
057         * expression.
058         */
059        private final Stack<ArrayList<String>> compilationScopes;
060
061        /**
062         * As SpEL ast nodes are called to generate code for the main evaluation method
063         * they can register to add a field to this class. Any registered FieldAdders
064         * will be called after the main evaluation function has finished being generated.
065         */
066        private List<FieldAdder> fieldAdders;
067
068        /**
069         * As SpEL ast nodes are called to generate code for the main evaluation method
070         * they can register to add code to a static initializer in the class. Any
071         * registered ClinitAdders will be called after the main evaluation function
072         * has finished being generated.
073         */
074        private List<ClinitAdder> clinitAdders;
075
076        /**
077         * When code generation requires holding a value in a class level field, this
078         * is used to track the next available field id (used as a name suffix).
079         */
080        private int nextFieldId = 1;
081
082        /**
083         * When code generation requires an intermediate variable within a method,
084         * this method records the next available variable (variable 0 is 'this').
085         */
086        private int nextFreeVariableId = 1;
087
088
089        /**
090         * Construct a new {@code CodeFlow} for the given class.
091         * @param className the name of the class
092         * @param classWriter the corresponding ASM {@code ClassWriter}
093         */
094        public CodeFlow(String className, ClassWriter classWriter) {
095                this.className = className;
096                this.classWriter = classWriter;
097                this.compilationScopes = new Stack<ArrayList<String>>();
098                this.compilationScopes.add(new ArrayList<String>());
099        }
100
101
102        /**
103         * Push the byte code to load the target (i.e. what was passed as the first argument
104         * to CompiledExpression.getValue(target, context))
105         * @param mv the visitor into which the load instruction should be inserted
106         */
107        public void loadTarget(MethodVisitor mv) {
108                mv.visitVarInsn(ALOAD, 1);
109        }
110
111        /**
112         * Push the bytecode to load the EvaluationContext (the second parameter passed to
113         * the compiled expression method).
114         * @param mv the visitor into which the load instruction should be inserted
115         * @since 4.3.4
116         */
117        public void loadEvaluationContext(MethodVisitor mv) {
118                mv.visitVarInsn(ALOAD, 2);
119        }
120
121        /**
122         * Record the descriptor for the most recently evaluated expression element.
123         * @param descriptor type descriptor for most recently evaluated element
124         */
125        public void pushDescriptor(String descriptor) {
126                Assert.notNull(descriptor, "Descriptor must not be null");
127                this.compilationScopes.peek().add(descriptor);
128        }
129
130        /**
131         * Enter a new compilation scope, usually due to nested expression evaluation. For
132         * example when the arguments for a method invocation expression are being evaluated,
133         * each argument will be evaluated in a new scope.
134         */
135        public void enterCompilationScope() {
136                this.compilationScopes.push(new ArrayList<String>());
137        }
138
139        /**
140         * Exit a compilation scope, usually after a nested expression has been evaluated. For
141         * example after an argument for a method invocation has been evaluated this method
142         * returns us to the previous (outer) scope.
143         */
144        public void exitCompilationScope() {
145                this.compilationScopes.pop();
146        }
147
148        /**
149         * Return the descriptor for the item currently on top of the stack (in the current scope).
150         */
151        public String lastDescriptor() {
152                ArrayList<String> scopes = this.compilationScopes.peek();
153                return (!scopes.isEmpty() ? scopes.get(scopes.size() - 1) : null);
154        }
155
156        /**
157         * If the codeflow shows the last expression evaluated to java.lang.Boolean then
158         * insert the necessary instructions to unbox that to a boolean primitive.
159         * @param mv the visitor into which new instructions should be inserted
160         */
161        public void unboxBooleanIfNecessary(MethodVisitor mv) {
162                if ("Ljava/lang/Boolean".equals(lastDescriptor())) {
163                        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
164                }
165        }
166
167        /**
168         * Called after the main expression evaluation method has been generated, this
169         * method will callback any registered FieldAdders or ClinitAdders to add any
170         * extra information to the class representing the compiled expression.
171         */
172        public void finish() {
173                if (this.fieldAdders != null) {
174                        for (FieldAdder fieldAdder : this.fieldAdders) {
175                                fieldAdder.generateField(this.classWriter, this);
176                        }
177                }
178                if (this.clinitAdders != null) {
179                        MethodVisitor mv = this.classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "<clinit>", "()V", null, null);
180                        mv.visitCode();
181                        this.nextFreeVariableId = 0;  // to 0 because there is no 'this' in a clinit
182                        for (ClinitAdder clinitAdder : this.clinitAdders) {
183                                clinitAdder.generateCode(mv, this);
184                        }
185                        mv.visitInsn(RETURN);
186                        mv.visitMaxs(0,0);  // not supplied due to COMPUTE_MAXS
187                        mv.visitEnd();
188                }
189        }
190
191        /**
192         * Register a FieldAdder which will add a new field to the generated
193         * class to support the code produced by an ast nodes primary
194         * generateCode() method.
195         */
196        public void registerNewField(FieldAdder fieldAdder) {
197                if (this.fieldAdders == null) {
198                        this.fieldAdders = new ArrayList<FieldAdder>();
199                }
200                this.fieldAdders.add(fieldAdder);
201        }
202
203        /**
204         * Register a ClinitAdder which will add code to the static
205         * initializer in the generated class to support the code
206         * produced by an ast nodes primary generateCode() method.
207         */
208        public void registerNewClinit(ClinitAdder clinitAdder) {
209                if (this.clinitAdders == null) {
210                        this.clinitAdders = new ArrayList<ClinitAdder>();
211                }
212                this.clinitAdders.add(clinitAdder);
213        }
214
215        public int nextFieldId() {
216                return this.nextFieldId++;
217        }
218
219        public int nextFreeVariableId() {
220                return this.nextFreeVariableId++;
221        }
222
223        public String getClassName() {
224                return this.className;
225        }
226
227        @Deprecated
228        public String getClassname() {
229                return this.className;
230        }
231
232
233        /**
234         * Insert any necessary cast and value call to convert from a boxed type to a
235         * primitive value.
236         * @param mv the method visitor into which instructions should be inserted
237         * @param ch the primitive type desired as output
238         * @param stackDescriptor the descriptor of the type on top of the stack
239         */
240        public static void insertUnboxInsns(MethodVisitor mv, char ch, String stackDescriptor) {
241                switch (ch) {
242                        case 'Z':
243                                if (!stackDescriptor.equals("Ljava/lang/Boolean")) {
244                                        mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
245                                }
246                                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
247                                break;
248                        case 'B':
249                                if (!stackDescriptor.equals("Ljava/lang/Byte")) {
250                                        mv.visitTypeInsn(CHECKCAST, "java/lang/Byte");
251                                }
252                                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false);
253                                break;
254                        case 'C':
255                                if (!stackDescriptor.equals("Ljava/lang/Character")) {
256                                        mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
257                                }
258                                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false);
259                                break;
260                        case 'D':
261                                if (!stackDescriptor.equals("Ljava/lang/Double")) {
262                                        mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
263                                }
264                                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false);
265                                break;
266                        case 'F':
267                                if (!stackDescriptor.equals("Ljava/lang/Float")) {
268                                        mv.visitTypeInsn(CHECKCAST, "java/lang/Float");
269                                }
270                                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false);
271                                break;
272                        case 'I':
273                                if (!stackDescriptor.equals("Ljava/lang/Integer")) {
274                                        mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
275                                }
276                                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
277                                break;
278                        case 'J':
279                                if (!stackDescriptor.equals("Ljava/lang/Long")) {
280                                        mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
281                                }
282                                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
283                                break;
284                        case 'S':
285                                if (!stackDescriptor.equals("Ljava/lang/Short")) {
286                                        mv.visitTypeInsn(CHECKCAST, "java/lang/Short");
287                                }
288                                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false);
289                                break;
290                        default:
291                                throw new IllegalArgumentException("Unboxing should not be attempted for descriptor '" + ch + "'");
292                }
293        }
294
295        /**
296         * For numbers, use the appropriate method on the number to convert it to the primitive type requested.
297         * @param mv the method visitor into which instructions should be inserted
298         * @param targetDescriptor the primitive type desired as output
299         * @param stackDescriptor the descriptor of the type on top of the stack
300         */
301        public static void insertUnboxNumberInsns(MethodVisitor mv, char targetDescriptor, String stackDescriptor) {
302                switch (targetDescriptor) {
303                        case 'D':
304                                if (stackDescriptor.equals("Ljava/lang/Object")) {
305                                        mv.visitTypeInsn(CHECKCAST, "java/lang/Number");
306                                }
307                                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "doubleValue", "()D", false);
308                                break;
309                        case 'F':
310                                if (stackDescriptor.equals("Ljava/lang/Object")) {
311                                        mv.visitTypeInsn(CHECKCAST, "java/lang/Number");
312                                }
313                                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "floatValue", "()F", false);
314                                break;
315                        case 'J':
316                                if (stackDescriptor.equals("Ljava/lang/Object")) {
317                                        mv.visitTypeInsn(CHECKCAST, "java/lang/Number");
318                                }
319                                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "longValue", "()J", false);
320                                break;
321                        case 'I':
322                                if (stackDescriptor.equals("Ljava/lang/Object")) {
323                                        mv.visitTypeInsn(CHECKCAST, "java/lang/Number");
324                                }
325                                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "intValue", "()I", false);
326                                break;
327                        // does not handle Z, B, C, S
328                        default:
329                                throw new IllegalArgumentException("Unboxing should not be attempted for descriptor '" + targetDescriptor + "'");
330                }
331        }
332
333        /**
334         * Insert any necessary numeric conversion bytecodes based upon what is on the stack and the desired target type.
335         * @param mv the method visitor into which instructions should be placed
336         * @param targetDescriptor the (primitive) descriptor of the target type
337         * @param stackDescriptor the descriptor of the operand on top of the stack
338         */
339        public static void insertAnyNecessaryTypeConversionBytecodes(MethodVisitor mv, char targetDescriptor, String stackDescriptor) {
340                if (CodeFlow.isPrimitive(stackDescriptor)) {
341                        char stackTop = stackDescriptor.charAt(0);
342                        if (stackTop == 'I' || stackTop == 'B' || stackTop == 'S' || stackTop == 'C') {
343                                if (targetDescriptor == 'D') {
344                                        mv.visitInsn(I2D);
345                                }
346                                else if (targetDescriptor == 'F') {
347                                        mv.visitInsn(I2F);
348                                }
349                                else if (targetDescriptor == 'J') {
350                                        mv.visitInsn(I2L);
351                                }
352                                else if (targetDescriptor == 'I') {
353                                        // nop
354                                }
355                                else {
356                                        throw new IllegalStateException("Cannot get from " + stackTop + " to " + targetDescriptor);
357                                }
358                        }
359                        else if (stackTop == 'J') {
360                                if (targetDescriptor == 'D') {
361                                        mv.visitInsn(L2D);
362                                }
363                                else if (targetDescriptor == 'F') {
364                                        mv.visitInsn(L2F);
365                                }
366                                else if (targetDescriptor == 'J') {
367                                        // nop
368                                }
369                                else if (targetDescriptor == 'I') {
370                                        mv.visitInsn(L2I);
371                                }
372                                else {
373                                        throw new IllegalStateException("Cannot get from " + stackTop + " to " + targetDescriptor);
374                                }
375                        }
376                        else if (stackTop == 'F') {
377                                if (targetDescriptor == 'D') {
378                                        mv.visitInsn(F2D);
379                                }
380                                else if (targetDescriptor == 'F') {
381                                        // nop
382                                }
383                                else if (targetDescriptor == 'J') {
384                                        mv.visitInsn(F2L);
385                                }
386                                else if (targetDescriptor == 'I') {
387                                        mv.visitInsn(F2I);
388                                }
389                                else {
390                                        throw new IllegalStateException("Cannot get from " + stackTop + " to " + targetDescriptor);
391                                }
392                        }
393                        else if (stackTop == 'D') {
394                                if (targetDescriptor == 'D') {
395                                        // nop
396                                }
397                                else if (targetDescriptor == 'F') {
398                                        mv.visitInsn(D2F);
399                                }
400                                else if (targetDescriptor == 'J') {
401                                        mv.visitInsn(D2L);
402                                }
403                                else if (targetDescriptor == 'I') {
404                                        mv.visitInsn(D2I);
405                                }
406                                else {
407                                        throw new IllegalStateException("Cannot get from " + stackDescriptor + " to " + targetDescriptor);
408                                }
409                        }
410                }
411        }
412
413
414        /**
415         * Create the JVM signature descriptor for a method. This consists of the descriptors
416         * for the method parameters surrounded with parentheses, followed by the
417         * descriptor for the return type. Note the descriptors here are JVM descriptors,
418         * unlike the other descriptor forms the compiler is using which do not include the
419         * trailing semicolon.
420         * @param method the method
421         * @return a String signature descriptor (e.g. "(ILjava/lang/String;)V")
422         */
423        public static String createSignatureDescriptor(Method method) {
424                Class<?>[] params = method.getParameterTypes();
425                StringBuilder sb = new StringBuilder();
426                sb.append("(");
427                for (Class<?> param : params) {
428                        sb.append(toJvmDescriptor(param));
429                }
430                sb.append(")");
431                sb.append(toJvmDescriptor(method.getReturnType()));
432                return sb.toString();
433        }
434
435        /**
436         * Create the JVM signature descriptor for a constructor. This consists of the
437         * descriptors for the constructor parameters surrounded with parentheses, followed by
438         * the descriptor for the return type, which is always "V". Note the
439         * descriptors here are JVM descriptors, unlike the other descriptor forms the
440         * compiler is using which do not include the trailing semicolon.
441         * @param ctor the constructor
442         * @return a String signature descriptor (e.g. "(ILjava/lang/String;)V")
443         */
444        public static String createSignatureDescriptor(Constructor<?> ctor) {
445                Class<?>[] params = ctor.getParameterTypes();
446                StringBuilder sb = new StringBuilder();
447                sb.append("(");
448                for (Class<?> param : params) {
449                        sb.append(toJvmDescriptor(param));
450                }
451                sb.append(")V");
452                return sb.toString();
453        }
454
455        /**
456         * Determine the JVM descriptor for a specified class. Unlike the other descriptors
457         * used in the compilation process, this is the one the JVM wants, so this one
458         * includes any necessary trailing semicolon (e.g. Ljava/lang/String; rather than
459         * Ljava/lang/String)
460         * @param clazz a class
461         * @return the JVM descriptor for the class
462         */
463        public static String toJvmDescriptor(Class<?> clazz) {
464                StringBuilder sb = new StringBuilder();
465                if (clazz.isArray()) {
466                        while (clazz.isArray()) {
467                                sb.append("[");
468                                clazz = clazz.getComponentType();
469                        }
470                }
471                if (clazz.isPrimitive()) {
472                        if (clazz == Boolean.TYPE) {
473                                sb.append('Z');
474                        }
475                        else if (clazz == Byte.TYPE) {
476                                sb.append('B');
477                        }
478                        else if (clazz == Character.TYPE) {
479                                sb.append('C');
480                        }
481                        else if (clazz == Double.TYPE) {
482                                sb.append('D');
483                        }
484                        else if (clazz == Float.TYPE) {
485                                sb.append('F');
486                        }
487                        else if (clazz == Integer.TYPE) {
488                                sb.append('I');
489                        }
490                        else if (clazz == Long.TYPE) {
491                                sb.append('J');
492                        }
493                        else if (clazz == Short.TYPE) {
494                                sb.append('S');
495                        }
496                        else if (clazz == Void.TYPE) {
497                                sb.append('V');
498                        }
499                }
500                else {
501                        sb.append("L");
502                        sb.append(clazz.getName().replace('.', '/'));
503                        sb.append(";");
504                }
505                return sb.toString();
506        }
507
508        /**
509         * Determine the descriptor for an object instance (or {@code null}).
510         * @param value an object (possibly {@code null})
511         * @return the type descriptor for the object
512         * (descriptor is "Ljava/lang/Object" for {@code null} value)
513         */
514        public static String toDescriptorFromObject(Object value) {
515                if (value == null) {
516                        return "Ljava/lang/Object";
517                }
518                else {
519                        return toDescriptor(value.getClass());
520                }
521        }
522
523        /**
524         * Determine whether the descriptor is for a boolean primitive or boolean reference type.
525         * @param descriptor type descriptor
526         * @return {@code true} if the descriptor is boolean compatible
527         */
528        public static boolean isBooleanCompatible(String descriptor) {
529                return (descriptor != null && (descriptor.equals("Z") || descriptor.equals("Ljava/lang/Boolean")));
530        }
531
532        /**
533         * Determine whether the descriptor is for a primitive type.
534         * @param descriptor type descriptor
535         * @return {@code true} if a primitive type
536         */
537        public static boolean isPrimitive(String descriptor) {
538                return (descriptor != null && descriptor.length() == 1);
539        }
540
541        /**
542         * Determine whether the descriptor is for a primitive array (e.g. "[[I").
543         * @param descriptor the descriptor for a possible primitive array
544         * @return {@code true} if the descriptor a primitive array
545         */
546        public static boolean isPrimitiveArray(String descriptor) {
547                boolean primitive = true;
548                for (int i = 0, max = descriptor.length(); i < max; i++) {
549                        char ch = descriptor.charAt(i);
550                        if (ch == '[') {
551                                continue;
552                        }
553                        primitive = (ch != 'L');
554                        break;
555                }
556                return primitive;
557        }
558
559        /**
560         * Determine whether boxing/unboxing can get from one type to the other.
561         * Assumes at least one of the types is in boxed form (i.e. single char descriptor).
562         * @return {@code true} if it is possible to get (via boxing) from one descriptor to the other
563         */
564        public static boolean areBoxingCompatible(String desc1, String desc2) {
565                if (desc1.equals(desc2)) {
566                        return true;
567                }
568                if (desc1.length() == 1) {
569                        if (desc1.equals("Z")) {
570                                return desc2.equals("Ljava/lang/Boolean");
571                        }
572                        else if (desc1.equals("D")) {
573                                return desc2.equals("Ljava/lang/Double");
574                        }
575                        else if (desc1.equals("F")) {
576                                return desc2.equals("Ljava/lang/Float");
577                        }
578                        else if (desc1.equals("I")) {
579                                return desc2.equals("Ljava/lang/Integer");
580                        }
581                        else if (desc1.equals("J")) {
582                                return desc2.equals("Ljava/lang/Long");
583                        }
584                }
585                else if (desc2.length() == 1) {
586                        if (desc2.equals("Z")) {
587                                return desc1.equals("Ljava/lang/Boolean");
588                        }
589                        else if (desc2.equals("D")) {
590                                return desc1.equals("Ljava/lang/Double");
591                        }
592                        else if (desc2.equals("F")) {
593                                return desc1.equals("Ljava/lang/Float");
594                        }
595                        else if (desc2.equals("I")) {
596                                return desc1.equals("Ljava/lang/Integer");
597                        }
598                        else if (desc2.equals("J")) {
599                                return desc1.equals("Ljava/lang/Long");
600                        }
601                }
602                return false;
603        }
604
605        /**
606         * Determine if the supplied descriptor is for a supported number type or boolean. The
607         * compilation process only (currently) supports certain number types. These are
608         * double, float, long and int.
609         * @param descriptor the descriptor for a type
610         * @return {@code true} if the descriptor is for a supported numeric type or boolean
611         */
612        public static boolean isPrimitiveOrUnboxableSupportedNumberOrBoolean(String descriptor) {
613                if (descriptor == null) {
614                        return false;
615                }
616                if (isPrimitiveOrUnboxableSupportedNumber(descriptor)) {
617                        return true;
618                }
619                return ("Z".equals(descriptor) || descriptor.equals("Ljava/lang/Boolean"));
620        }
621
622        /**
623         * Determine if the supplied descriptor is for a supported number. The compilation
624         * process only (currently) supports certain number types. These are double, float,
625         * long and int.
626         * @param descriptor the descriptor for a type
627         * @return {@code true} if the descriptor is for a supported numeric type
628         */
629        public static boolean isPrimitiveOrUnboxableSupportedNumber(String descriptor) {
630                if (descriptor == null) {
631                        return false;
632                }
633                if (descriptor.length() == 1) {
634                        return "DFIJ".contains(descriptor);
635                }
636                if (descriptor.startsWith("Ljava/lang/")) {
637                        String name = descriptor.substring("Ljava/lang/".length());
638                        if (name.equals("Double") || name.equals("Float") || name.equals("Integer") || name.equals("Long")) {
639                                return true;
640                        }
641                }
642                return false;
643        }
644
645        /**
646         * Determine whether the given number is to be considered as an integer
647         * for the purposes of a numeric operation at the bytecode level.
648         * @param number the number to check
649         * @return {@code true} if it is an {@link Integer}, {@link Short} or {@link Byte}
650         */
651        public static boolean isIntegerForNumericOp(Number number) {
652                return (number instanceof Integer || number instanceof Short || number instanceof Byte);
653        }
654
655        /**
656         * Convert a type descriptor to the single character primitive descriptor.
657         * @param descriptor a descriptor for a type that should have a primitive representation
658         * @return the single character descriptor for a primitive input descriptor
659         */
660        public static char toPrimitiveTargetDesc(String descriptor) {
661                if (descriptor.length() == 1) {
662                        return descriptor.charAt(0);
663                }
664                else if (descriptor.equals("Ljava/lang/Boolean")) {
665                        return 'Z';
666                }
667                else if (descriptor.equals("Ljava/lang/Byte")) {
668                        return 'B';
669                }
670                else if (descriptor.equals("Ljava/lang/Character")) {
671                        return 'C';
672                }
673                else if (descriptor.equals("Ljava/lang/Double")) {
674                        return 'D';
675                }
676                else if (descriptor.equals("Ljava/lang/Float")) {
677                        return 'F';
678                }
679                else if (descriptor.equals("Ljava/lang/Integer")) {
680                        return 'I';
681                }
682                else if (descriptor.equals("Ljava/lang/Long")) {
683                        return 'J';
684                }
685                else if (descriptor.equals("Ljava/lang/Short")) {
686                        return 'S';
687                }
688                else {
689                        throw new IllegalStateException("No primitive for '" + descriptor + "'");
690                }
691        }
692
693        /**
694         * Insert the appropriate CHECKCAST instruction for the supplied descriptor.
695         * @param mv the target visitor into which the instruction should be inserted
696         * @param descriptor the descriptor of the type to cast to
697         */
698        public static void insertCheckCast(MethodVisitor mv, String descriptor) {
699                if (descriptor.length() != 1) {
700                        if (descriptor.charAt(0) == '[') {
701                                if (isPrimitiveArray(descriptor)) {
702                                        mv.visitTypeInsn(CHECKCAST, descriptor);
703                                }
704                                else {
705                                        mv.visitTypeInsn(CHECKCAST, descriptor + ";");
706                                }
707                        }
708                        else {
709                                if (!descriptor.equals("Ljava/lang/Object")) {
710                                        // This is chopping off the 'L' to leave us with "java/lang/String"
711                                        mv.visitTypeInsn(CHECKCAST, descriptor.substring(1));
712                                }
713                        }
714                }
715        }
716
717        /**
718         * Determine the appropriate boxing instruction for a specific type (if it needs
719         * boxing) and insert the instruction into the supplied visitor.
720         * @param mv the target visitor for the new instructions
721         * @param descriptor the descriptor of a type that may or may not need boxing
722         */
723        public static void insertBoxIfNecessary(MethodVisitor mv, String descriptor) {
724                if (descriptor.length() == 1) {
725                        insertBoxIfNecessary(mv, descriptor.charAt(0));
726                }
727        }
728
729        /**
730         * Determine the appropriate boxing instruction for a specific type (if it needs
731         * boxing) and insert the instruction into the supplied visitor.
732         * @param mv the target visitor for the new instructions
733         * @param ch the descriptor of the type that might need boxing
734         */
735        public static void insertBoxIfNecessary(MethodVisitor mv, char ch) {
736                switch (ch) {
737                        case 'Z':
738                                mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
739                                break;
740                        case 'B':
741                                mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false);
742                                break;
743                        case 'C':
744                                mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", false);
745                                break;
746                        case 'D':
747                                mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
748                                break;
749                        case 'F':
750                                mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false);
751                                break;
752                        case 'I':
753                                mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
754                                break;
755                        case 'J':
756                                mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
757                                break;
758                        case 'S':
759                                mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false);
760                                break;
761                        case 'L':
762                        case 'V':
763                        case '[':
764                                // no box needed
765                                break;
766                        default:
767                                throw new IllegalArgumentException("Boxing should not be attempted for descriptor '" + ch + "'");
768                }
769        }
770
771        /**
772         * Deduce the descriptor for a type. Descriptors are like JVM type names but missing the
773         * trailing ';' so for Object the descriptor is "Ljava/lang/Object" for int it is "I".
774         * @param type the type (may be primitive) for which to determine the descriptor
775         * @return the descriptor
776         */
777        public static String toDescriptor(Class<?> type) {
778                String name = type.getName();
779                if (type.isPrimitive()) {
780                        switch (name.length()) {
781                                case 3:
782                                        return "I";
783                                case 4:
784                                        if (name.equals("byte")) {
785                                                return "B";
786                                        }
787                                        else if (name.equals("char")) {
788                                                return "C";
789                                        }
790                                        else if (name.equals("long")) {
791                                                return "J";
792                                        }
793                                        else if (name.equals("void")) {
794                                                return "V";
795                                        }
796                                        break;
797                                case 5:
798                                        if (name.equals("float")) {
799                                                return "F";
800                                        }
801                                        else if (name.equals("short")) {
802                                                return "S";
803                                        }
804                                        break;
805                                case 6:
806                                        if (name.equals("double")) {
807                                                return "D";
808                                        }
809                                        break;
810                                case 7:
811                                        if (name.equals("boolean")) {
812                                                return "Z";
813                                        }
814                                        break;
815                        }
816                }
817                else {
818                        if (name.charAt(0) != '[') {
819                                return "L" + type.getName().replace('.', '/');
820                        }
821                        else {
822                                if (name.endsWith(";")) {
823                                        return name.substring(0, name.length() - 1).replace('.', '/');
824                                }
825                                else {
826                                        return name;  // array has primitive component type
827                                }
828                        }
829                }
830                return null;
831        }
832
833        /**
834         * Create an array of descriptors representing the parameter types for the supplied
835         * method. Returns a zero sized array if there are no parameters.
836         * @param method a Method
837         * @return a String array of descriptors, one entry for each method parameter
838         */
839        public static String[] toParamDescriptors(Method method) {
840                return toDescriptors(method.getParameterTypes());
841        }
842
843        /**
844         * Create an array of descriptors representing the parameter types for the supplied
845         * constructor. Returns a zero sized array if there are no parameters.
846         * @param ctor a Constructor
847         * @return a String array of descriptors, one entry for each constructor parameter
848         */
849        public static String[] toParamDescriptors(Constructor<?> ctor) {
850                return toDescriptors(ctor.getParameterTypes());
851        }
852
853        /**
854         * Create an array of descriptors from an array of classes.
855         * @param types the input array of classes
856         * @return an array of descriptors
857         */
858        public static String[] toDescriptors(Class<?>[] types) {
859                int typesCount = types.length;
860                String[] descriptors = new String[typesCount];
861                for (int p = 0; p < typesCount; p++) {
862                        descriptors[p] = toDescriptor(types[p]);
863                }
864                return descriptors;
865        }
866
867        /**
868         * Create the optimal instruction for loading a number on the stack.
869         * @param mv where to insert the bytecode
870         * @param value the value to be loaded
871         */
872        public static void insertOptimalLoad(MethodVisitor mv, int value) {
873                if (value < 6) {
874                        mv.visitInsn(ICONST_0+value);
875                }
876                else if (value < Byte.MAX_VALUE) {
877                        mv.visitIntInsn(BIPUSH, value);
878                }
879                else if (value < Short.MAX_VALUE) {
880                        mv.visitIntInsn(SIPUSH, value);
881                }
882                else {
883                        mv.visitLdcInsn(value);
884                }
885        }
886
887        /**
888         * Produce appropriate bytecode to store a stack item in an array. The
889         * instruction to use varies depending on whether the type
890         * is a primitive or reference type.
891         * @param mv where to insert the bytecode
892         * @param arrayElementType the type of the array elements
893         */
894        public static void insertArrayStore(MethodVisitor mv, String arrayElementType) {
895                if (arrayElementType.length()==1) {
896                        switch (arrayElementType.charAt(0)) {
897                                case 'I':
898                                        mv.visitInsn(IASTORE);
899                                        break;
900                                case 'J':
901                                        mv.visitInsn(LASTORE);
902                                        break;
903                                case 'F':
904                                        mv.visitInsn(FASTORE);
905                                        break;
906                                case 'D':
907                                        mv.visitInsn(DASTORE);
908                                        break;
909                                case 'B':
910                                        mv.visitInsn(BASTORE);
911                                        break;
912                                case 'C':
913                                        mv.visitInsn(CASTORE);
914                                        break;
915                                case 'S':
916                                        mv.visitInsn(SASTORE);
917                                        break;
918                                case 'Z':
919                                        mv.visitInsn(BASTORE);
920                                        break;
921                                default:
922                                        throw new IllegalArgumentException(
923                                                        "Unexpected arraytype " + arrayElementType.charAt(0));
924                        }
925                }
926                else {
927                        mv.visitInsn(AASTORE);
928                }
929        }
930
931        /**
932         * Determine the appropriate T tag to use for the NEWARRAY bytecode.
933         * @param arraytype the array primitive component type
934         * @return the T tag to use for NEWARRAY
935         */
936        public static int arrayCodeFor(String arraytype) {
937                switch (arraytype.charAt(0)) {
938                        case 'I': return T_INT;
939                        case 'J': return T_LONG;
940                        case 'F': return T_FLOAT;
941                        case 'D': return T_DOUBLE;
942                        case 'B': return T_BYTE;
943                        case 'C': return T_CHAR;
944                        case 'S': return T_SHORT;
945                        case 'Z': return T_BOOLEAN;
946                        default:
947                                throw new IllegalArgumentException("Unexpected arraytype " + arraytype.charAt(0));
948                }
949        }
950
951        /**
952         * Return if the supplied array type has a core component reference type.
953         */
954        public static boolean isReferenceTypeArray(String arraytype) {
955                int length = arraytype.length();
956                for (int i = 0; i < length; i++) {
957                        char ch = arraytype.charAt(i);
958                        if (ch == '[') {
959                                continue;
960                        }
961                        return (ch == 'L');
962                }
963                return false;
964        }
965
966        /**
967         * Produce the correct bytecode to build an array. The opcode to use and the
968         * signature to pass along with the opcode can vary depending on the signature
969         * of the array type.
970         * @param mv the methodvisitor into which code should be inserted
971         * @param size the size of the array
972         * @param arraytype the type of the array
973         */
974        public static void insertNewArrayCode(MethodVisitor mv, int size, String arraytype) {
975                insertOptimalLoad(mv, size);
976                if (arraytype.length() == 1) {
977                        mv.visitIntInsn(NEWARRAY, CodeFlow.arrayCodeFor(arraytype));
978                }
979                else {
980                        if (arraytype.charAt(0) == '[') {
981                                // Handling the nested array case here.
982                                // If vararg is [[I then we want [I and not [I;
983                                if (CodeFlow.isReferenceTypeArray(arraytype)) {
984                                        mv.visitTypeInsn(ANEWARRAY, arraytype + ";");
985                                }
986                                else {
987                                        mv.visitTypeInsn(ANEWARRAY, arraytype);
988                                }
989                        }
990                        else {
991                                mv.visitTypeInsn(ANEWARRAY, arraytype.substring(1));
992                        }
993                }
994        }
995
996        /**
997         * For use in mathematical operators, handles converting from a (possibly boxed)
998         * number on the stack to a primitive numeric type.
999         * <p>For example, from a Integer to a double, just need to call 'Number.doubleValue()'
1000         * but from an int to a double, need to use the bytecode 'i2d'.
1001         * @param mv the method visitor when instructions should be appended
1002         * @param stackDescriptor a descriptor of the operand on the stack
1003         * @param targetDescriptor a primitive type descriptor
1004         */
1005        public static void insertNumericUnboxOrPrimitiveTypeCoercion(
1006                        MethodVisitor mv, String stackDescriptor, char targetDescriptor) {
1007
1008                if (!CodeFlow.isPrimitive(stackDescriptor)) {
1009                        CodeFlow.insertUnboxNumberInsns(mv, targetDescriptor, stackDescriptor);
1010                }
1011                else {
1012                        CodeFlow.insertAnyNecessaryTypeConversionBytecodes(mv, targetDescriptor, stackDescriptor);
1013                }
1014        }
1015
1016        public static String toBoxedDescriptor(String primitiveDescriptor) {
1017                switch (primitiveDescriptor.charAt(0)) {
1018                        case 'I': return "Ljava/lang/Integer";
1019                        case 'J': return "Ljava/lang/Long";
1020                        case 'F': return "Ljava/lang/Float";
1021                        case 'D': return "Ljava/lang/Double";
1022                        case 'B': return "Ljava/lang/Byte";
1023                        case 'C': return "Ljava/lang/Character";
1024                        case 'S': return "Ljava/lang/Short";
1025                        case 'Z': return "Ljava/lang/Boolean";
1026                        default:
1027                                throw new IllegalArgumentException("Unexpected non primitive descriptor " + primitiveDescriptor);
1028                }
1029        }
1030
1031
1032        /**
1033         * Interface used to generate fields.
1034         */
1035        @FunctionalInterface
1036        public interface FieldAdder {
1037
1038                void generateField(ClassWriter cw, CodeFlow codeflow);
1039        }
1040
1041
1042        /**
1043         * Interface used to generate {@code clinit} static initializer blocks.
1044         */
1045        @FunctionalInterface
1046        public interface ClinitAdder {
1047
1048                void generateCode(MethodVisitor mv, CodeFlow codeflow);
1049        }
1050
1051}