@Nullable all the way: null-safety at field level

This commits extends nullability declarations to the field level, formalizing the interaction between methods and their underlying fields and therefore avoiding any nullability mismatch.

Issue: SPR-15720
This commit is contained in:
Juergen Hoeller
2017-06-30 01:53:45 +02:00
parent c4694c3f5c
commit cc74a2891a
936 changed files with 6090 additions and 2806 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.springframework.expression;
import org.springframework.lang.Nullable;
/**
* Super class for exceptions that can occur whilst processing expressions.
*
@@ -26,6 +28,7 @@ package org.springframework.expression;
@SuppressWarnings("serial")
public class ExpressionException extends RuntimeException {
@Nullable
protected String expressionString;
protected int position; // -1 if not known; should be known in all reasonable cases
@@ -53,7 +56,7 @@ public class ExpressionException extends RuntimeException {
* @param expressionString the expression string
* @param message a descriptive message
*/
public ExpressionException(String expressionString, String message) {
public ExpressionException(@Nullable String expressionString, String message) {
super(message);
this.expressionString = expressionString;
this.position = -1;
@@ -65,7 +68,7 @@ public class ExpressionException extends RuntimeException {
* @param position the position in the expression string where the problem occurred
* @param message a descriptive message
*/
public ExpressionException(String expressionString, int position, String message) {
public ExpressionException(@Nullable String expressionString, int position, String message) {
super(message);
this.expressionString = expressionString;
this.position = position;
@@ -96,6 +99,7 @@ public class ExpressionException extends RuntimeException {
/**
* Return the expression string.
*/
@Nullable
public final String getExpressionString() {
return this.expressionString;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.springframework.expression;
import org.springframework.lang.Nullable;
/**
* Represent an exception that occurs during expression parsing.
*
@@ -31,7 +33,7 @@ public class ParseException extends ExpressionException {
* @param position the position in the expression string where the problem occurred
* @param message description of the problem that occurred
*/
public ParseException(String expressionString, int position, String message) {
public ParseException(@Nullable String expressionString, int position, String message) {
super(expressionString, position, message);
}

View File

@@ -34,8 +34,10 @@ public class TypedValue {
public static final TypedValue NULL = new TypedValue(null);
@Nullable
private final Object value;
@Nullable
private TypeDescriptor typeDescriptor;

View File

@@ -26,7 +26,6 @@ import org.springframework.asm.ClassWriter;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Opcodes;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Manages the class being generated by the compilation process.
@@ -64,6 +63,7 @@ public class CodeFlow implements Opcodes {
* 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.
*/
@Nullable
private List<FieldAdder> fieldAdders;
/**
@@ -72,6 +72,7 @@ public class CodeFlow implements Opcodes {
* registered ClinitAdders will be called after the main evaluation function
* has finished being generated.
*/
@Nullable
private List<ClinitAdder> clinitAdders;
/**
@@ -123,9 +124,10 @@ public class CodeFlow implements Opcodes {
* Record the descriptor for the most recently evaluated expression element.
* @param descriptor type descriptor for most recently evaluated element
*/
public void pushDescriptor(String descriptor) {
Assert.notNull(descriptor, "Descriptor must not be null");
this.compilationScopes.peek().add(descriptor);
public void pushDescriptor(@Nullable String descriptor) {
if (descriptor != null) {
this.compilationScopes.peek().add(descriptor);
}
}
/**
@@ -236,7 +238,10 @@ public class CodeFlow implements Opcodes {
* @param ch the primitive type desired as output
* @param stackDescriptor the descriptor of the type on top of the stack
*/
public static void insertUnboxInsns(MethodVisitor mv, char ch, String stackDescriptor) {
public static void insertUnboxInsns(MethodVisitor mv, char ch, @Nullable String stackDescriptor) {
if (stackDescriptor == null) {
return;
}
switch (ch) {
case 'Z':
if (!stackDescriptor.equals("Ljava/lang/Boolean")) {
@@ -297,7 +302,13 @@ public class CodeFlow implements Opcodes {
* @param targetDescriptor the primitive type desired as output
* @param stackDescriptor the descriptor of the type on top of the stack
*/
public static void insertUnboxNumberInsns(MethodVisitor mv, char targetDescriptor, String stackDescriptor) {
public static void insertUnboxNumberInsns(
MethodVisitor mv, char targetDescriptor, @Nullable String stackDescriptor) {
if (stackDescriptor == null) {
return;
}
switch (targetDescriptor) {
case 'D':
if (stackDescriptor.equals("Ljava/lang/Object")) {
@@ -510,7 +521,6 @@ public class CodeFlow implements Opcodes {
* @return the type descriptor for the object
* (descriptor is "Ljava/lang/Object" for {@code null} value)
*/
@Nullable
public static String toDescriptorFromObject(@Nullable Object value) {
if (value == null) {
return "Ljava/lang/Object";
@@ -540,7 +550,10 @@ public class CodeFlow implements Opcodes {
* @param descriptor the descriptor for a possible primitive array
* @return {@code true} if the descriptor is for a primitive array (e.g. "[[I")
*/
public static boolean isPrimitiveArray(String descriptor) {
public static boolean isPrimitiveArray(@Nullable String descriptor) {
if (descriptor == null) {
return false;
}
boolean primitive = true;
for (int i = 0, max = descriptor.length(); i < max; i++) {
char ch = descriptor.charAt(i);
@@ -691,8 +704,8 @@ public class CodeFlow implements Opcodes {
* @param mv the target visitor into which the instruction should be inserted
* @param descriptor the descriptor of the type to cast to
*/
public static void insertCheckCast(MethodVisitor mv, String descriptor) {
if (descriptor.length() != 1) {
public static void insertCheckCast(MethodVisitor mv, @Nullable String descriptor) {
if (descriptor != null && descriptor.length() != 1) {
if (descriptor.charAt(0) == '[') {
if (isPrimitiveArray(descriptor)) {
mv.visitTypeInsn(CHECKCAST, descriptor);
@@ -771,7 +784,6 @@ public class CodeFlow implements Opcodes {
* @param type the type (may be primitive) for which to determine the descriptor
* @return the descriptor
*/
@Nullable
public static String toDescriptor(Class<?> type) {
String name = type.getName();
if (type.isPrimitive()) {
@@ -825,7 +837,7 @@ public class CodeFlow implements Opcodes {
}
}
}
return null;
return "";
}
/**
@@ -982,7 +994,7 @@ public class CodeFlow implements Opcodes {
* @param targetDescriptor a primitive type descriptor
*/
public static void insertNumericUnboxOrPrimitiveTypeCoercion(
MethodVisitor mv, String stackDescriptor, char targetDescriptor) {
MethodVisitor mv, @Nullable String stackDescriptor, char targetDescriptor) {
if (!CodeFlow.isPrimitive(stackDescriptor)) {
CodeFlow.insertUnboxNumberInsns(mv, targetDescriptor, stackDescriptor);

View File

@@ -33,6 +33,7 @@ import org.springframework.expression.TypeConverter;
import org.springframework.expression.TypedValue;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* An ExpressionState is for maintaining per-expression-evaluation state, any changes to
@@ -54,6 +55,14 @@ public class ExpressionState {
private final TypedValue rootObject;
private final SpelParserConfiguration configuration;
@Nullable
private Stack<TypedValue> contextObjects;
@Nullable
private Stack<VariableScope> variableScopes;
// When entering a new scope there is a new base object which should be used
// for '#this' references (or to act as a target for unqualified references).
// This stack captures those objects at each nested scope level.
@@ -61,14 +70,9 @@ public class ExpressionState {
// #list1.?[#list2.contains(#this)]
// On entering the selection we enter a new scope, and #this is now the
// element from list1
@Nullable
private Stack<TypedValue> scopeRootObjects;
private final SpelParserConfiguration configuration;
private Stack<VariableScope> variableScopes;
private Stack<TypedValue> contextObjects;
public ExpressionState(EvaluationContext context) {
this(context, context.getRootObject(), new SpelParserConfiguration(false, false));
@@ -91,22 +95,11 @@ public class ExpressionState {
}
private void ensureVariableScopesInitialized() {
if (this.variableScopes == null) {
this.variableScopes = new Stack<>();
// top level empty variable scope
this.variableScopes.add(new VariableScope());
}
if (this.scopeRootObjects == null) {
this.scopeRootObjects = new Stack<>();
}
}
/**
* The active context object is what unqualified references to properties/etc are resolved against.
*/
public TypedValue getActiveContextObject() {
if (this.contextObjects == null || this.contextObjects.isEmpty()) {
if (CollectionUtils.isEmpty(this.contextObjects)) {
return this.rootObject;
}
return this.contextObjects.peek();
@@ -131,7 +124,7 @@ public class ExpressionState {
}
public TypedValue getScopeRootContextObject() {
if (this.scopeRootObjects == null || this.scopeRootObjects.isEmpty()) {
if (CollectionUtils.isEmpty(this.scopeRootObjects)) {
return this.rootObject;
}
return this.scopeRootObjects.peek();
@@ -177,46 +170,57 @@ public class ExpressionState {
* A new scope is entered when a function is invoked.
*/
public void enterScope(Map<String, Object> argMap) {
ensureVariableScopesInitialized();
this.variableScopes.push(new VariableScope(argMap));
this.scopeRootObjects.push(getActiveContextObject());
initVariableScopes().push(new VariableScope(argMap));
initScopeRootObjects().push(getActiveContextObject());
}
public void enterScope() {
ensureVariableScopesInitialized();
this.variableScopes.push(new VariableScope(Collections.emptyMap()));
this.scopeRootObjects.push(getActiveContextObject());
initVariableScopes().push(new VariableScope(Collections.emptyMap()));
initScopeRootObjects().push(getActiveContextObject());
}
public void enterScope(String name, Object value) {
ensureVariableScopesInitialized();
this.variableScopes.push(new VariableScope(name, value));
this.scopeRootObjects.push(getActiveContextObject());
initVariableScopes().push(new VariableScope(name, value));
initScopeRootObjects().push(getActiveContextObject());
}
public void exitScope() {
ensureVariableScopesInitialized();
this.variableScopes.pop();
this.scopeRootObjects.pop();
initVariableScopes().pop();
initScopeRootObjects().pop();
}
public void setLocalVariable(String name, Object value) {
ensureVariableScopesInitialized();
this.variableScopes.peek().setVariable(name, value);
initVariableScopes().peek().setVariable(name, value);
}
@Nullable
public Object lookupLocalVariable(String name) {
ensureVariableScopesInitialized();
int scopeNumber = this.variableScopes.size() - 1;
int scopeNumber = initVariableScopes().size() - 1;
for (int i = scopeNumber; i >= 0; i--) {
if (this.variableScopes.get(i).definesVariable(name)) {
return this.variableScopes.get(i).lookupVariable(name);
VariableScope scope = initVariableScopes().get(i);
if (scope.definesVariable(name)) {
return scope.lookupVariable(name);
}
}
return null;
}
private Stack<VariableScope> initVariableScopes() {
if (this.variableScopes == null) {
this.variableScopes = new Stack<>();
// top level empty variable scope
this.variableScopes.add(new VariableScope());
}
return this.variableScopes;
}
private Stack<TypedValue> initScopeRootObjects() {
if (this.scopeRootObjects == null) {
this.scopeRootObjects = new Stack<>();
}
return this.scopeRootObjects;
}
public TypedValue operate(Operation op, @Nullable Object left, @Nullable Object right) throws EvaluationException {
OperatorOverloader overloader = this.relatedContext.getOperatorOverloader();
if (overloader.overridesOperation(op, left, right)) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
package org.springframework.expression.spel;
import org.springframework.expression.ParseException;
import org.springframework.lang.Nullable;
/**
* Root exception for Spring EL related exceptions. Rather than holding a hard coded
@@ -35,7 +36,7 @@ public class SpelParseException extends ParseException {
private final Object[] inserts;
public SpelParseException(String expressionString, int position, SpelMessage message, Object... inserts) {
public SpelParseException(@Nullable String expressionString, int position, SpelMessage message, Object... inserts) {
super(expressionString, position, message.formatMessage(inserts));
this.message = message;
this.inserts = inserts;

View File

@@ -41,6 +41,7 @@ public class SpelParserConfiguration {
private final SpelCompilerMode compilerMode;
@Nullable
private final ClassLoader compilerClassLoader;
private final boolean autoGrowNullReferences;
@@ -106,35 +107,36 @@ public class SpelParserConfiguration {
/**
* @return the configuration mode for parsers using this configuration object
* Return the configuration mode for parsers using this configuration object.
*/
public SpelCompilerMode getCompilerMode() {
return this.compilerMode;
}
/**
* @return the ClassLoader to use as the basis for expression compilation
* Return the ClassLoader to use as the basis for expression compilation.
*/
@Nullable
public ClassLoader getCompilerClassLoader() {
return this.compilerClassLoader;
}
/**
* @return {@code true} if {@code null} references should be automatically grown
* Return {@code true} if {@code null} references should be automatically grown.
*/
public boolean isAutoGrowNullReferences() {
return this.autoGrowNullReferences;
}
/**
* @return {@code true} if collections should be automatically grown
* Return {@code true} if collections should be automatically grown.
*/
public boolean isAutoGrowCollections() {
return this.autoGrowCollections;
}
/**
* @return the maximum size that a collection can auto grow
* Return the maximum size that a collection can auto grow.
*/
public int getMaximumAutoGrowSize() {
return this.maximumAutoGrowSize;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,7 +37,7 @@ public class BeanReference extends SpelNodeImpl {
private final String beanName;
public BeanReference(int pos,String beanName) {
public BeanReference(int pos, String beanName) {
super(pos);
this.beanName = beanName;
}

View File

@@ -39,6 +39,7 @@ import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.SpelNode;
import org.springframework.expression.spel.support.ReflectiveConstructorExecutor;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -58,12 +59,12 @@ public class ConstructorReference extends SpelNodeImpl {
private boolean isArrayConstructor = false;
@Nullable
private SpelNodeImpl[] dimensions;
// TODO is this caching safe - passing the expression around will mean this executor is also being passed around
/**
* The cached executor that may be reused on subsequent evaluations.
*/
/** The cached executor that may be reused on subsequent evaluations */
@Nullable
private volatile ConstructorExecutor cachedExecutor;
@@ -157,9 +158,9 @@ public class ConstructorReference extends SpelNodeImpl {
executorToUse = findExecutorForConstructor(typeName, argumentTypes, state);
try {
this.cachedExecutor = executorToUse;
if (this.cachedExecutor instanceof ReflectiveConstructorExecutor) {
if (executorToUse instanceof ReflectiveConstructorExecutor) {
this.exitTypeDescriptor = CodeFlow.toDescriptor(
((ReflectiveConstructorExecutor) this.cachedExecutor).getConstructor().getDeclaringClass());
((ReflectiveConstructorExecutor) executorToUse).getConstructor().getDeclaringClass());
}
return executorToUse.execute(state.getEvaluationContext(), arguments);
@@ -245,9 +246,11 @@ public class ConstructorReference extends SpelNodeImpl {
Object newArray;
if (!hasInitializer()) {
// Confirm all dimensions were specified (for example [3][][5] is missing the 2nd dimension)
for (SpelNodeImpl dimension : this.dimensions) {
if (dimension == null) {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.MISSING_ARRAY_DIMENSION);
if (this.dimensions != null) {
for (SpelNodeImpl dimension : this.dimensions) {
if (dimension == null) {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.MISSING_ARRAY_DIMENSION);
}
}
}
TypeConverter typeConverter = state.getEvaluationContext().getTypeConverter();
@@ -270,7 +273,7 @@ public class ConstructorReference extends SpelNodeImpl {
}
else {
// There is an initializer
if (this.dimensions.length > 1) {
if (this.dimensions == null || this.dimensions.length > 1) {
// There is an initializer but this is a multi-dimensional array (e.g. new int[][]{{1,2},{3,4}}) - this
// is not currently supported
throw new SpelEvaluationException(getStartPosition(),
@@ -436,6 +439,9 @@ public class ConstructorReference extends SpelNodeImpl {
}
ReflectiveConstructorExecutor executor = (ReflectiveConstructorExecutor) this.cachedExecutor;
if (executor == null) {
return false;
}
Constructor<?> constructor = executor.getConstructor();
return (Modifier.isPublic(constructor.getModifiers()) &&
Modifier.isPublic(constructor.getDeclaringClass().getModifiers()));
@@ -444,10 +450,13 @@ public class ConstructorReference extends SpelNodeImpl {
@Override
public void generateCode(MethodVisitor mv, CodeFlow cf) {
ReflectiveConstructorExecutor executor = ((ReflectiveConstructorExecutor) this.cachedExecutor);
Constructor<?> constructor = executor.getConstructor();
Assert.state(executor != null, "No cached executor");
Constructor<?> constructor = executor.getConstructor();
String classDesc = constructor.getDeclaringClass().getName().replace('.', '/');
mv.visitTypeInsn(NEW, classDesc);
mv.visitInsn(DUP);
// children[0] is the type of the constructor, don't want to include that in argument processing
SpelNodeImpl[] arguments = new SpelNodeImpl[children.length - 1];
System.arraycopy(children, 1, arguments, 0, children.length - 1);

View File

@@ -23,6 +23,7 @@ import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
@@ -109,7 +110,7 @@ public class Elvis extends SpelNodeImpl {
this.children[1].exitTypeDescriptor != null) {
String conditionDescriptor = this.children[0].exitTypeDescriptor;
String ifNullValueDescriptor = this.children[1].exitTypeDescriptor;
if (conditionDescriptor.equals(ifNullValueDescriptor)) {
if (ObjectUtils.nullSafeEquals(conditionDescriptor, ifNullValueDescriptor)) {
this.exitTypeDescriptor = conditionDescriptor;
}
else {

View File

@@ -30,6 +30,8 @@ import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.support.ReflectionHelper;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
@@ -53,6 +55,7 @@ public class FunctionReference extends SpelNodeImpl {
// Captures the most recently used method for the function invocation *if* the method
// can safely be used for compilation (i.e. no argument conversion is going on)
@Nullable
private Method method;
private boolean argumentConversionOccurred;
@@ -177,6 +180,7 @@ public class FunctionReference extends SpelNodeImpl {
@Override
public void generateCode(MethodVisitor mv,CodeFlow cf) {
Assert.state(this.method != null, "No method handle");
String classDesc = this.method.getDeclaringClass().getName().replace('.', '/');
generateCodeForArguments(mv, cf, this.method, this.children);
mv.visitMethodInsn(INVOKESTATIC, classDesc, this.method.getName(),

View File

@@ -54,7 +54,7 @@ import org.springframework.util.ReflectionUtils;
// TODO support correct syntax for multidimensional [][][] and not [,,,]
public class Indexer extends SpelNodeImpl {
private static enum IndexedType {ARRAY, LIST, MAP, STRING, OBJECT}
private enum IndexedType {ARRAY, LIST, MAP, STRING, OBJECT}
// These fields are used when the indexer is being used as a property read accessor.
@@ -62,10 +62,13 @@ public class Indexer extends SpelNodeImpl {
// is used to read the property. If they do not match, the correct accessor is
// discovered and then cached for later use.
@Nullable
private String cachedReadName;
@Nullable
private Class<?> cachedReadTargetType;
@Nullable
private PropertyAccessor cachedReadAccessor;
// These fields are used when the indexer is being used as a property write accessor.
@@ -73,12 +76,16 @@ public class Indexer extends SpelNodeImpl {
// is used to write the property. If they do not match, the correct accessor is
// discovered and then cached for later use.
@Nullable
private String cachedWriteName;
@Nullable
private Class<?> cachedWriteTargetType;
@Nullable
private PropertyAccessor cachedWriteAccessor;
@Nullable
private IndexedType indexedType;
@@ -282,6 +289,7 @@ public class Indexer extends SpelNodeImpl {
else if (this.indexedType == IndexedType.OBJECT) {
ReflectivePropertyAccessor.OptimalPropertyAccessor accessor =
(ReflectivePropertyAccessor.OptimalPropertyAccessor) this.cachedReadAccessor;
Assert.state(accessor != null, "No cached read accessor");
Member member = accessor.member;
boolean isStatic = Modifier.isStatic(member.getModifiers());
String classDesc = member.getDeclaringClass().getName().replace('.', '/');
@@ -493,6 +501,7 @@ public class Indexer extends SpelNodeImpl {
private final Map map;
@Nullable
private final Object key;
private final TypeDescriptor mapEntryDescriptor;
@@ -556,7 +565,9 @@ public class Indexer extends SpelNodeImpl {
Indexer.this.cachedReadTargetType != null &&
Indexer.this.cachedReadTargetType.equals(targetObjectRuntimeClass)) {
// It is OK to use the cached accessor
return Indexer.this.cachedReadAccessor.read(this.evaluationContext, this.targetObject, this.name);
PropertyAccessor accessor = Indexer.this.cachedReadAccessor;
Assert.state(accessor != null, "No cached read accessor");
return accessor.read(this.evaluationContext, this.targetObject, this.name);
}
List<PropertyAccessor> accessorsToTry = AstUtils.getPropertyAccessorsToTry(
targetObjectRuntimeClass, this.evaluationContext.getPropertyAccessors());
@@ -596,7 +607,9 @@ public class Indexer extends SpelNodeImpl {
Indexer.this.cachedWriteTargetType != null &&
Indexer.this.cachedWriteTargetType.equals(contextObjectClass)) {
// It is OK to use the cached accessor
Indexer.this.cachedWriteAccessor.write(this.evaluationContext, this.targetObject, this.name, newValue);
PropertyAccessor accessor = Indexer.this.cachedWriteAccessor;
Assert.state(accessor != null, "No cached write accessor");
accessor.write(this.evaluationContext, this.targetObject, this.name, newValue);
return;
}
List<PropertyAccessor> accessorsToTry =

View File

@@ -27,6 +27,7 @@ import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelNode;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Represent a list in an expression, e.g. '{1,2,3}'
@@ -37,7 +38,8 @@ import org.springframework.lang.Nullable;
public class InlineList extends SpelNodeImpl {
// If the list is purely literals, it is a constant value and can be computed and cached
private TypedValue constant = null; // TODO must be immutable list
@Nullable
private TypedValue constant; // TODO must be immutable list
public InlineList(int pos, SpelNodeImpl... args) {
@@ -123,6 +125,7 @@ public class InlineList extends SpelNodeImpl {
@SuppressWarnings("unchecked")
@Nullable
public List<Object> getConstantValue() {
Assert.state(this.constant != null, "No constant");
return (List<Object>) this.constant.getValue();
}

View File

@@ -25,6 +25,7 @@ import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelNode;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Represent a map in an expression, e.g. '{name:'foo',age:12}'
@@ -35,7 +36,8 @@ import org.springframework.lang.Nullable;
public class InlineMap extends SpelNodeImpl {
// If the map is purely literals, it is a constant value and can be computed and cached
private TypedValue constant = null;
@Nullable
private TypedValue constant;
public InlineMap(int pos, SpelNodeImpl... args) {
@@ -149,7 +151,7 @@ public class InlineMap extends SpelNodeImpl {
}
/**
* @return whether this list is a constant value
* Return whether this list is a constant value.
*/
public boolean isConstant() {
return this.constant != null;
@@ -158,6 +160,7 @@ public class InlineMap extends SpelNodeImpl {
@SuppressWarnings("unchecked")
@Nullable
public Map<Object,Object> getConstantValue() {
Assert.state(this.constant != null, "No constant");
return (Map<Object,Object>) this.constant.getValue();
}

View File

@@ -32,6 +32,7 @@ import org.springframework.lang.Nullable;
*/
public abstract class Literal extends SpelNodeImpl {
@Nullable
private final String originalValue;
@@ -41,6 +42,7 @@ public abstract class Literal extends SpelNodeImpl {
}
@Nullable
public final String getOriginalValue() {
return this.originalValue;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -55,6 +55,7 @@ public class MethodReference extends SpelNodeImpl {
private final boolean nullSafe;
@Nullable
private volatile CachedMethodExecutor cachedExecutor;
@@ -338,8 +339,10 @@ public class MethodReference extends SpelNodeImpl {
private final EvaluationContext evaluationContext;
@Nullable
private final Object value;
@Nullable
private final TypeDescriptor targetType;
private final Object[] arguments;
@@ -360,7 +363,7 @@ public class MethodReference extends SpelNodeImpl {
}
@Override
public void setValue(Object newValue) {
public void setValue(@Nullable Object newValue) {
throw new IllegalAccessError();
}
@@ -375,8 +378,10 @@ public class MethodReference extends SpelNodeImpl {
private final MethodExecutor methodExecutor;
@Nullable
private final Class<?> staticClass;
@Nullable
private final TypeDescriptor target;
private final List<TypeDescriptor> argumentTypes;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ import org.springframework.expression.Operation;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.util.Assert;
import org.springframework.util.NumberUtils;
/**
@@ -105,14 +106,17 @@ public class OpDivide extends Operator {
public void generateCode(MethodVisitor mv, CodeFlow cf) {
getLeftOperand().generateCode(mv, cf);
String leftDesc = getLeftOperand().exitTypeDescriptor;
CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, leftDesc, this.exitTypeDescriptor.charAt(0));
String exitDesc = this.exitTypeDescriptor;
Assert.state(exitDesc != null, "No exit type descriptor");
char targetDesc = exitDesc.charAt(0);
CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, leftDesc, targetDesc);
if (this.children.length > 1) {
cf.enterCompilationScope();
getRightOperand().generateCode(mv, cf);
String rightDesc = getRightOperand().exitTypeDescriptor;
cf.exitCompilationScope();
CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, rightDesc, this.exitTypeDescriptor.charAt(0));
switch (this.exitTypeDescriptor.charAt(0)) {
CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, rightDesc, targetDesc);
switch (targetDesc) {
case 'I':
mv.visitInsn(IDIV);
break;

View File

@@ -25,6 +25,7 @@ import org.springframework.expression.Operation;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.util.Assert;
import org.springframework.util.NumberUtils;
/**
@@ -176,14 +177,17 @@ public class OpMinus extends Operator {
public void generateCode(MethodVisitor mv, CodeFlow cf) {
getLeftOperand().generateCode(mv, cf);
String leftDesc = getLeftOperand().exitTypeDescriptor;
CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, leftDesc, this.exitTypeDescriptor.charAt(0));
String exitDesc = this.exitTypeDescriptor;
Assert.state(exitDesc != null, "No exit type descriptor");
char targetDesc = exitDesc.charAt(0);
CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, leftDesc, targetDesc);
if (this.children.length > 1) {
cf.enterCompilationScope();
getRightOperand().generateCode(mv, cf);
String rightDesc = getRightOperand().exitTypeDescriptor;
cf.exitCompilationScope();
CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, rightDesc, this.exitTypeDescriptor.charAt(0));
switch (this.exitTypeDescriptor.charAt(0)) {
CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, rightDesc, targetDesc);
switch (targetDesc) {
case 'I':
mv.visitInsn(ISUB);
break;
@@ -202,7 +206,7 @@ public class OpMinus extends Operator {
}
}
else {
switch (this.exitTypeDescriptor.charAt(0)) {
switch (targetDesc) {
case 'I':
mv.visitInsn(INEG);
break;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@ import org.springframework.expression.Operation;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.util.Assert;
import org.springframework.util.NumberUtils;
/**
@@ -103,14 +104,17 @@ public class OpModulus extends Operator {
public void generateCode(MethodVisitor mv, CodeFlow cf) {
getLeftOperand().generateCode(mv, cf);
String leftDesc = getLeftOperand().exitTypeDescriptor;
CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, leftDesc, this.exitTypeDescriptor.charAt(0));
String exitDesc = this.exitTypeDescriptor;
Assert.state(exitDesc != null, "No exit type descriptor");
char targetDesc = exitDesc.charAt(0);
CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, leftDesc, targetDesc);
if (this.children.length > 1) {
cf.enterCompilationScope();
getRightOperand().generateCode(mv, cf);
String rightDesc = getRightOperand().exitTypeDescriptor;
cf.exitCompilationScope();
CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, rightDesc, this.exitTypeDescriptor.charAt(0));
switch (this.exitTypeDescriptor.charAt(0)) {
CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, rightDesc, targetDesc);
switch (targetDesc) {
case 'I':
mv.visitInsn(IREM);
break;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@ import org.springframework.expression.Operation;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.util.Assert;
import org.springframework.util.NumberUtils;
/**
@@ -136,14 +137,17 @@ public class OpMultiply extends Operator {
public void generateCode(MethodVisitor mv, CodeFlow cf) {
getLeftOperand().generateCode(mv, cf);
String leftDesc = getLeftOperand().exitTypeDescriptor;
CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, leftDesc, this.exitTypeDescriptor.charAt(0));
String exitDesc = this.exitTypeDescriptor;
Assert.state(exitDesc != null, "No exit type descriptor");
char targetDesc = exitDesc.charAt(0);
CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, leftDesc, targetDesc);
if (this.children.length > 1) {
cf.enterCompilationScope();
getRightOperand().generateCode(mv, cf);
String rightDesc = getRightOperand().exitTypeDescriptor;
cf.exitCompilationScope();
CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, rightDesc, this.exitTypeDescriptor.charAt(0));
switch (this.exitTypeDescriptor.charAt(0)) {
CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, rightDesc, targetDesc);
switch (targetDesc) {
case 'I':
mv.visitInsn(IMUL);
break;

View File

@@ -219,14 +219,17 @@ public class OpPlus extends Operator {
else {
this.children[0].generateCode(mv, cf);
String leftDesc = this.children[0].exitTypeDescriptor;
CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, leftDesc, this.exitTypeDescriptor.charAt(0));
String exitDesc = this.exitTypeDescriptor;
Assert.state(exitDesc != null, "No exit type descriptor");
char targetDesc = exitDesc.charAt(0);
CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, leftDesc, targetDesc);
if (this.children.length > 1) {
cf.enterCompilationScope();
this.children[1].generateCode(mv, cf);
String rightDesc = this.children[1].exitTypeDescriptor;
cf.exitCompilationScope();
CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, rightDesc, this.exitTypeDescriptor.charAt(0));
switch (this.exitTypeDescriptor.charAt(0)) {
CodeFlow.insertNumericUnboxOrPrimitiveTypeCoercion(mv, rightDesc, targetDesc);
switch (targetDesc) {
case 'I':
mv.visitInsn(IADD);
break;

View File

@@ -47,8 +47,10 @@ public abstract class Operator extends SpelNodeImpl {
// whose accessors seem to only be returning 'Object' - the actual descriptors may
// indicate 'int')
@Nullable
protected String leftActualDescriptor;
@Nullable
protected String rightActualDescriptor;
@@ -266,8 +268,9 @@ public abstract class Operator extends SpelNodeImpl {
* @param rightActualDescriptor the dynamic/runtime right object descriptor
* @return a DescriptorComparison object indicating the type of compatibility, if any
*/
public static DescriptorComparison checkNumericCompatibility(String leftDeclaredDescriptor,
String rightDeclaredDescriptor, String leftActualDescriptor, String rightActualDescriptor) {
public static DescriptorComparison checkNumericCompatibility(
@Nullable String leftDeclaredDescriptor, @Nullable String rightDeclaredDescriptor,
@Nullable String leftActualDescriptor, @Nullable String rightActualDescriptor) {
String ld = leftDeclaredDescriptor;
String rd = rightDeclaredDescriptor;
@@ -276,11 +279,11 @@ public abstract class Operator extends SpelNodeImpl {
boolean rightNumeric = CodeFlow.isPrimitiveOrUnboxableSupportedNumberOrBoolean(rd);
// If the declared descriptors aren't providing the information, try the actual descriptors
if (!leftNumeric && !ld.equals(leftActualDescriptor)) {
if (!leftNumeric && !ObjectUtils.nullSafeEquals(ld, leftActualDescriptor)) {
ld = leftActualDescriptor;
leftNumeric = CodeFlow.isPrimitiveOrUnboxableSupportedNumberOrBoolean(ld);
}
if (!rightNumeric && !rd.equals(rightActualDescriptor)) {
if (!rightNumeric && !ObjectUtils.nullSafeEquals(rd, rightActualDescriptor)) {
rd = rightActualDescriptor;
rightNumeric = CodeFlow.isPrimitiveOrUnboxableSupportedNumberOrBoolean(rd);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -53,8 +53,10 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
private final String name;
@Nullable
private volatile PropertyAccessor cachedReadAccessor;
@Nullable
private volatile PropertyAccessor cachedWriteAccessor;

View File

@@ -19,6 +19,7 @@ package org.springframework.expression.spel.ast;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.lang.Nullable;
/**
* Represents a dot separated sequence of strings that indicate a package qualified type
@@ -31,7 +32,7 @@ import org.springframework.expression.spel.ExpressionState;
*/
public class QualifiedIdentifier extends SpelNodeImpl {
// TODO safe to cache? dont think so
@Nullable
private TypedValue value;

View File

@@ -50,6 +50,7 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
protected SpelNodeImpl[] children = SpelNodeImpl.NO_CHILDREN;
@Nullable
private SpelNodeImpl parent;
/**
@@ -61,6 +62,7 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
* It does not include the trailing semicolon (for non array reference types).
* Some examples: Ljava/lang/String, I, [I
*/
@Nullable
protected volatile String exitTypeDescriptor;
@@ -182,6 +184,7 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
throw new IllegalStateException(getClass().getName() +" has no generateCode(..) method");
}
@Nullable
public String getExitDescriptor() {
return this.exitTypeDescriptor;
}
@@ -222,25 +225,25 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
generateCodeForArgument(mv, cf, arguments[p], paramDescriptors[p]);
}
SpelNodeImpl lastchild = (childCount == 0 ? null : arguments[childCount - 1]);
String arraytype = paramDescriptors[paramDescriptors.length - 1];
SpelNodeImpl lastChild = (childCount == 0 ? null : arguments[childCount - 1]);
String arrayType = paramDescriptors[paramDescriptors.length - 1];
// Determine if the final passed argument is already suitably packaged in array
// form to be passed to the method
if (lastchild != null && lastchild.getExitDescriptor().equals(arraytype)) {
generateCodeForArgument(mv, cf, lastchild, paramDescriptors[p]);
if (lastChild != null && arrayType.equals(lastChild.getExitDescriptor())) {
generateCodeForArgument(mv, cf, lastChild, paramDescriptors[p]);
}
else {
arraytype = arraytype.substring(1); // trim the leading '[', may leave other '['
arrayType = arrayType.substring(1); // trim the leading '[', may leave other '['
// build array big enough to hold remaining arguments
CodeFlow.insertNewArrayCode(mv, childCount - p, arraytype);
CodeFlow.insertNewArrayCode(mv, childCount - p, arrayType);
// Package up the remaining arguments into the array
int arrayindex = 0;
while (p < childCount) {
SpelNodeImpl child = arguments[p];
mv.visitInsn(DUP);
CodeFlow.insertOptimalLoad(mv, arrayindex++);
generateCodeForArgument(mv, cf, child, arraytype);
CodeFlow.insertArrayStore(mv, arraytype);
generateCodeForArgument(mv, cf, child, arrayType);
CodeFlow.insertArrayStore(mv, arrayType);
p++;
}
}

View File

@@ -25,6 +25,7 @@ import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Represents a ternary expression, for example: "someCheck()?true:false".
@@ -69,7 +70,7 @@ public class Ternary extends SpelNodeImpl {
this.children[2].exitTypeDescriptor != null) {
String leftDescriptor = this.children[1].exitTypeDescriptor;
String rightDescriptor = this.children[2].exitTypeDescriptor;
if (leftDescriptor.equals(rightDescriptor)) {
if (ObjectUtils.nullSafeEquals(leftDescriptor, rightDescriptor)) {
this.exitTypeDescriptor = leftDescriptor;
}
else {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -153,7 +153,7 @@ public class VariableReference extends SpelNodeImpl {
mv.visitLdcInsn(name);
mv.visitMethodInsn(INVOKEINTERFACE, "org/springframework/expression/EvaluationContext", "lookupVariable", "(Ljava/lang/String;)Ljava/lang/Object;",true);
}
CodeFlow.insertCheckCast(mv,this.exitTypeDescriptor);
CodeFlow.insertCheckCast(mv, this.exitTypeDescriptor);
cf.pushDescriptor(this.exitTypeDescriptor);
}

View File

@@ -17,6 +17,7 @@
package org.springframework.expression.spel.standard;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;
@@ -95,10 +96,10 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
private final Stack<SpelNodeImpl> constructedNodes = new Stack<>();
// The expression being parsed
private String expressionString;
private String expressionString = "";
// The token stream constructed from that expression string
private List<Token> tokenStream;
private List<Token> tokenStream = Collections.emptyList();
// length of a populated token stream
private int tokenStreamLength;
@@ -117,7 +118,9 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
@Override
protected SpelExpression doParseExpression(String expressionString, @Nullable ParserContext context) throws ParseException {
protected SpelExpression doParseExpression(String expressionString, @Nullable ParserContext context)
throws ParseException {
try {
this.expressionString = expressionString;
Tokenizer tokenizer = new Tokenizer(expressionString);
@@ -127,8 +130,10 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
this.tokenStreamPointer = 0;
this.constructedNodes.clear();
SpelNodeImpl ast = eatExpression();
if (moreTokens()) {
throw new SpelParseException(peekToken().startPos, SpelMessage.MORE_INPUT, toString(nextToken()));
Assert.state(ast != null, "No node");
Token t = peekToken();
if (t != null) {
throw new SpelParseException(t.startPos, SpelMessage.MORE_INPUT, toString(nextToken()));
}
Assert.isTrue(this.constructedNodes.isEmpty(), "At least one node expected");
return new SpelExpression(expressionString, ast, this.configuration);
@@ -144,10 +149,11 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
// | (DEFAULT^ logicalOrExpression)
// | (QMARK^ expression COLON! expression)
// | (ELVIS^ expression))?;
@Nullable
private SpelNodeImpl eatExpression() {
SpelNodeImpl expr = eatLogicalOrExpression();
if (moreTokens()) {
Token t = peekToken();
Token t = peekToken();
if (t != null) {
if (t.kind == TokenKind.ASSIGN) { // a=b
if (expr == null) {
expr = new NullLiteral(toPos(t.startPos - 1, t.endPos - 1));
@@ -182,10 +188,11 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
}
//logicalOrExpression : logicalAndExpression (OR^ logicalAndExpression)*;
@Nullable
private SpelNodeImpl eatLogicalOrExpression() {
SpelNodeImpl expr = eatLogicalAndExpression();
while (peekIdentifierToken("or") || peekToken(TokenKind.SYMBOLIC_OR)) {
Token t = nextToken(); //consume OR
Token t = takeToken(); //consume OR
SpelNodeImpl rhExpr = eatLogicalAndExpression();
checkOperands(t, expr, rhExpr);
expr = new OpOr(toPos(t), expr, rhExpr);
@@ -194,10 +201,11 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
}
// logicalAndExpression : relationalExpression (AND^ relationalExpression)*;
@Nullable
private SpelNodeImpl eatLogicalAndExpression() {
SpelNodeImpl expr = eatRelationalExpression();
while (peekIdentifierToken("and") || peekToken(TokenKind.SYMBOLIC_AND)) {
Token t = nextToken(); // consume 'AND'
Token t = takeToken(); // consume 'AND'
SpelNodeImpl rhExpr = eatRelationalExpression();
checkOperands(t, expr, rhExpr);
expr = new OpAnd(toPos(t), expr, rhExpr);
@@ -206,11 +214,12 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
}
// relationalExpression : sumExpression (relationalOperator^ sumExpression)?;
@Nullable
private SpelNodeImpl eatRelationalExpression() {
SpelNodeImpl expr = eatSumExpression();
Token relationalOperatorToken = maybeEatRelationalOperator();
if (relationalOperatorToken != null) {
Token t = nextToken(); // consume relational operator token
Token t = takeToken(); // consume relational operator token
SpelNodeImpl rhExpr = eatSumExpression();
checkOperands(t, expr, rhExpr);
TokenKind tk = relationalOperatorToken.kind;
@@ -251,10 +260,11 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
}
//sumExpression: productExpression ( (PLUS^ | MINUS^) productExpression)*;
@Nullable
private SpelNodeImpl eatSumExpression() {
SpelNodeImpl expr = eatProductExpression();
while (peekToken(TokenKind.PLUS, TokenKind.MINUS, TokenKind.INC)) {
Token t = nextToken(); //consume PLUS or MINUS or INC
Token t = takeToken(); //consume PLUS or MINUS or INC
SpelNodeImpl rhExpr = eatProductExpression();
checkRightOperand(t, rhExpr);
if (t.kind == TokenKind.PLUS) {
@@ -268,10 +278,11 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
}
// productExpression: powerExpr ((STAR^ | DIV^| MOD^) powerExpr)* ;
@Nullable
private SpelNodeImpl eatProductExpression() {
SpelNodeImpl expr = eatPowerIncDecExpression();
while (peekToken(TokenKind.STAR, TokenKind.DIV, TokenKind.MOD)) {
Token t = nextToken(); // consume STAR/DIV/MOD
Token t = takeToken(); // consume STAR/DIV/MOD
SpelNodeImpl rhExpr = eatPowerIncDecExpression();
checkOperands(t, expr, rhExpr);
if (t.kind == TokenKind.STAR) {
@@ -289,16 +300,17 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
}
// powerExpr : unaryExpression (POWER^ unaryExpression)? (INC || DEC) ;
@Nullable
private SpelNodeImpl eatPowerIncDecExpression() {
SpelNodeImpl expr = eatUnaryExpression();
if (peekToken(TokenKind.POWER)) {
Token t = nextToken(); //consume POWER
Token t = takeToken(); //consume POWER
SpelNodeImpl rhExpr = eatUnaryExpression();
checkRightOperand(t, rhExpr);
return new OperatorPower(toPos(t), expr, rhExpr);
}
if (expr != null && peekToken(TokenKind.INC, TokenKind.DEC)) {
Token t = nextToken(); //consume INC/DEC
Token t = takeToken(); //consume INC/DEC
if (t.getKind() == TokenKind.INC) {
return new OpInc(toPos(t), true, expr);
}
@@ -308,10 +320,12 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
}
// unaryExpression: (PLUS^ | MINUS^ | BANG^ | INC^ | DEC^) unaryExpression | primaryExpression ;
@Nullable
private SpelNodeImpl eatUnaryExpression() {
if (peekToken(TokenKind.PLUS, TokenKind.MINUS, TokenKind.NOT)) {
Token t = nextToken();
Token t = takeToken();
SpelNodeImpl expr = eatUnaryExpression();
Assert.state(expr != null, "No node");
if (t.kind == TokenKind.NOT) {
return new OperatorNot(toPos(t), expr);
}
@@ -324,7 +338,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
}
if (peekToken(TokenKind.INC, TokenKind.DEC)) {
Token t = nextToken();
Token t = takeToken();
SpelNodeImpl expr = eatUnaryExpression();
if (t.getKind() == TokenKind.INC) {
return new OpInc(toPos(t), false, expr);
@@ -346,7 +360,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
if (nodes.size() == 1) {
return nodes.get(0);
}
return new CompoundExpression(toPos(start.getStartPosition(),
return new CompoundExpression(toPos((start != null ? start.getStartPosition() : 0),
nodes.get(nodes.size() - 1).getEndPosition()),
nodes.toArray(new SpelNodeImpl[nodes.size()]));
}
@@ -392,7 +406,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
// ;
@Nullable
private SpelNodeImpl eatDottedNode() {
Token t = nextToken(); // it was a '.' or a '?.'
Token t = takeToken(); // it was a '.' or a '?.'
boolean nullSafeNavigation = (t.kind == TokenKind.SAFE_NAVI);
if (maybeEatMethodOrProperty(nullSafeNavigation) || maybeEatFunctionOrVar() ||
maybeEatProjection(nullSafeNavigation) || maybeEatSelection(nullSafeNavigation)) {
@@ -418,16 +432,16 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
if (!peekToken(TokenKind.HASH)) {
return false;
}
Token t = nextToken();
Token t = takeToken();
Token functionOrVariableName = eatToken(TokenKind.IDENTIFIER);
SpelNodeImpl[] args = maybeEatMethodArgs();
if (args == null) {
push(new VariableReference(functionOrVariableName.data,
push(new VariableReference(functionOrVariableName.stringValue(),
toPos(t.startPos, functionOrVariableName.endPos)));
return true;
}
push(new FunctionReference(functionOrVariableName.data,
push(new FunctionReference(functionOrVariableName.stringValue(),
toPos(t.startPos, functionOrVariableName.endPos), args));
return true;
}
@@ -457,11 +471,13 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
* Used for consuming arguments for either a method or a constructor call
*/
private void consumeArguments(List<SpelNodeImpl> accumulatedArguments) {
int pos = peekToken().startPos;
Token t = peekToken();
Assert.state(t != null, "Expected token");
int pos = t.startPos;
Token next;
do {
nextToken(); // consume (first time through) or comma (subsequent times)
Token t = peekToken();
t = peekToken();
if (t == null) {
raiseInternalException(pos, SpelMessage.RUN_OUT_OF_ARGUMENTS);
}
@@ -527,12 +543,12 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
// quoted if dotted
private boolean maybeEatBeanReference() {
if (peekToken(TokenKind.BEAN_REF) || peekToken(TokenKind.FACTORY_BEAN_REF)) {
Token beanRefToken = nextToken();
Token beanRefToken = takeToken();
Token beanNameToken = null;
String beanName = null;
if (peekToken(TokenKind.IDENTIFIER)) {
beanNameToken = eatToken(TokenKind.IDENTIFIER);
beanName = beanNameToken.data;
beanName = beanNameToken.stringValue();
}
else if (peekToken(TokenKind.LITERAL_STRING)) {
beanNameToken = eatToken(TokenKind.LITERAL_STRING);
@@ -546,8 +562,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
BeanReference beanReference;
if (beanRefToken.getKind() == TokenKind.FACTORY_BEAN_REF) {
String beanNameString = new StringBuilder().
append(TokenKind.FACTORY_BEAN_REF.tokenChars).append(beanName).toString();
String beanNameString = String.valueOf(TokenKind.FACTORY_BEAN_REF.tokenChars) + beanName;
beanReference = new BeanReference(
toPos(beanRefToken.startPos, beanNameToken.endPos), beanNameString);
}
@@ -563,14 +578,15 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
private boolean maybeEatTypeReference() {
if (peekToken(TokenKind.IDENTIFIER)) {
Token typeName = peekToken();
Assert.state(typeName != null, "Expected token");
if (!"T".equals(typeName.stringValue())) {
return false;
}
// It looks like a type reference but is T being used as a map key?
Token t = nextToken();
Token t = takeToken();
if (peekToken(TokenKind.RSQUARE)) {
// looks like 'T]' (T is map key)
push(new PropertyOrFieldReference(false, t.data, toPos(t)));
push(new PropertyOrFieldReference(false, t.stringValue(), toPos(t)));
return true;
}
eatToken(TokenKind.LPAREN);
@@ -592,6 +608,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
private boolean maybeEatNullReference() {
if (peekToken(TokenKind.IDENTIFIER)) {
Token nullToken = peekToken();
Assert.state(nullToken != null, "Expected token");
if (!"null".equalsIgnoreCase(nullToken.stringValue())) {
return false;
}
@@ -608,7 +625,9 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
if (!peekToken(TokenKind.PROJECT, true)) {
return false;
}
Assert.state(t != null, "No token");
SpelNodeImpl expr = eatExpression();
Assert.state(expr != null, "No node");
eatToken(TokenKind.RSQUARE);
this.constructedNodes.push(new Projection(nullSafeNavigation, toPos(t), expr));
return true;
@@ -621,10 +640,12 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
if (!peekToken(TokenKind.LCURLY, true)) {
return false;
}
Assert.state(t != null, "No token");
SpelNodeImpl expr = null;
Token closingCurly = peekToken();
if (peekToken(TokenKind.RCURLY, true)) {
// empty list '{}'
Assert.state(closingCurly != null, "No token");
expr = new InlineList(toPos(t.startPos, closingCurly.endPos));
}
else if (peekToken(TokenKind.COLON, true)) {
@@ -683,7 +704,9 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
if (!peekToken(TokenKind.LSQUARE, true)) {
return false;
}
Assert.state(t != null, "No token");
SpelNodeImpl expr = eatExpression();
Assert.state(expr != null, "No node");
eatToken(TokenKind.RSQUARE);
this.constructedNodes.push(new Indexer(toPos(t), expr));
return true;
@@ -694,6 +717,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
if (!peekSelectToken()) {
return false;
}
Assert.state(t != null, "No token");
nextToken();
SpelNodeImpl expr = eatExpression();
if (expr == null) {
@@ -755,16 +779,16 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
// a series of identifiers and dollars into a single identifier.
private boolean maybeEatMethodOrProperty(boolean nullSafeNavigation) {
if (peekToken(TokenKind.IDENTIFIER)) {
Token methodOrPropertyName = nextToken();
Token methodOrPropertyName = takeToken();
SpelNodeImpl[] args = maybeEatMethodArgs();
if (args == null) {
// property
push(new PropertyOrFieldReference(nullSafeNavigation, methodOrPropertyName.data,
push(new PropertyOrFieldReference(nullSafeNavigation, methodOrPropertyName.stringValue(),
toPos(methodOrPropertyName)));
return true;
}
// method reference
push(new MethodReference(nullSafeNavigation, methodOrPropertyName.data,
push(new MethodReference(nullSafeNavigation, methodOrPropertyName.stringValue(),
toPos(methodOrPropertyName), args));
// TODO what is the end position for a method reference? the name or the last arg?
return true;
@@ -776,11 +800,11 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
//: ('new' qualifiedId LPAREN) => 'new' qualifiedId ctorArgs -> ^(CONSTRUCTOR qualifiedId ctorArgs)
private boolean maybeEatConstructorReference() {
if (peekIdentifierToken("new")) {
Token newToken = nextToken();
Token newToken = takeToken();
// It looks like a constructor reference but is NEW being used as a map key?
if (peekToken(TokenKind.RSQUARE)) {
// looks like 'NEW]' (so NEW used as map key)
push(new PropertyOrFieldReference(false, newToken.data, toPos(newToken)));
push(new PropertyOrFieldReference(false, newToken.stringValue(), toPos(newToken)));
return true;
}
SpelNodeImpl possiblyQualifiedConstructorName = eatPossiblyQualifiedId();
@@ -839,31 +863,31 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
return false;
}
if (t.kind == TokenKind.LITERAL_INT) {
push(Literal.getIntLiteral(t.data, toPos(t), 10));
push(Literal.getIntLiteral(t.stringValue(), toPos(t), 10));
}
else if (t.kind == TokenKind.LITERAL_LONG) {
push(Literal.getLongLiteral(t.data, toPos(t), 10));
push(Literal.getLongLiteral(t.stringValue(), toPos(t), 10));
}
else if (t.kind == TokenKind.LITERAL_HEXINT) {
push(Literal.getIntLiteral(t.data, toPos(t), 16));
push(Literal.getIntLiteral(t.stringValue(), toPos(t), 16));
}
else if (t.kind == TokenKind.LITERAL_HEXLONG) {
push(Literal.getLongLiteral(t.data, toPos(t), 16));
push(Literal.getLongLiteral(t.stringValue(), toPos(t), 16));
}
else if (t.kind == TokenKind.LITERAL_REAL) {
push(Literal.getRealLiteral(t.data, toPos(t), false));
push(Literal.getRealLiteral(t.stringValue(), toPos(t), false));
}
else if (t.kind == TokenKind.LITERAL_REAL_FLOAT) {
push(Literal.getRealLiteral(t.data, toPos(t), true));
push(Literal.getRealLiteral(t.stringValue(), toPos(t), true));
}
else if (peekIdentifierToken("true")) {
push(new BooleanLiteral(t.data, toPos(t), true));
push(new BooleanLiteral(t.stringValue(), toPos(t), true));
}
else if (peekIdentifierToken("false")) {
push(new BooleanLiteral(t.data, toPos(t), false));
push(new BooleanLiteral(t.stringValue(), toPos(t), false));
}
else if (t.kind == TokenKind.LITERAL_STRING) {
push(new StringLiteral(t.data, toPos(t), t.data));
push(new StringLiteral(t.stringValue(), toPos(t), t.stringValue()));
}
else {
return false;
@@ -877,6 +901,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
if (peekToken(TokenKind.LPAREN)) {
nextToken();
SpelNodeImpl expr = eatExpression();
Assert.state(expr != null, "No node");
eatToken(TokenKind.RPAREN);
push(expr);
return true;
@@ -916,7 +941,8 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
private Token eatToken(TokenKind expectedKind) {
Token t = nextToken();
if (t == null) {
raiseInternalException( this.expressionString.length(), SpelMessage.OOD);
int pos = this.expressionString.length();
raiseInternalException(pos, SpelMessage.OOD);
}
if (t.kind != expectedKind) {
raiseInternalException(t.startPos, SpelMessage.NOT_EXPECTED_TOKEN,
@@ -930,10 +956,10 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
}
private boolean peekToken(TokenKind desiredTokenKind, boolean consumeIfMatched) {
if (!moreTokens()) {
Token t = peekToken();
if (t == null) {
return false;
}
Token t = peekToken();
if (t.kind == desiredTokenKind) {
if (consumeIfMatched) {
this.tokenStreamPointer++;
@@ -955,39 +981,42 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
}
private boolean peekToken(TokenKind possible1, TokenKind possible2) {
if (!moreTokens()) {
Token t = peekToken();
if (t == null) {
return false;
}
Token t = peekToken();
return (t.kind == possible1 || t.kind == possible2);
}
private boolean peekToken(TokenKind possible1, TokenKind possible2, TokenKind possible3) {
if (!moreTokens()) {
Token t = peekToken();
if (t == null) {
return false;
}
Token t = peekToken();
return (t.kind == possible1 || t.kind == possible2 || t.kind == possible3);
}
private boolean peekIdentifierToken(String identifierString) {
if (!moreTokens()) {
Token t = peekToken();
if (t == null) {
return false;
}
Token t = peekToken();
return (t.kind == TokenKind.IDENTIFIER && t.stringValue().equalsIgnoreCase(identifierString));
return (t.kind == TokenKind.IDENTIFIER && identifierString.equalsIgnoreCase(t.stringValue()));
}
private boolean peekSelectToken() {
if (!moreTokens()) {
Token t = peekToken();
if (t == null) {
return false;
}
Token t = peekToken();
return (t.kind == TokenKind.SELECT || t.kind == TokenKind.SELECT_FIRST || t.kind == TokenKind.SELECT_LAST);
}
private boolean moreTokens() {
return this.tokenStreamPointer<this.tokenStream.size();
private Token takeToken() {
if (this.tokenStreamPointer >= this.tokenStreamLength) {
throw new IllegalStateException("No token");
}
return this.tokenStream.get(this.tokenStreamPointer++);
}
@Nullable
@@ -1010,14 +1039,17 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
throw new InternalParseException(new SpelParseException(this.expressionString, pos, message, inserts));
}
public String toString(Token t) {
public String toString(@Nullable Token t) {
if (t == null) {
return "";
}
if (t.getKind().hasPayload()) {
return t.stringValue();
}
return t.kind.toString().toLowerCase();
}
private void checkOperands(Token token, SpelNodeImpl left, SpelNodeImpl right) {
private void checkOperands(Token token, @Nullable SpelNodeImpl left, @Nullable SpelNodeImpl right) {
checkLeftOperand(token, left);
checkRightOperand(token, right);
}

View File

@@ -59,9 +59,11 @@ public class SpelExpression implements Expression {
private final SpelParserConfiguration configuration;
// The default context is used if no override is supplied by the user
@Nullable
private EvaluationContext evaluationContext;
// Holds the compiled form of the expression (if it has been compiled)
@Nullable
private CompiledExpression compiledAst;
// Count of many times as the expression been interpreted - can trigger compilation

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.springframework.expression.spel.standard;
import org.springframework.lang.Nullable;
/**
* Holder for a kind of token, the associated data and its position in the input data
* stream (start/end).
@@ -27,6 +29,7 @@ class Token {
TokenKind kind;
@Nullable
String data;
int startPos; // index of first character
@@ -56,18 +59,6 @@ class Token {
return this.kind;
}
@Override
public String toString() {
StringBuilder s = new StringBuilder();
s.append("[").append(this.kind.toString());
if (this.kind.hasPayload()) {
s.append(":").append(this.data);
}
s.append("]");
s.append("(").append(this.startPos).append(",").append(this.endPos).append(")");
return s.toString();
}
public boolean isIdentifier() {
return (this.kind == TokenKind.IDENTIFIER);
}
@@ -78,7 +69,7 @@ class Token {
}
public String stringValue() {
return this.data;
return (this.data != null ? this.data : "");
}
public Token asInstanceOfToken() {
@@ -93,4 +84,17 @@ class Token {
return new Token(TokenKind.BETWEEN, this.startPos, this.endPos);
}
@Override
public String toString() {
StringBuilder s = new StringBuilder();
s.append("[").append(this.kind.toString());
if (this.kind.hasPayload()) {
s.append(":").append(this.data);
}
s.append("]");
s.append("(").append(this.startPos).append(",").append(this.endPos).append(")");
return s.toString();
}
}

View File

@@ -22,6 +22,7 @@ import org.springframework.expression.AccessException;
import org.springframework.expression.ConstructorExecutor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.TypedValue;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
/**
@@ -36,6 +37,7 @@ public class ReflectiveConstructorExecutor implements ConstructorExecutor {
private final Constructor<?> ctor;
@Nullable
private final Integer varargsPosition;

View File

@@ -37,10 +37,12 @@ public class ReflectiveMethodExecutor implements MethodExecutor {
private final Method method;
@Nullable
private final Integer varargsPosition;
private boolean computedPublicDeclaringClass = false;
@Nullable
private Class<?> publicDeclaringClass;
private boolean argumentConversionOccurred = false;

View File

@@ -57,6 +57,7 @@ public class ReflectiveMethodResolver implements MethodResolver {
// more closely following the Java rules.
private final boolean useDistance;
@Nullable
private Map<Class<?>, MethodFilter> filters;

View File

@@ -76,6 +76,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
private final Map<PropertyCacheKey, TypeDescriptor> typeDescriptorCache = new ConcurrentHashMap<>(64);
@Nullable
private InvokerPair lastReadInvokerPair;
@@ -122,8 +123,10 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
return false;
}
@Nullable
public Member getLastReadInvokerPair() {
return this.lastReadInvokerPair.member;
InvokerPair lastReadInvoker = this.lastReadInvokerPair;
return (lastReadInvoker != null ? lastReadInvoker.member : null);
}
@Override

View File

@@ -49,20 +49,27 @@ import org.springframework.util.Assert;
*/
public class StandardEvaluationContext implements EvaluationContext {
private TypedValue rootObject;
private TypedValue rootObject = TypedValue.NULL;
private List<ConstructorResolver> constructorResolvers;
@Nullable
private volatile List<PropertyAccessor> propertyAccessors;
private List<MethodResolver> methodResolvers;
@Nullable
private volatile List<ConstructorResolver> constructorResolvers;
@Nullable
private volatile List<MethodResolver> methodResolvers;
@Nullable
private volatile ReflectiveMethodResolver reflectiveMethodResolver;
@Nullable
private BeanResolver beanResolver;
private ReflectiveMethodResolver reflectiveMethodResolver;
private List<PropertyAccessor> propertyAccessors;
@Nullable
private TypeLocator typeLocator;
@Nullable
private TypeConverter typeConverter;
private TypeComparator typeComparator = new StandardTypeComparator();
@@ -94,14 +101,21 @@ public class StandardEvaluationContext implements EvaluationContext {
return this.rootObject;
}
public void addConstructorResolver(ConstructorResolver resolver) {
ensureConstructorResolversInitialized();
this.constructorResolvers.add(this.constructorResolvers.size() - 1, resolver);
public void setPropertyAccessors(List<PropertyAccessor> propertyAccessors) {
this.propertyAccessors = propertyAccessors;
}
public boolean removeConstructorResolver(ConstructorResolver resolver) {
ensureConstructorResolversInitialized();
return this.constructorResolvers.remove(resolver);
@Override
public List<PropertyAccessor> getPropertyAccessors() {
return initPropertyAccessors();
}
public void addPropertyAccessor(PropertyAccessor accessor) {
addBeforeDefault(initPropertyAccessors(), accessor);
}
public boolean removePropertyAccessor(PropertyAccessor accessor) {
return initPropertyAccessors().remove(accessor);
}
public void setConstructorResolvers(List<ConstructorResolver> constructorResolvers) {
@@ -110,18 +124,15 @@ public class StandardEvaluationContext implements EvaluationContext {
@Override
public List<ConstructorResolver> getConstructorResolvers() {
ensureConstructorResolversInitialized();
return this.constructorResolvers;
return initConstructorResolvers();
}
public void addMethodResolver(MethodResolver resolver) {
ensureMethodResolversInitialized();
this.methodResolvers.add(this.methodResolvers.size() - 1, resolver);
public void addConstructorResolver(ConstructorResolver resolver) {
addBeforeDefault(initConstructorResolvers(), resolver);
}
public boolean removeMethodResolver(MethodResolver methodResolver) {
ensureMethodResolversInitialized();
return this.methodResolvers.remove(methodResolver);
public boolean removeConstructorResolver(ConstructorResolver resolver) {
return initConstructorResolvers().remove(resolver);
}
public void setMethodResolvers(List<MethodResolver> methodResolvers) {
@@ -130,8 +141,15 @@ public class StandardEvaluationContext implements EvaluationContext {
@Override
public List<MethodResolver> getMethodResolvers() {
ensureMethodResolversInitialized();
return this.methodResolvers;
return initMethodResolvers();
}
public void addMethodResolver(MethodResolver resolver) {
addBeforeDefault(initMethodResolvers(), resolver);
}
public boolean removeMethodResolver(MethodResolver methodResolver) {
return initMethodResolvers().remove(methodResolver);
}
public void setBeanResolver(BeanResolver beanResolver) {
@@ -143,25 +161,6 @@ public class StandardEvaluationContext implements EvaluationContext {
return this.beanResolver;
}
public void addPropertyAccessor(PropertyAccessor accessor) {
ensurePropertyAccessorsInitialized();
this.propertyAccessors.add(this.propertyAccessors.size() - 1, accessor);
}
public boolean removePropertyAccessor(PropertyAccessor accessor) {
return this.propertyAccessors.remove(accessor);
}
public void setPropertyAccessors(List<PropertyAccessor> propertyAccessors) {
this.propertyAccessors = propertyAccessors;
}
@Override
public List<PropertyAccessor> getPropertyAccessors() {
ensurePropertyAccessorsInitialized();
return this.propertyAccessors;
}
public void setTypeLocator(TypeLocator typeLocator) {
Assert.notNull(typeLocator, "TypeLocator must not be null");
this.typeLocator = typeLocator;
@@ -236,56 +235,49 @@ public class StandardEvaluationContext implements EvaluationContext {
* @throws IllegalStateException if the {@link ReflectiveMethodResolver} is not in use
*/
public void registerMethodFilter(Class<?> type, MethodFilter filter) throws IllegalStateException {
ensureMethodResolversInitialized();
if (this.reflectiveMethodResolver != null) {
this.reflectiveMethodResolver.registerMethodFilter(type, filter);
}
else {
throw new IllegalStateException("Method filter cannot be set as the reflective method resolver is not in use");
initMethodResolvers();
ReflectiveMethodResolver resolver = this.reflectiveMethodResolver;
if (resolver == null) {
throw new IllegalStateException(
"Method filter cannot be set as the reflective method resolver is not in use");
}
resolver.registerMethodFilter(type, filter);
}
private void ensurePropertyAccessorsInitialized() {
if (this.propertyAccessors == null) {
initializePropertyAccessors();
private List<PropertyAccessor> initPropertyAccessors() {
List<PropertyAccessor> accessors = this.propertyAccessors;
if (accessors == null) {
accessors = new ArrayList<>(5);
accessors.add(new ReflectivePropertyAccessor());
this.propertyAccessors = accessors;
}
return accessors;
}
private synchronized void initializePropertyAccessors() {
if (this.propertyAccessors == null) {
List<PropertyAccessor> defaultAccessors = new ArrayList<>();
defaultAccessors.add(new ReflectivePropertyAccessor());
this.propertyAccessors = defaultAccessors;
private List<ConstructorResolver> initConstructorResolvers() {
List<ConstructorResolver> resolvers = this.constructorResolvers;
if (resolvers == null) {
resolvers = new ArrayList<>(1);
resolvers.add(new ReflectiveConstructorResolver());
this.constructorResolvers = resolvers;
}
return resolvers;
}
private void ensureMethodResolversInitialized() {
if (this.methodResolvers == null) {
initializeMethodResolvers();
}
}
private synchronized void initializeMethodResolvers() {
if (this.methodResolvers == null) {
List<MethodResolver> defaultResolvers = new ArrayList<>();
private List<MethodResolver> initMethodResolvers() {
List<MethodResolver> resolvers = this.methodResolvers;
if (resolvers == null) {
resolvers = new ArrayList<>(1);
this.reflectiveMethodResolver = new ReflectiveMethodResolver();
defaultResolvers.add(this.reflectiveMethodResolver);
this.methodResolvers = defaultResolvers;
resolvers.add(this.reflectiveMethodResolver);
this.methodResolvers = resolvers;
}
return resolvers;
}
private void ensureConstructorResolversInitialized() {
if (this.constructorResolvers == null) {
initializeConstructorResolvers();
}
}
private synchronized void initializeConstructorResolvers() {
if (this.constructorResolvers == null) {
List<ConstructorResolver> defaultResolvers = new ArrayList<>();
defaultResolvers.add(new ReflectiveConstructorResolver());
this.constructorResolvers = defaultResolvers;
}
private static <T> void addBeforeDefault(List<T> resolvers, T resolver) {
resolvers.add(resolvers.size() - 1, resolver);
}
}

View File

@@ -38,6 +38,7 @@ import org.springframework.util.ClassUtils;
*/
public class StandardTypeLocator implements TypeLocator {
@Nullable
private final ClassLoader classLoader;
private final List<String> knownPackagePrefixes = new LinkedList<>();

View File

@@ -1768,6 +1768,7 @@ public class SpelReproTests extends AbstractExpressionTests {
};
}
});
result = spel.getValue(context);
assertNotNull(result);
assertTrue(result.getClass().isArray());