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