Skip to content

Commit 888e501

Browse files
committed
Polish SpEL Javadocs and internals
1 parent 1080c14 commit 888e501

File tree

13 files changed

+194
-172
lines changed

13 files changed

+194
-172
lines changed

spring-expression/src/main/java/org/springframework/expression/common/CompositeStringExpression.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public String getValue() throws EvaluationException {
8080
@Override
8181
@Nullable
8282
public <T> T getValue(@Nullable Class<T> expectedResultType) throws EvaluationException {
83-
Object value = getValue();
83+
String value = getValue();
8484
return ExpressionUtils.convertTypedValue(null, new TypedValue(value), expectedResultType);
8585
}
8686

@@ -99,7 +99,7 @@ public String getValue(@Nullable Object rootObject) throws EvaluationException {
9999
@Override
100100
@Nullable
101101
public <T> T getValue(@Nullable Object rootObject, @Nullable Class<T> desiredResultType) throws EvaluationException {
102-
Object value = getValue(rootObject);
102+
String value = getValue(rootObject);
103103
return ExpressionUtils.convertTypedValue(null, new TypedValue(value), desiredResultType);
104104
}
105105

@@ -120,7 +120,7 @@ public String getValue(EvaluationContext context) throws EvaluationException {
120120
public <T> T getValue(EvaluationContext context, @Nullable Class<T> expectedResultType)
121121
throws EvaluationException {
122122

123-
Object value = getValue(context);
123+
String value = getValue(context);
124124
return ExpressionUtils.convertTypedValue(context, new TypedValue(value), expectedResultType);
125125
}
126126

@@ -141,7 +141,7 @@ public String getValue(EvaluationContext context, @Nullable Object rootObject) t
141141
public <T> T getValue(EvaluationContext context, @Nullable Object rootObject, @Nullable Class<T> desiredResultType)
142142
throws EvaluationException {
143143

144-
Object value = getValue(context,rootObject);
144+
String value = getValue(context,rootObject);
145145
return ExpressionUtils.convertTypedValue(context, new TypedValue(value), desiredResultType);
146146
}
147147

spring-expression/src/main/java/org/springframework/expression/common/LiteralExpression.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public String getValue() {
6464
@Override
6565
@Nullable
6666
public <T> T getValue(@Nullable Class<T> expectedResultType) throws EvaluationException {
67-
Object value = getValue();
67+
String value = getValue();
6868
return ExpressionUtils.convertTypedValue(null, new TypedValue(value), expectedResultType);
6969
}
7070

@@ -76,7 +76,7 @@ public String getValue(@Nullable Object rootObject) {
7676
@Override
7777
@Nullable
7878
public <T> T getValue(@Nullable Object rootObject, @Nullable Class<T> desiredResultType) throws EvaluationException {
79-
Object value = getValue(rootObject);
79+
String value = getValue(rootObject);
8080
return ExpressionUtils.convertTypedValue(null, new TypedValue(value), desiredResultType);
8181
}
8282

@@ -87,10 +87,8 @@ public String getValue(EvaluationContext context) {
8787

8888
@Override
8989
@Nullable
90-
public <T> T getValue(EvaluationContext context, @Nullable Class<T> expectedResultType)
91-
throws EvaluationException {
92-
93-
Object value = getValue(context);
90+
public <T> T getValue(EvaluationContext context, @Nullable Class<T> expectedResultType) throws EvaluationException {
91+
String value = getValue(context);
9492
return ExpressionUtils.convertTypedValue(context, new TypedValue(value), expectedResultType);
9593
}
9694

@@ -104,7 +102,7 @@ public String getValue(EvaluationContext context, @Nullable Object rootObject) t
104102
public <T> T getValue(EvaluationContext context, @Nullable Object rootObject, @Nullable Class<T> desiredResultType)
105103
throws EvaluationException {
106104

107-
Object value = getValue(context, rootObject);
105+
String value = getValue(context, rootObject);
108106
return ExpressionUtils.convertTypedValue(context, new TypedValue(value), desiredResultType);
109107
}
110108

spring-expression/src/main/java/org/springframework/expression/spel/ast/FormatHelper.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@
2121

2222
import org.springframework.core.convert.TypeDescriptor;
2323
import org.springframework.lang.Nullable;
24-
import org.springframework.util.ClassUtils;
2524

2625
/**
2726
* Utility methods (formatters, etc) used during parsing and evaluation.
@@ -51,10 +50,9 @@ static String formatMethodForMessage(String name, List<TypeDescriptor> argumentT
5150
* <p>A String array will have the formatted name "java.lang.String[]".
5251
* @param clazz the Class whose name is to be formatted
5352
* @return a formatted String suitable for message inclusion
54-
* @see ClassUtils#getQualifiedName(Class)
5553
*/
5654
static String formatClassNameForMessage(@Nullable Class<?> clazz) {
57-
return (clazz != null ? ClassUtils.getQualifiedName(clazz) : "null");
55+
return (clazz != null ? clazz.getTypeName() : "null");
5856
}
5957

6058
}

spring-expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java

Lines changed: 44 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -39,16 +39,21 @@
3939
import org.springframework.util.ReflectionUtils;
4040

4141
/**
42-
* A function reference is of the form "#someFunction(a,b,c)". Functions may be defined
43-
* in the context prior to the expression being evaluated. Functions may also be static
44-
* Java methods, registered in the context prior to invocation of the expression.
42+
* A function reference is of the form "#someFunction(a,b,c)".
4543
*
46-
* <p>Functions are very simplistic. The arguments are not part of the definition
47-
* (right now), so the names must be unique.
44+
* <p>Functions can be either a {@link Method} (for static Java methods) or a
45+
* {@link MethodHandle} and must be registered in the context prior to evaluation
46+
* of the expression. See the {@code registerFunction()} methods in
47+
* {@link org.springframework.expression.spel.support.StandardEvaluationContext}
48+
* for details.
4849
*
4950
* @author Andy Clement
5051
* @author Juergen Hoeller
52+
* @author Simon Baslé
53+
* @author Sam Brannen
5154
* @since 3.0
55+
* @see org.springframework.expression.spel.support.StandardEvaluationContext#registerFunction(String, Method)
56+
* @see org.springframework.expression.spel.support.StandardEvaluationContext#registerFunction(String, MethodHandle)
5257
*/
5358
public class FunctionReference extends SpelNodeImpl {
5459

@@ -72,39 +77,44 @@ public TypedValue getValueInternal(ExpressionState state) throws EvaluationExcep
7277
if (value == TypedValue.NULL) {
7378
throw new SpelEvaluationException(getStartPosition(), SpelMessage.FUNCTION_NOT_DEFINED, this.name);
7479
}
75-
Object resolvedValue = value.getValue();
76-
if (resolvedValue instanceof MethodHandle methodHandle) {
80+
Object function = value.getValue();
81+
82+
// Static Java method registered via a Method.
83+
// Note: "javaMethod" cannot be named "method" due to a bug in Checkstyle.
84+
if (function instanceof Method javaMethod) {
7785
try {
78-
return executeFunctionBoundMethodHandle(state, methodHandle);
86+
return executeFunctionViaMethod(state, javaMethod);
7987
}
8088
catch (SpelEvaluationException ex) {
8189
ex.setPosition(getStartPosition());
8290
throw ex;
8391
}
8492
}
85-
if (!(resolvedValue instanceof Method function)) {
86-
// Possibly a static Java method registered as a function
87-
throw new SpelEvaluationException(
88-
SpelMessage.FUNCTION_REFERENCE_CANNOT_BE_INVOKED, this.name, value.getClass());
89-
}
9093

91-
try {
92-
return executeFunctionJLRMethod(state, function);
93-
}
94-
catch (SpelEvaluationException ex) {
95-
ex.setPosition(getStartPosition());
96-
throw ex;
94+
// Function registered via a MethodHandle.
95+
if (function instanceof MethodHandle methodHandle) {
96+
try {
97+
return executeFunctionViaMethodHandle(state, methodHandle);
98+
}
99+
catch (SpelEvaluationException ex) {
100+
ex.setPosition(getStartPosition());
101+
throw ex;
102+
}
97103
}
104+
105+
// Neither a Method nor a MethodHandle?
106+
throw new SpelEvaluationException(
107+
SpelMessage.FUNCTION_REFERENCE_CANNOT_BE_INVOKED, this.name, value.getClass());
98108
}
99109

100110
/**
101-
* Execute a function represented as a {@code java.lang.reflect.Method}.
111+
* Execute a function represented as a {@link Method}.
102112
* @param state the expression evaluation state
103113
* @param method the method to invoke
104114
* @return the return value of the invoked Java method
105115
* @throws EvaluationException if there is any problem invoking the method
106116
*/
107-
private TypedValue executeFunctionJLRMethod(ExpressionState state, Method method) throws EvaluationException {
117+
private TypedValue executeFunctionViaMethod(ExpressionState state, Method method) throws EvaluationException {
108118
Object[] functionArgs = getArguments(state);
109119

110120
if (!method.isVarArgs()) {
@@ -151,35 +161,33 @@ private TypedValue executeFunctionJLRMethod(ExpressionState state, Method method
151161
}
152162

153163
/**
154-
* Execute a function represented as {@code java.lang.invoke.MethodHandle}.
155-
* Method types that take no arguments (fully bound handles or static methods
156-
* with no parameters) can use {@code #invoke()} which is the most efficient.
157-
* Otherwise, {@code #invokeWithArguments)} is used.
164+
* Execute a function represented as {@link MethodHandle}.
165+
* <p>Method types that take no arguments (fully bound handles or static methods
166+
* with no parameters) can use {@link MethodHandle#invoke()} which is the most
167+
* efficient. Otherwise, {@link MethodHandle#invokeWithArguments()} is used.
158168
* @param state the expression evaluation state
159-
* @param methodHandle the method to invoke
169+
* @param methodHandle the method handle to invoke
160170
* @return the return value of the invoked Java method
161171
* @throws EvaluationException if there is any problem invoking the method
162172
* @since 6.1
163173
*/
164-
private TypedValue executeFunctionBoundMethodHandle(ExpressionState state, MethodHandle methodHandle) throws EvaluationException {
174+
private TypedValue executeFunctionViaMethodHandle(ExpressionState state, MethodHandle methodHandle) throws EvaluationException {
165175
Object[] functionArgs = getArguments(state);
166176
MethodType declaredParams = methodHandle.type();
167177
int spelParamCount = functionArgs.length;
168178
int declaredParamCount = declaredParams.parameterCount();
169179

170180
boolean isSuspectedVarargs = declaredParams.lastParameterType().isArray();
171181

172-
if (spelParamCount < declaredParamCount || (spelParamCount > declaredParamCount
173-
&& !isSuspectedVarargs)) {
174-
//incorrect number, including more arguments and not a vararg
182+
if (spelParamCount < declaredParamCount || (spelParamCount > declaredParamCount && !isSuspectedVarargs)) {
183+
// incorrect number, including more arguments and not a vararg
184+
// perhaps a subset of arguments was provided but the MethodHandle wasn't bound?
175185
throw new SpelEvaluationException(SpelMessage.INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNCTION,
176186
functionArgs.length, declaredParamCount);
177-
//perhaps a subset of arguments was provided but the MethodHandle wasn't bound?
178187
}
179188

180189
// simplest case: the MethodHandle is fully bound or represents a static method with no params:
181190
if (declaredParamCount == 0) {
182-
//note we consider MethodHandles not compilable
183191
try {
184192
return new TypedValue(methodHandle.invoke());
185193
}
@@ -188,6 +196,7 @@ private TypedValue executeFunctionBoundMethodHandle(ExpressionState state, Metho
188196
this.name, ex.getMessage());
189197
}
190198
finally {
199+
// Note: we consider MethodHandles not compilable
191200
this.exitTypeDescriptor = null;
192201
this.method = null;
193202
}
@@ -202,12 +211,11 @@ private TypedValue executeFunctionBoundMethodHandle(ExpressionState state, Metho
202211
ReflectionHelper.convertAllMethodHandleArguments(converter, functionArgs, methodHandle, varArgPosition);
203212

204213
if (isSuspectedVarargs && declaredParamCount == 1) {
205-
//we only repack the varargs if it is the ONLY argument
214+
// we only repack the varargs if it is the ONLY argument
206215
functionArgs = ReflectionHelper.setupArgumentsForVarargsInvocation(
207216
methodHandle.type().parameterArray(), functionArgs);
208217
}
209218

210-
//note we consider MethodHandles not compilable
211219
try {
212220
return new TypedValue(methodHandle.invokeWithArguments(functionArgs));
213221
}
@@ -216,6 +224,7 @@ private TypedValue executeFunctionBoundMethodHandle(ExpressionState state, Metho
216224
this.name, ex.getMessage());
217225
}
218226
finally {
227+
// Note: we consider MethodHandles not compilable
219228
this.exitTypeDescriptor = null;
220229
this.method = null;
221230
}

spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java

Lines changed: 41 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -180,12 +180,12 @@ else if (target instanceof Collection<?> collection) {
180180
}
181181
else {
182182
this.indexedType = IndexedType.STRING;
183-
return new StringIndexingLValue((String) target, idx, targetDescriptor);
183+
return new StringIndexingValueRef((String) target, idx, targetDescriptor);
184184
}
185185
}
186186

187187
// Try and treat the index value as a property of the context object
188-
// TODO: could call the conversion service to convert the value to a String
188+
// TODO Could call the conversion service to convert the value to a String
189189
TypeDescriptor valueType = indexValue.getTypeDescriptor();
190190
if (valueType != null && String.class == valueType.getType()) {
191191
this.indexedType = IndexedType.OBJECT;
@@ -226,41 +226,42 @@ public void generateCode(MethodVisitor mv, CodeFlow cf) {
226226
}
227227

228228
if (this.indexedType == IndexedType.ARRAY) {
229-
int insn;
230-
if ("D".equals(this.exitTypeDescriptor)) {
231-
mv.visitTypeInsn(CHECKCAST, "[D");
232-
insn = DALOAD;
233-
}
234-
else if ("F".equals(this.exitTypeDescriptor)) {
235-
mv.visitTypeInsn(CHECKCAST, "[F");
236-
insn = FALOAD;
237-
}
238-
else if ("J".equals(this.exitTypeDescriptor)) {
239-
mv.visitTypeInsn(CHECKCAST, "[J");
240-
insn = LALOAD;
241-
}
242-
else if ("I".equals(this.exitTypeDescriptor)) {
243-
mv.visitTypeInsn(CHECKCAST, "[I");
244-
insn = IALOAD;
245-
}
246-
else if ("S".equals(this.exitTypeDescriptor)) {
247-
mv.visitTypeInsn(CHECKCAST, "[S");
248-
insn = SALOAD;
249-
}
250-
else if ("B".equals(this.exitTypeDescriptor)) {
251-
mv.visitTypeInsn(CHECKCAST, "[B");
252-
insn = BALOAD;
253-
}
254-
else if ("C".equals(this.exitTypeDescriptor)) {
255-
mv.visitTypeInsn(CHECKCAST, "[C");
256-
insn = CALOAD;
257-
}
258-
else {
259-
mv.visitTypeInsn(CHECKCAST, "["+ this.exitTypeDescriptor +
260-
(CodeFlow.isPrimitiveArray(this.exitTypeDescriptor) ? "" : ";"));
261-
//depthPlusOne(exitTypeDescriptor)+"Ljava/lang/Object;");
262-
insn = AALOAD;
263-
}
229+
int insn = switch (this.exitTypeDescriptor) {
230+
case "D" -> {
231+
mv.visitTypeInsn(CHECKCAST, "[D");
232+
yield DALOAD;
233+
}
234+
case "F" -> {
235+
mv.visitTypeInsn(CHECKCAST, "[F");
236+
yield FALOAD;
237+
}
238+
case "J" -> {
239+
mv.visitTypeInsn(CHECKCAST, "[J");
240+
yield LALOAD;
241+
}
242+
case "I" -> {
243+
mv.visitTypeInsn(CHECKCAST, "[I");
244+
yield IALOAD;
245+
}
246+
case "S" -> {
247+
mv.visitTypeInsn(CHECKCAST, "[S");
248+
yield SALOAD;
249+
}
250+
case "B" -> {
251+
mv.visitTypeInsn(CHECKCAST, "[B");
252+
yield BALOAD;
253+
}
254+
case "C" -> {
255+
mv.visitTypeInsn(CHECKCAST, "[C");
256+
yield CALOAD;
257+
}
258+
default -> {
259+
mv.visitTypeInsn(CHECKCAST, "["+ this.exitTypeDescriptor +
260+
(CodeFlow.isPrimitiveArray(this.exitTypeDescriptor) ? "" : ";"));
261+
yield AALOAD;
262+
}
263+
};
264+
264265
SpelNodeImpl index = this.children[0];
265266
cf.enterCompilationScope();
266267
index.generateCode(mv, cf);
@@ -325,6 +326,7 @@ else if (this.indexedType == IndexedType.OBJECT) {
325326

326327
@Override
327328
public String toStringAST() {
329+
// TODO Since we do not support multidimensional arrays, we should be able to return: "[" + getChild(0).toStringAST() + "]"
328330
StringJoiner sj = new StringJoiner(",", "[", "]");
329331
for (int i = 0; i < getChildCount(); i++) {
330332
sj.add(getChild(i).toStringAST());
@@ -744,15 +746,15 @@ public boolean isWritable() {
744746
}
745747

746748

747-
private class StringIndexingLValue implements ValueRef {
749+
private class StringIndexingValueRef implements ValueRef {
748750

749751
private final String target;
750752

751753
private final int index;
752754

753755
private final TypeDescriptor typeDescriptor;
754756

755-
public StringIndexingLValue(String target, int index, TypeDescriptor typeDescriptor) {
757+
public StringIndexingValueRef(String target, int index, TypeDescriptor typeDescriptor) {
756758
this.target = target;
757759
this.index = index;
758760
this.typeDescriptor = typeDescriptor;

0 commit comments

Comments
 (0)