Top Description Inners Fields Constructors Methods
org.springframework.expression.spel

public Class CodeFlow

extends Object
implements Opcodes
Class Inheritance
All Implemented Interfaces
org.springframework.asm.Opcodes
Imports
java.lang.reflect.Constructor, .Method, java.util.ArrayDeque, .ArrayList, .Deque, .List, org.springframework.asm.ClassWriter, .MethodVisitor, .Opcodes, org.springframework.lang.Nullable, org.springframework.util.CollectionUtils

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.

Authors
Andy Clement, Juergen Hoeller
Since
4.1

Nested and Inner Type Summary

Modifier and TypeClass and Description
public static interface
CodeFlow.ClinitAdder

Interface used to generate clinit static initializer blocks.

public static interface
CodeFlow.FieldAdder

Interface used to generate fields.

Field Summary

Modifier and TypeField and Description
private final String
className

Name of the class being generated.

private final ClassWriter
classWriter

The current class being generated.

private List<CodeFlow.ClinitAdder>
clinitAdders

As SpEL AST nodes are called to generate code for the main evaluation method they can register to add code to a static initializer in the class.

private final Deque<List<String>>
compilationScopes

Record the type of what is on top of the bytecode stack (i.e. the type of the output from the previous expression component).

private List<CodeFlow.FieldAdder>
fieldAdders

As SpEL AST nodes are called to generate code for the main evaluation method they can register to add a field to this class.

private int
nextFieldId

When code generation requires holding a value in a class level field, this is used to track the next available field id (used as a name suffix).

private int
nextFreeVariableId

When code generation requires an intermediate variable within a method, this method records the next available variable (variable 0 is 'this').

Constructor Summary

AccessConstructor and Description
public
CodeFlow(String
the name of the class
className
,
ClassWriter
the corresponding ASM ClassWriter
classWriter
)

Construct a new CodeFlow for the given class.

Method Summary

Modifier and TypeMethod and Description
public static boolean

Returns:

true if it is possible to get (via boxing) from one descriptor to the other
areBoxingCompatible
(String desc1, String desc2)

Determine whether boxing/unboxing can get from one type to the other.

public static int

Returns:

the T tag to use for NEWARRAY
arrayCodeFor
(String
the array primitive component type
arrayType
)

Determine the appropriate T tag to use for the NEWARRAY bytecode.

private static boolean
checkPairs(String desc1, String desc2)

public static String

Returns:

a String signature descriptor (e.g. "(ILjava/lang/String;)V")
createSignatureDescriptor
(Method
the method
method
)

Create the JVM signature descriptor for a method.

public static String

Returns:

a String signature descriptor (e.g. "(ILjava/lang/String;)V")
createSignatureDescriptor
(Constructor<?>
the constructor
ctor
)

Create the JVM signature descriptor for a constructor.

public void
enterCompilationScope()

Enter a new compilation scope, usually due to nested expression evaluation.

public void
exitCompilationScope()

Exit a compilation scope, usually after a nested expression has been evaluated.

public void
finish()

Called after the main expression evaluation method has been generated, this method will call back any registered FieldAdders or ClinitAdders to add any extra information to the class representing the compiled expression.

public String
public static void
insertAnyNecessaryTypeConversionBytecodes(MethodVisitor
the method visitor into which instructions should be placed
mv
,
char
the (primitive) descriptor of the target type
targetDescriptor
,
String
the descriptor of the operand on top of the stack
stackDescriptor
)

Insert any necessary numeric conversion bytecodes based upon what is on the stack and the desired target type.

public static void
insertArrayStore(MethodVisitor
where to insert the bytecode
mv
,
String
the type of the array elements
arrayElementType
)

Produce appropriate bytecode to store a stack item in an array.

public static void
insertBoxIfNecessary(MethodVisitor
the method visitor for the new instructions
mv
,
String
the descriptor of a type that may or may not need boxing
descriptor
)

Determine the appropriate boxing instruction for a specific type (if it needs boxing) and insert the instruction into the supplied visitor.

public static void
insertBoxIfNecessary(MethodVisitor
the method visitor for the new instructions
mv
,
char
the descriptor of the type that might need boxing
ch
)

Determine the appropriate boxing instruction for a specific type (if it needs boxing) and insert the instruction into the supplied visitor.

public static void
insertCheckCast(MethodVisitor
the method visitor into which the instruction should be inserted
mv
,
String
the descriptor of the type to cast to
descriptor
)

Insert the appropriate CHECKCAST instruction for the supplied descriptor.

public static void
insertNewArrayCode(MethodVisitor
the method visitor into which code should be inserted
mv
,
int
the size of the array
size
,
String
the type of the array
arrayType
)

Produce the correct bytecode to build an array.

public static void
insertNumericUnboxOrPrimitiveTypeCoercion(MethodVisitor
the method visitor when instructions should be appended
mv
,
String
a descriptor of the operand on the stack
stackDescriptor
,
char
a primitive type descriptor
targetDescriptor
)

For use in mathematical operators, handles converting from a (possibly boxed) number on the stack to a primitive numeric type.

public static void
insertOptimalLoad(MethodVisitor
where to insert the bytecode
mv
,
int
the value to be loaded
value
)

Create the optimal instruction for loading a number on the stack.

public static void
insertUnboxInsns(MethodVisitor
the method visitor into which instructions should be inserted
mv
,
char
the primitive type desired as output
ch
,
String
the descriptor of the type on top of the stack
stackDescriptor
)

Insert any necessary cast and value call to convert from a boxed type to a primitive value.

public static void
insertUnboxNumberInsns(MethodVisitor
the method visitor into which instructions should be inserted
mv
,
char
the primitive type desired as output
targetDescriptor
,
String
the descriptor of the type on top of the stack
stackDescriptor
)

For numbers, use the appropriate method on the number to convert it to the primitive type requested.

public static boolean

Returns:

true if the descriptor is boolean compatible
isBooleanCompatible
(String
type descriptor
descriptor
)

Determine whether the descriptor is for a boolean primitive or boolean reference type.

public static boolean

Returns:

true if it is an Integer, Short or Byte
isIntegerForNumericOp
(Number
the number to check
number
)

Determine whether the given number is to be considered as an integer for the purposes of a numeric operation at the bytecode level.

public static boolean

Returns:

true if a primitive type or void
isPrimitive
(String
type descriptor
descriptor
)

Determine whether the descriptor is for a primitive type or void.

public static boolean

Returns:

true if the descriptor a primitive array
isPrimitiveArray
(String
the descriptor for a possible primitive array
descriptor
)

Determine whether the descriptor is for a primitive array (e.g. "[[I").

public static boolean

Returns:

true if the descriptor is for a supported numeric type
isPrimitiveOrUnboxableSupportedNumber
(String
the descriptor for a type
descriptor
)

Determine if the supplied descriptor is for a supported number.

public static boolean

Returns:

true if the descriptor is for a supported numeric type or boolean
isPrimitiveOrUnboxableSupportedNumberOrBoolean
(String
the descriptor for a type
descriptor
)

Determine if the supplied descriptor is for a supported number type or boolean.

public static boolean
isReferenceTypeArray(String arrayType)

Return if the supplied array type has a core component reference type.

public String
lastDescriptor()

Return the descriptor for the item currently on top of the stack (in the current scope).

public void
loadEvaluationContext(MethodVisitor
the method visitor into which the load instruction should be inserted
mv
)

Push the bytecode to load the EvaluationContext (the second parameter passed to the compiled expression method).

public void
loadTarget(MethodVisitor
the method visitor into which the load instruction should be inserted
mv
)

Push the byte code to load the target (i.e. what was passed as the first argument to CompiledExpression.getValue(target, context))

public int
public int
public void
pushDescriptor(String
type descriptor for most recently evaluated element
descriptor
)

Record the descriptor for the most recently evaluated expression element.

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 node's primary generateCode() method.

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 node's primary generateCode() method.

public static String
toBoxedDescriptor(String primitiveDescriptor)

public static String

Returns:

the descriptor
toDescriptor
(Class<?>
the type (may be primitive) for which to determine the descriptor
type
)

Deduce the descriptor for a type.

public static String

Returns:

the type descriptor for the object (descriptor is "Ljava/lang/Object" for null value)
toDescriptorFromObject
(Object
an object (possibly null)
value
)

Determine the descriptor for an object instance (or null).

public static String[]

Returns:

an array of descriptors
toDescriptors
(Class<?>[]
the input array of classes
types
)

Create an array of descriptors from an array of classes.

public static String

Returns:

the JVM descriptor for the class
toJvmDescriptor
(Class<?>
a class
clazz
)

Determine the JVM descriptor for a specified class.

public static String[]

Returns:

a String array of descriptors, one entry for each method parameter
toParamDescriptors
(Method
a Method
method
)

Create an array of descriptors representing the parameter types for the supplied method.

public static String[]

Returns:

a String array of descriptors, one entry for each constructor parameter
toParamDescriptors
(Constructor<?>
a Constructor
ctor
)

Create an array of descriptors representing the parameter types for the supplied constructor.

public static char

Returns:

the single character descriptor for a primitive input descriptor
toPrimitiveTargetDesc
(String
a descriptor for a type that should have a primitive representation
descriptor
)

Convert a type descriptor to the single character primitive descriptor.

public void
unboxBooleanIfNecessary(MethodVisitor
the method visitor into which new instructions should be inserted
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.

Inherited from java.lang.Object:
cloneequalsfinalizegetClasshashCodenotifynotifyAlltoStringwaitwaitwait

Field Detail

classNameback to summary
private final String className

Name of the class being generated. Typically used when generating code that accesses freshly generated fields on the generated type.

classWriterback to summary
private final ClassWriter classWriter

The current class being generated.

clinitAddersback to summary
private List<CodeFlow.ClinitAdder> clinitAdders

As SpEL AST nodes are called to generate code for the main evaluation method they can register to add code to a static initializer in the class. Any registered ClinitAdders will be called after the main evaluation function has finished being generated.

Annotations
@Nullable
compilationScopesback to summary
private final Deque<List<String>> compilationScopes

Record the type of what is on top of the bytecode stack (i.e. the type of the output from the previous expression component). New scopes are used to evaluate sub-expressions like the expressions for the argument values in a method invocation expression.

fieldAddersback to summary
private List<CodeFlow.FieldAdder> fieldAdders

As SpEL AST nodes are called to generate code for the main evaluation method they can register to add a field to this class. Any registered FieldAdders will be called after the main evaluation function has finished being generated.

Annotations
@Nullable
nextFieldIdback to summary
private int nextFieldId

When code generation requires holding a value in a class level field, this is used to track the next available field id (used as a name suffix).

nextFreeVariableIdback to summary
private int nextFreeVariableId

When code generation requires an intermediate variable within a method, this method records the next available variable (variable 0 is 'this').

Constructor Detail

CodeFlowback to summary
public CodeFlow(String className, ClassWriter classWriter)

Construct a new CodeFlow for the given class.

Parameters
className:String

the name of the class

classWriter:ClassWriter

the corresponding ASM ClassWriter

Method Detail

areBoxingCompatibleback to summary
public static boolean areBoxingCompatible(String desc1, 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:boolean

true if it is possible to get (via boxing) from one descriptor to the other

arrayCodeForback to summary
public static int arrayCodeFor(String arrayType)

Determine the appropriate T tag to use for the NEWARRAY bytecode.

Parameters
arrayType:String

the array primitive component type

Returns:int

the T tag to use for NEWARRAY

checkPairsback to summary
private static boolean checkPairs(String desc1, String desc2)
createSignatureDescriptorback to summary
public static String createSignatureDescriptor(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:Method

the method

Returns:String

a String signature descriptor (e.g. "(ILjava/lang/String;)V")

createSignatureDescriptorback to summary
public static String createSignatureDescriptor(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:Constructor<?>

the constructor

Returns:String

a String signature descriptor (e.g. "(ILjava/lang/String;)V")

enterCompilationScopeback to summary
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.

exitCompilationScopeback to summary
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.

finishback to summary
public void finish()

Called after the main expression evaluation method has been generated, this method will call back any registered FieldAdders or ClinitAdders to add any extra information to the class representing the compiled expression.

getClassNameback to summary
public String getClassName()
insertAnyNecessaryTypeConversionBytecodesback to summary
public static void insertAnyNecessaryTypeConversionBytecodes(MethodVisitor mv, char targetDescriptor, String stackDescriptor)

Insert any necessary numeric conversion bytecodes based upon what is on the stack and the desired target type.

Parameters
mv:MethodVisitor

the method visitor into which instructions should be placed

targetDescriptor:char

the (primitive) descriptor of the target type

stackDescriptor:String

the descriptor of the operand on top of the stack

insertArrayStoreback to summary
public static void insertArrayStore(MethodVisitor mv, 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:MethodVisitor

where to insert the bytecode

arrayElementType:String

the type of the array elements

insertBoxIfNecessaryback to summary
public static void insertBoxIfNecessary(MethodVisitor mv, 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:MethodVisitor

the method visitor for the new instructions

descriptor:String

the descriptor of a type that may or may not need boxing

insertBoxIfNecessaryback to summary
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:MethodVisitor

the method visitor for the new instructions

ch:char

the descriptor of the type that might need boxing

insertCheckCastback to summary
public static void insertCheckCast(MethodVisitor mv, String descriptor)

Insert the appropriate CHECKCAST instruction for the supplied descriptor.

Parameters
mv:MethodVisitor

the method visitor into which the instruction should be inserted

descriptor:String

the descriptor of the type to cast to

insertNewArrayCodeback to summary
public static void insertNewArrayCode(MethodVisitor mv, int size, 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:MethodVisitor

the method visitor into which code should be inserted

size:int

the size of the array

arrayType:String

the type of the array

insertNumericUnboxOrPrimitiveTypeCoercionback to summary
public static void insertNumericUnboxOrPrimitiveTypeCoercion(MethodVisitor mv, 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 an 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:MethodVisitor

the method visitor when instructions should be appended

stackDescriptor:String

a descriptor of the operand on the stack

targetDescriptor:char

a primitive type descriptor

insertOptimalLoadback to summary
public static void insertOptimalLoad(MethodVisitor mv, int value)

Create the optimal instruction for loading a number on the stack.

Parameters
mv:MethodVisitor

where to insert the bytecode

value:int

the value to be loaded

insertUnboxInsnsback to summary
public static void insertUnboxInsns(MethodVisitor mv, char ch, String stackDescriptor)

Insert any necessary cast and value call to convert from a boxed type to a primitive value.

Parameters
mv:MethodVisitor

the method visitor into which instructions should be inserted

ch:char

the primitive type desired as output

stackDescriptor:String

the descriptor of the type on top of the stack

insertUnboxNumberInsnsback to summary
public static void insertUnboxNumberInsns(MethodVisitor mv, char targetDescriptor, String stackDescriptor)

For numbers, use the appropriate method on the number to convert it to the primitive type requested.

Parameters
mv:MethodVisitor

the method visitor into which instructions should be inserted

targetDescriptor:char

the primitive type desired as output

stackDescriptor:String

the descriptor of the type on top of the stack

isBooleanCompatibleback to summary
public static boolean isBooleanCompatible(String descriptor)

Determine whether the descriptor is for a boolean primitive or boolean reference type.

Parameters
descriptor:String

type descriptor

Returns:boolean

true if the descriptor is boolean compatible

isIntegerForNumericOpback to summary
public static boolean isIntegerForNumericOp(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:Number

the number to check

Returns:boolean

true if it is an Integer, Short or Byte

isPrimitiveback to summary
public static boolean isPrimitive(String descriptor)

Determine whether the descriptor is for a primitive type or void.

Parameters
descriptor:String

type descriptor

Returns:boolean

true if a primitive type or void

isPrimitiveArrayback to summary
public static boolean isPrimitiveArray(String descriptor)

Determine whether the descriptor is for a primitive array (e.g. "[[I").

Parameters
descriptor:String

the descriptor for a possible primitive array

Returns:boolean

true if the descriptor a primitive array

isPrimitiveOrUnboxableSupportedNumberback to summary
public static boolean isPrimitiveOrUnboxableSupportedNumber(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:String

the descriptor for a type

Returns:boolean

true if the descriptor is for a supported numeric type

isPrimitiveOrUnboxableSupportedNumberOrBooleanback to summary
public static boolean isPrimitiveOrUnboxableSupportedNumberOrBoolean(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:String

the descriptor for a type

Returns:boolean

true if the descriptor is for a supported numeric type or boolean

isReferenceTypeArrayback to summary
public static boolean isReferenceTypeArray(String arrayType)

Return if the supplied array type has a core component reference type.

lastDescriptorback to summary
public String lastDescriptor()

Return the descriptor for the item currently on top of the stack (in the current scope).

Annotations
@Nullable
loadEvaluationContextback to summary
public void loadEvaluationContext(MethodVisitor mv)

Push the bytecode to load the EvaluationContext (the second parameter passed to the compiled expression method).

Parameters
mv:MethodVisitor

the method visitor into which the load instruction should be inserted

Since
4.3.4
loadTargetback to summary
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:MethodVisitor

the method visitor into which the load instruction should be inserted

nextFieldIdback to summary
public int nextFieldId()
nextFreeVariableIdback to summary
public int nextFreeVariableId()
pushDescriptorback to summary
public void pushDescriptor(String descriptor)

Record the descriptor for the most recently evaluated expression element.

Parameters
descriptor:String

type descriptor for most recently evaluated element

registerNewClinitback to summary
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 node's primary generateCode() method.

registerNewFieldback to summary
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 node's primary generateCode() method.

toBoxedDescriptorback to summary
public static String toBoxedDescriptor(String primitiveDescriptor)
toDescriptorback to summary
public static String toDescriptor(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:Class<?>

the type (may be primitive) for which to determine the descriptor

Returns:String

the descriptor

toDescriptorFromObjectback to summary
public static String toDescriptorFromObject(Object value)

Determine the descriptor for an object instance (or null).

Parameters
value:Object

an object (possibly null)

Returns:String

the type descriptor for the object (descriptor is "Ljava/lang/Object" for null value)

toDescriptorsback to summary
public static String[] toDescriptors(Class<?>[] types)

Create an array of descriptors from an array of classes.

Parameters
types:Class<?>[]

the input array of classes

Returns:String[]

an array of descriptors

toJvmDescriptorback to summary
public static String toJvmDescriptor(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:Class<?>

a class

Returns:String

the JVM descriptor for the class

toParamDescriptorsback to summary
public static String[] toParamDescriptors(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:Method

a Method

Returns:String[]

a String array of descriptors, one entry for each method parameter

toParamDescriptorsback to summary
public static String[] toParamDescriptors(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:Constructor<?>

a Constructor

Returns:String[]

a String array of descriptors, one entry for each constructor parameter

toPrimitiveTargetDescback to summary
public static char toPrimitiveTargetDesc(String descriptor)

Convert a type descriptor to the single character primitive descriptor.

Parameters
descriptor:String

a descriptor for a type that should have a primitive representation

Returns:char

the single character descriptor for a primitive input descriptor

unboxBooleanIfNecessaryback to summary
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:MethodVisitor

the method visitor into which new instructions should be inserted

org.springframework.expression.spel back to summary

public Interface CodeFlow.ClinitAdder

Annotations
@FunctionalInterface

Interface used to generate clinit static initializer blocks.

Method Summary

Modifier and TypeMethod and Description
public void

Method Detail

generateCodeback to summary
public void generateCode(MethodVisitor mv, CodeFlow codeflow)
org.springframework.expression.spel back to summary

public Interface CodeFlow.FieldAdder

Annotations
@FunctionalInterface

Interface used to generate fields.

Method Summary

Modifier and TypeMethod and Description
public void

Method Detail

generateFieldback to summary
public void generateField(ClassWriter cw, CodeFlow codeflow)