org.springframework.expression.spel

Class CodeFlow

  • java.lang.Object
    • org.springframework.expression.spel.CodeFlow
  • All Implemented Interfaces:
    Opcodes


    public class CodeFlow
    extends java.lang.Object
    implements Opcodes
    Manages the class being generated by the compilation process.

    Records intermediate compilation state as the bytecode is generated. Also includes various bytecode generation helper functions.

    Since:
    4.1
    Author:
    Andy Clement, Juergen Hoeller
    • Constructor Detail

      • CodeFlow

        public CodeFlow(java.lang.String className,
                        ClassWriter classWriter)
        Construct a new CodeFlow for the given class.
        Parameters:
        className - the name of the class
        classWriter - the corresponding ASM ClassWriter
    • Method Detail

      • loadTarget

        public void loadTarget(MethodVisitor mv)
        Push the byte code to load the target (i.e. what was passed as the first argument to CompiledExpression.getValue(target, context))
        Parameters:
        mv - the visitor into which the load instruction should be inserted
      • loadEvaluationContext

        public void loadEvaluationContext(MethodVisitor mv)
        Push the bytecode to load the EvaluationContext (the second parameter passed to the compiled expression method).
        Parameters:
        mv - the visitor into which the load instruction should be inserted
        Since:
        4.3.4
      • pushDescriptor

        public void pushDescriptor(@Nullable
                                   java.lang.String descriptor)
        Record the descriptor for the most recently evaluated expression element.
        Parameters:
        descriptor - type descriptor for most recently evaluated element
      • enterCompilationScope

        public void enterCompilationScope()
        Enter a new compilation scope, usually due to nested expression evaluation. For example when the arguments for a method invocation expression are being evaluated, each argument will be evaluated in a new scope.
      • exitCompilationScope

        public void exitCompilationScope()
        Exit a compilation scope, usually after a nested expression has been evaluated. For example after an argument for a method invocation has been evaluated this method returns us to the previous (outer) scope.
      • lastDescriptor

        @Nullable
        public java.lang.String lastDescriptor()
        Return the descriptor for the item currently on top of the stack (in the current scope).
      • unboxBooleanIfNecessary

        public void unboxBooleanIfNecessary(MethodVisitor mv)
        If the codeflow shows the last expression evaluated to java.lang.Boolean then insert the necessary instructions to unbox that to a boolean primitive.
        Parameters:
        mv - the visitor into which new instructions should be inserted
      • finish

        public void finish()
        Called after the main expression evaluation method has been generated, this method will callback any registered FieldAdders or ClinitAdders to add any extra information to the class representing the compiled expression.
      • registerNewField

        public void registerNewField(CodeFlow.FieldAdder fieldAdder)
        Register a FieldAdder which will add a new field to the generated class to support the code produced by an ast nodes primary generateCode() method.
      • registerNewClinit

        public void registerNewClinit(CodeFlow.ClinitAdder clinitAdder)
        Register a ClinitAdder which will add code to the static initializer in the generated class to support the code produced by an ast nodes primary generateCode() method.
      • nextFieldId

        public int nextFieldId()
      • nextFreeVariableId

        public int nextFreeVariableId()
      • getClassName

        public java.lang.String getClassName()
      • insertUnboxInsns

        public static void insertUnboxInsns(MethodVisitor mv,
                                            char ch,
                                            @Nullable
                                            java.lang.String stackDescriptor)
        Insert any necessary cast and value call to convert from a boxed type to a primitive value.
        Parameters:
        mv - the method visitor into which instructions should be inserted
        ch - the primitive type desired as output
        stackDescriptor - the descriptor of the type on top of the stack
      • insertUnboxNumberInsns

        public static void insertUnboxNumberInsns(MethodVisitor mv,
                                                  char targetDescriptor,
                                                  @Nullable
                                                  java.lang.String stackDescriptor)
        For numbers, use the appropriate method on the number to convert it to the primitive type requested.
        Parameters:
        mv - the method visitor into which instructions should be inserted
        targetDescriptor - the primitive type desired as output
        stackDescriptor - the descriptor of the type on top of the stack
      • insertAnyNecessaryTypeConversionBytecodes

        public static void insertAnyNecessaryTypeConversionBytecodes(MethodVisitor mv,
                                                                     char targetDescriptor,
                                                                     java.lang.String stackDescriptor)
        Insert any necessary numeric conversion bytecodes based upon what is on the stack and the desired target type.
        Parameters:
        mv - the method visitor into which instructions should be placed
        targetDescriptor - the (primitive) descriptor of the target type
        stackDescriptor - the descriptor of the operand on top of the stack
      • createSignatureDescriptor

        public static java.lang.String createSignatureDescriptor(java.lang.reflect.Method method)
        Create the JVM signature descriptor for a method. This consists of the descriptors for the method parameters surrounded with parentheses, followed by the descriptor for the return type. Note the descriptors here are JVM descriptors, unlike the other descriptor forms the compiler is using which do not include the trailing semicolon.
        Parameters:
        method - the method
        Returns:
        a String signature descriptor (e.g. "(ILjava/lang/String;)V")
      • createSignatureDescriptor

        public static java.lang.String createSignatureDescriptor(java.lang.reflect.Constructor<?> ctor)
        Create the JVM signature descriptor for a constructor. This consists of the descriptors for the constructor parameters surrounded with parentheses, followed by the descriptor for the return type, which is always "V". Note the descriptors here are JVM descriptors, unlike the other descriptor forms the compiler is using which do not include the trailing semicolon.
        Parameters:
        ctor - the constructor
        Returns:
        a String signature descriptor (e.g. "(ILjava/lang/String;)V")
      • toJvmDescriptor

        public static java.lang.String toJvmDescriptor(java.lang.Class<?> clazz)
        Determine the JVM descriptor for a specified class. Unlike the other descriptors used in the compilation process, this is the one the JVM wants, so this one includes any necessary trailing semicolon (e.g. Ljava/lang/String; rather than Ljava/lang/String)
        Parameters:
        clazz - a class
        Returns:
        the JVM descriptor for the class
      • toDescriptorFromObject

        public static java.lang.String toDescriptorFromObject(@Nullable
                                                              java.lang.Object value)
        Determine the descriptor for an object instance (or null).
        Parameters:
        value - an object (possibly null)
        Returns:
        the type descriptor for the object (descriptor is "Ljava/lang/Object" for null value)
      • isBooleanCompatible

        public static boolean isBooleanCompatible(@Nullable
                                                  java.lang.String descriptor)
        Determine whether the descriptor is for a boolean primitive or boolean reference type.
        Parameters:
        descriptor - type descriptor
        Returns:
        true if the descriptor is boolean compatible
      • isPrimitive

        public static boolean isPrimitive(@Nullable
                                          java.lang.String descriptor)
        Determine whether the descriptor is for a primitive type.
        Parameters:
        descriptor - type descriptor
        Returns:
        true if a primitive type
      • isPrimitiveArray

        public static boolean isPrimitiveArray(@Nullable
                                               java.lang.String descriptor)
        Determine whether the descriptor is for a primitive array (e.g. "[[I").
        Parameters:
        descriptor - the descriptor for a possible primitive array
        Returns:
        true if the descriptor a primitive array
      • areBoxingCompatible

        public static boolean areBoxingCompatible(java.lang.String desc1,
                                                  java.lang.String desc2)
        Determine whether boxing/unboxing can get from one type to the other. Assumes at least one of the types is in boxed form (i.e. single char descriptor).
        Returns:
        true if it is possible to get (via boxing) from one descriptor to the other
      • isPrimitiveOrUnboxableSupportedNumberOrBoolean

        public static boolean isPrimitiveOrUnboxableSupportedNumberOrBoolean(@Nullable
                                                                             java.lang.String descriptor)
        Determine if the supplied descriptor is for a supported number type or boolean. The compilation process only (currently) supports certain number types. These are double, float, long and int.
        Parameters:
        descriptor - the descriptor for a type
        Returns:
        true if the descriptor is for a supported numeric type or boolean
      • isPrimitiveOrUnboxableSupportedNumber

        public static boolean isPrimitiveOrUnboxableSupportedNumber(@Nullable
                                                                    java.lang.String descriptor)
        Determine if the supplied descriptor is for a supported number. The compilation process only (currently) supports certain number types. These are double, float, long and int.
        Parameters:
        descriptor - the descriptor for a type
        Returns:
        true if the descriptor is for a supported numeric type
      • isIntegerForNumericOp

        public static boolean isIntegerForNumericOp(java.lang.Number number)
        Determine whether the given number is to be considered as an integer for the purposes of a numeric operation at the bytecode level.
        Parameters:
        number - the number to check
        Returns:
        true if it is an Integer, Short or Byte
      • toPrimitiveTargetDesc

        public static char toPrimitiveTargetDesc(java.lang.String descriptor)
        Convert a type descriptor to the single character primitive descriptor.
        Parameters:
        descriptor - a descriptor for a type that should have a primitive representation
        Returns:
        the single character descriptor for a primitive input descriptor
      • insertCheckCast

        public static void insertCheckCast(MethodVisitor mv,
                                           @Nullable
                                           java.lang.String descriptor)
        Insert the appropriate CHECKCAST instruction for the supplied descriptor.
        Parameters:
        mv - the target visitor into which the instruction should be inserted
        descriptor - the descriptor of the type to cast to
      • insertBoxIfNecessary

        public static void insertBoxIfNecessary(MethodVisitor mv,
                                                @Nullable
                                                java.lang.String descriptor)
        Determine the appropriate boxing instruction for a specific type (if it needs boxing) and insert the instruction into the supplied visitor.
        Parameters:
        mv - the target visitor for the new instructions
        descriptor - the descriptor of a type that may or may not need boxing
      • insertBoxIfNecessary

        public static void insertBoxIfNecessary(MethodVisitor mv,
                                                char ch)
        Determine the appropriate boxing instruction for a specific type (if it needs boxing) and insert the instruction into the supplied visitor.
        Parameters:
        mv - the target visitor for the new instructions
        ch - the descriptor of the type that might need boxing
      • toDescriptor

        public static java.lang.String toDescriptor(java.lang.Class<?> type)
        Deduce the descriptor for a type. Descriptors are like JVM type names but missing the trailing ';' so for Object the descriptor is "Ljava/lang/Object" for int it is "I".
        Parameters:
        type - the type (may be primitive) for which to determine the descriptor
        Returns:
        the descriptor
      • toParamDescriptors

        public static java.lang.String[] toParamDescriptors(java.lang.reflect.Method method)
        Create an array of descriptors representing the parameter types for the supplied method. Returns a zero sized array if there are no parameters.
        Parameters:
        method - a Method
        Returns:
        a String array of descriptors, one entry for each method parameter
      • toParamDescriptors

        public static java.lang.String[] toParamDescriptors(java.lang.reflect.Constructor<?> ctor)
        Create an array of descriptors representing the parameter types for the supplied constructor. Returns a zero sized array if there are no parameters.
        Parameters:
        ctor - a Constructor
        Returns:
        a String array of descriptors, one entry for each constructor parameter
      • toDescriptors

        public static java.lang.String[] toDescriptors(java.lang.Class<?>[] types)
        Create an array of descriptors from an array of classes.
        Parameters:
        types - the input array of classes
        Returns:
        an array of descriptors
      • insertOptimalLoad

        public static void insertOptimalLoad(MethodVisitor mv,
                                             int value)
        Create the optimal instruction for loading a number on the stack.
        Parameters:
        mv - where to insert the bytecode
        value - the value to be loaded
      • insertArrayStore

        public static void insertArrayStore(MethodVisitor mv,
                                            java.lang.String arrayElementType)
        Produce appropriate bytecode to store a stack item in an array. The instruction to use varies depending on whether the type is a primitive or reference type.
        Parameters:
        mv - where to insert the bytecode
        arrayElementType - the type of the array elements
      • arrayCodeFor

        public static int arrayCodeFor(java.lang.String arraytype)
        Determine the appropriate T tag to use for the NEWARRAY bytecode.
        Parameters:
        arraytype - the array primitive component type
        Returns:
        the T tag to use for NEWARRAY
      • isReferenceTypeArray

        public static boolean isReferenceTypeArray(java.lang.String arraytype)
        Return if the supplied array type has a core component reference type.
      • insertNewArrayCode

        public static void insertNewArrayCode(MethodVisitor mv,
                                              int size,
                                              java.lang.String arraytype)
        Produce the correct bytecode to build an array. The opcode to use and the signature to pass along with the opcode can vary depending on the signature of the array type.
        Parameters:
        mv - the methodvisitor into which code should be inserted
        size - the size of the array
        arraytype - the type of the array
      • insertNumericUnboxOrPrimitiveTypeCoercion

        public static void insertNumericUnboxOrPrimitiveTypeCoercion(MethodVisitor mv,
                                                                     @Nullable
                                                                     java.lang.String stackDescriptor,
                                                                     char targetDescriptor)
        For use in mathematical operators, handles converting from a (possibly boxed) number on the stack to a primitive numeric type.

        For example, from a Integer to a double, just need to call 'Number.doubleValue()' but from an int to a double, need to use the bytecode 'i2d'.

        Parameters:
        mv - the method visitor when instructions should be appended
        stackDescriptor - a descriptor of the operand on the stack
        targetDescriptor - a primitive type descriptor
      • toBoxedDescriptor

        public static java.lang.String toBoxedDescriptor(java.lang.String primitiveDescriptor)