diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/VariableReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/VariableReference.java
index 3b8af0d32b..9124038496 100644
--- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/VariableReference.java
+++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/VariableReference.java
@@ -29,7 +29,8 @@ import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.lang.Nullable;
/**
- * Represents a variable reference — for example, {@code #someVar}.
+ * Represents a variable reference — for example, {@code #root}, {@code #this},
+ * {@code #someVar}, etc.
*
* @author Andy Clement
* @author Sam Brannen
@@ -37,10 +38,11 @@ import org.springframework.lang.Nullable;
*/
public class VariableReference extends SpelNodeImpl {
- // Well known variables:
- private static final String THIS = "this"; // currently active context object
+ /** Currently active context object. */
+ private static final String THIS = "this";
- private static final String ROOT = "root"; // root context object
+ /** Root context object. */
+ private static final String ROOT = "root";
private final String name;
@@ -54,41 +56,56 @@ public class VariableReference extends SpelNodeImpl {
@Override
public ValueRef getValueRef(ExpressionState state) throws SpelEvaluationException {
- if (this.name.equals(THIS)) {
+ if (THIS.equals(this.name)) {
return new ValueRef.TypedValueHolderValueRef(state.getActiveContextObject(), this);
}
- if (this.name.equals(ROOT)) {
+ if (ROOT.equals(this.name)) {
return new ValueRef.TypedValueHolderValueRef(state.getRootContextObject(), this);
}
TypedValue result = state.lookupVariable(this.name);
- // a null value will mean either the value was null or the variable was not found
+ // A null value in the returned VariableRef will mean either the value was
+ // null or the variable was not found.
return new VariableRef(this.name, result, state.getEvaluationContext());
}
@Override
public TypedValue getValueInternal(ExpressionState state) throws SpelEvaluationException {
- if (this.name.equals(THIS)) {
+ if (THIS.equals(this.name)) {
return state.getActiveContextObject();
}
- if (this.name.equals(ROOT)) {
+ if (ROOT.equals(this.name)) {
TypedValue result = state.getRootContextObject();
this.exitTypeDescriptor = CodeFlow.toDescriptorFromObject(result.getValue());
return result;
}
+
TypedValue result = state.lookupVariable(this.name);
- Object value = result.getValue();
+ setExitTypeDescriptor(result.getValue());
+
+ // A null value in the returned TypedValue will mean either the value was
+ // null or the variable was not found.
+ return result;
+ }
+
+ /**
+ * Set the exit type descriptor for the supplied value.
+ *
If the value is {@code null}, we set the exit type descriptor to
+ * {@link Object}.
+ *
If the value's type is not public, {@link #generateCode} would insert
+ * a checkcast to the non-public type in the generated byte code which would
+ * result in an {@link IllegalAccessError} when the compiled byte code is
+ * invoked. Thus, as a preventative measure, we set the exit type descriptor
+ * to {@code Object} in such cases. If resorting to {@code Object} is not
+ * sufficient, we could consider traversing the hierarchy to find the first
+ * public type.
+ */
+ private void setExitTypeDescriptor(@Nullable Object value) {
if (value == null || !Modifier.isPublic(value.getClass().getModifiers())) {
- // If the type is not public then when generateCode produces a checkcast to it
- // then an IllegalAccessError will occur.
- // If resorting to Object isn't sufficient, the hierarchy could be traversed for
- // the first public type.
this.exitTypeDescriptor = "Ljava/lang/Object";
}
else {
this.exitTypeDescriptor = CodeFlow.toDescriptorFromObject(value);
}
- // a null value will mean either the value was null or the variable was not found
- return result;
}
@Override
@@ -105,7 +122,7 @@ public class VariableReference extends SpelNodeImpl {
@Override
public boolean isWritable(ExpressionState expressionState) throws SpelEvaluationException {
- return !(this.name.equals(THIS) || this.name.equals(ROOT));
+ return !(THIS.equals(this.name) || ROOT.equals(this.name));
}
@Override
@@ -115,13 +132,14 @@ public class VariableReference extends SpelNodeImpl {
@Override
public void generateCode(MethodVisitor mv, CodeFlow cf) {
- if (this.name.equals(ROOT)) {
- mv.visitVarInsn(ALOAD,1);
+ if (ROOT.equals(this.name)) {
+ mv.visitVarInsn(ALOAD, 1);
}
else {
mv.visitVarInsn(ALOAD, 2);
mv.visitLdcInsn(this.name);
- mv.visitMethodInsn(INVOKEINTERFACE, "org/springframework/expression/EvaluationContext", "lookupVariable", "(Ljava/lang/String;)Ljava/lang/Object;",true);
+ mv.visitMethodInsn(INVOKEINTERFACE, "org/springframework/expression/EvaluationContext",
+ "lookupVariable", "(Ljava/lang/String;)Ljava/lang/Object;", true);
}
CodeFlow.insertCheckCast(mv, this.exitTypeDescriptor);
cf.pushDescriptor(this.exitTypeDescriptor);
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/CompilableMapAccessor.java b/spring-expression/src/test/java/org/springframework/expression/spel/CompilableMapAccessor.java
new file mode 100644
index 0000000000..3557dfa98d
--- /dev/null
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/CompilableMapAccessor.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2002-2024 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.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.expression.spel;
+
+import java.util.Map;
+
+import org.springframework.asm.MethodVisitor;
+import org.springframework.expression.AccessException;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.TypedValue;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+
+/**
+ * This is a local COPY of {@link org.springframework.context.expression.MapAccessor}.
+ *
+ * @author Juergen Hoeller
+ * @author Andy Clement
+ * @since 4.1
+ */
+class CompilableMapAccessor implements CompilablePropertyAccessor {
+
+ @Override
+ public Class>[] getSpecificTargetClasses() {
+ return new Class>[] {Map.class};
+ }
+
+ @Override
+ public boolean canRead(EvaluationContext context, @Nullable Object target, String name) throws AccessException {
+ return (target instanceof Map, ?> map && map.containsKey(name));
+ }
+
+ @Override
+ public TypedValue read(EvaluationContext context, @Nullable Object target, String name) throws AccessException {
+ Assert.state(target instanceof Map, "Target must be of type Map");
+ Map, ?> map = (Map, ?>) target;
+ Object value = map.get(name);
+ if (value == null && !map.containsKey(name)) {
+ throw new MapAccessException(name);
+ }
+ return new TypedValue(value);
+ }
+
+ @Override
+ public boolean canWrite(EvaluationContext context, @Nullable Object target, String name) throws AccessException {
+ return true;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void write(EvaluationContext context, @Nullable Object target, String name, @Nullable Object newValue)
+ throws AccessException {
+
+ Assert.state(target instanceof Map, "Target must be a Map");
+ Map map = (Map) target;
+ map.put(name, newValue);
+ }
+
+ @Override
+ public boolean isCompilable() {
+ return true;
+ }
+
+ @Override
+ public Class> getPropertyType() {
+ return Object.class;
+ }
+
+ @Override
+ public void generateCode(String propertyName, MethodVisitor mv, CodeFlow cf) {
+ String descriptor = cf.lastDescriptor();
+ if (descriptor == null || !descriptor.equals("Ljava/util/Map")) {
+ if (descriptor == null) {
+ cf.loadTarget(mv);
+ }
+ CodeFlow.insertCheckCast(mv, "Ljava/util/Map");
+ }
+ mv.visitLdcInsn(propertyName);
+ mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "get","(Ljava/lang/Object;)Ljava/lang/Object;",true);
+ }
+
+
+ /**
+ * Exception thrown from {@code read} in order to reset a cached
+ * PropertyAccessor, allowing other accessors to have a try.
+ */
+ @SuppressWarnings("serial")
+ private static class MapAccessException extends AccessException {
+
+ private final String key;
+
+ public MapAccessException(String key) {
+ super("");
+ this.key = key;
+ }
+
+ @Override
+ public String getMessage() {
+ return "Map does not contain a value for key '" + this.key + "'";
+ }
+ }
+
+}
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java
index 5cd5064bb2..da7b542afe 100644
--- a/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java
@@ -20,6 +20,8 @@ import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -27,13 +29,14 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.asm.MethodVisitor;
-import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.TypedValue;
@@ -47,6 +50,7 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.expression.spel.testdata.PersonInOtherPackage;
+import static java.util.stream.Collectors.joining;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatException;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -55,11 +59,64 @@ import static org.assertj.core.api.InstanceOfAssertFactories.BOOLEAN;
import static org.springframework.expression.spel.standard.SpelExpressionTestUtils.assertIsCompiled;
/**
- * Checks SpelCompiler behavior. This should cover compilation all compiled node types.
+ * Checks {@link org.springframework.expression.spel.standard.SpelCompiler} behavior.
+ *
+ * This should cover compilation of all compiled node types.
+ *
+ *
Compiled nodes:
+ *
+ * TypeReference
+ * OperatorInstanceOf
+ * StringLiteral
+ * NullLiteral
+ * RealLiteral
+ * IntLiteral
+ * LongLiteral
+ * BooleanLiteral
+ * FloatLiteral
+ * OpOr
+ * OpAnd
+ * OperatorNot
+ * Ternary
+ * Elvis
+ * VariableReference
+ * OpLt
+ * OpLe
+ * OpGt
+ * OpGe
+ * OpEq
+ * OpNe
+ * OpPlus
+ * OpMinus
+ * OpMultiply
+ * OpDivide
+ * MethodReference
+ * PropertyOrFieldReference
+ * Indexer
+ * CompoundExpression
+ * ConstructorReference
+ * FunctionReference
+ * InlineList
+ * OpModulus
+ *
+ *
Not yet compiled (some may never need to be):
+ *
+ * Assign
+ * BeanReference
+ * Identifier
+ * OpDec
+ * OpBetween
+ * OpMatches
+ * OpPower
+ * OpInc
+ * Projection
+ * QualifiedId
+ * Selection
*
* @author Andy Clement
* @author Sam Brannen
* @since 4.1
+ * @see org.springframework.expression.spel.standard.SpelCompilerTests
*/
public class SpelCompilationCoverageTests extends AbstractExpressionTests {
@@ -67,72 +124,463 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
* TODO Potential optimizations for SpEL compilation:
*
* - OpMinus with a single literal operand could be treated as a negative literal. Will save a
- * pointless loading of 0 and then a subtract instruction in code gen.
- * - allow other accessors/resolvers to participate in compilation and create their own code
+ * pointless loading of 0 and then a subtract instruction in code geneneration.
+ *
+ * - allow other accessors/resolvers to participate in compilation and create their own code.
+ *
* - A TypeReference followed by (what ends up as) a static method invocation can really skip
- * code gen for the TypeReference since once that is used to locate the method it is not
+ * code generation for the TypeReference since once that is used to locate the method it is not
* used again.
+ *
* - The opEq implementation is quite basic. It will compare numbers of the same type (allowing
* them to be their boxed or unboxed variants) or compare object references. It does not
* compile expressions where numbers are of different types or when objects implement
* Comparable.
- *
- * Compiled nodes:
- *
- * TypeReference
- * OperatorInstanceOf
- * StringLiteral
- * NullLiteral
- * RealLiteral
- * IntLiteral
- * LongLiteral
- * BooleanLiteral
- * FloatLiteral
- * OpOr
- * OpAnd
- * OperatorNot
- * Ternary
- * Elvis
- * VariableReference
- * OpLt
- * OpLe
- * OpGt
- * OpGe
- * OpEq
- * OpNe
- * OpPlus
- * OpMinus
- * OpMultiply
- * OpDivide
- * MethodReference
- * PropertyOrFieldReference
- * Indexer
- * CompoundExpression
- * ConstructorReference
- * FunctionReference
- * InlineList
- * OpModulus
- *
- * Not yet compiled (some may never need to be):
- * Assign
- * BeanReference
- * Identifier
- * OpDec
- * OpBetween
- * OpMatches
- * OpPower
- * OpInc
- * Projection
- * QualifiedId
- * Selection
*/
-
private Expression expression;
private SpelNodeImpl ast;
+ @Nested
+ class IndexingTests {
+
+ @Test
+ void indexIntoPrimitiveShortArray() {
+ short[] shorts = { (short) 33, (short) 44, (short) 55 };
+
+ expression = parser.parseExpression("[2]");
+
+ assertThat(expression.getValue(shorts)).isEqualTo((short) 55);
+ assertCanCompile(expression);
+ assertThat(expression.getValue(shorts)).isEqualTo((short) 55);
+ assertThat(getAst().getExitDescriptor()).isEqualTo("S");
+ }
+
+ @Test
+ void indexIntoPrimitiveByteArray() {
+ byte[] bytes = { (byte) 2, (byte) 3, (byte) 4 };
+
+ expression = parser.parseExpression("[2]");
+
+ assertThat(expression.getValue(bytes)).isEqualTo((byte) 4);
+ assertCanCompile(expression);
+ assertThat(expression.getValue(bytes)).isEqualTo((byte) 4);
+ assertThat(getAst().getExitDescriptor()).isEqualTo("B");
+ }
+
+ @Test
+ void indexIntoPrimitiveIntArray() {
+ int[] ints = { 8, 9, 10 };
+
+ expression = parser.parseExpression("[2]");
+
+ assertThat(expression.getValue(ints)).isEqualTo(10);
+ assertCanCompile(expression);
+ assertThat(expression.getValue(ints)).isEqualTo(10);
+ assertThat(getAst().getExitDescriptor()).isEqualTo("I");
+ }
+
+ @Test
+ void indexIntoPrimitiveLongArray() {
+ long[] longs = { 2L, 3L, 4L };
+
+ expression = parser.parseExpression("[0]");
+
+ assertThat(expression.getValue(longs)).isEqualTo(2L);
+ assertCanCompile(expression);
+ assertThat(expression.getValue(longs)).isEqualTo(2L);
+ assertThat(getAst().getExitDescriptor()).isEqualTo("J");
+ }
+
+ @Test
+ void indexIntoPrimitiveFloatArray() {
+ float[] floats = { 6.0f, 7.0f, 8.0f };
+
+ expression = parser.parseExpression("[0]");
+
+ assertThat(expression.getValue(floats)).isEqualTo(6.0f);
+ assertCanCompile(expression);
+ assertThat(expression.getValue(floats)).isEqualTo(6.0f);
+ assertThat(getAst().getExitDescriptor()).isEqualTo("F");
+ }
+
+ @Test
+ void indexIntoPrimitiveDoubleArray() {
+ double[] doubles = { 3.0d, 4.0d, 5.0d };
+
+ expression = parser.parseExpression("[1]");
+
+ assertThat(expression.getValue(doubles)).isEqualTo(4.0d);
+ assertCanCompile(expression);
+ assertThat(expression.getValue(doubles)).isEqualTo(4.0d);
+ assertThat(getAst().getExitDescriptor()).isEqualTo("D");
+ }
+
+ @Test
+ void indexIntoPrimitiveCharArray() {
+ char[] chars = { 'a', 'b', 'c' };
+
+ expression = parser.parseExpression("[1]");
+
+ assertThat(expression.getValue(chars)).isEqualTo('b');
+ assertCanCompile(expression);
+ assertThat(expression.getValue(chars)).isEqualTo('b');
+ assertThat(getAst().getExitDescriptor()).isEqualTo("C");
+ }
+
+ @Test
+ void indexIntoStringArray() {
+ String[] strings = { "a", "b", "c" };
+
+ expression = parser.parseExpression("[0]");
+
+ assertThat(expression.getValue(strings)).isEqualTo("a");
+ assertCanCompile(expression);
+ assertThat(expression.getValue(strings)).isEqualTo("a");
+ assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/String");
+ }
+
+ @Test
+ void indexIntoNumberArray() {
+ Number[] numbers = { 2, 8, 9 };
+
+ expression = parser.parseExpression("[1]");
+
+ assertThat(expression.getValue(numbers)).isEqualTo(8);
+ assertCanCompile(expression);
+ assertThat(expression.getValue(numbers)).isEqualTo(8);
+ assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Number");
+ }
+
+ @Test
+ void indexInto2DPrimitiveIntArray() {
+ int[][] array = new int[][] {
+ { 1, 2, 3 },
+ { 4, 5, 6 }
+ };
+
+ expression = parser.parseExpression("[1]");
+
+ assertThat(stringify(expression.getValue(array))).isEqualTo("4 5 6");
+ assertCanCompile(expression);
+ assertThat(stringify(expression.getValue(array))).isEqualTo("4 5 6");
+ assertThat(getAst().getExitDescriptor()).isEqualTo("[I");
+
+ expression = parser.parseExpression("[1][2]");
+
+ assertThat(stringify(expression.getValue(array))).isEqualTo("6");
+ assertCanCompile(expression);
+ assertThat(stringify(expression.getValue(array))).isEqualTo("6");
+ assertThat(getAst().getExitDescriptor()).isEqualTo("I");
+ }
+
+ @Test
+ void indexInto2DStringArray() {
+ String[][] array = new String[][] {
+ { "a", "b", "c" },
+ { "d", "e", "f" }
+ };
+
+ expression = parser.parseExpression("[1]");
+
+ assertThat(stringify(expression.getValue(array))).isEqualTo("d e f");
+ assertCanCompile(expression);
+ assertThat(getAst().getExitDescriptor()).isEqualTo("[Ljava/lang/String");
+ assertThat(stringify(expression.getValue(array))).isEqualTo("d e f");
+ assertThat(getAst().getExitDescriptor()).isEqualTo("[Ljava/lang/String");
+
+ expression = parser.parseExpression("[1][2]");
+
+ assertThat(stringify(expression.getValue(array))).isEqualTo("f");
+ assertCanCompile(expression);
+ assertThat(stringify(expression.getValue(array))).isEqualTo("f");
+ assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/String");
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ void indexIntoArrayOfListOfString() {
+ List[] array = new List[] {
+ List.of("a", "b", "c"),
+ List.of("d", "e", "f")
+ };
+
+ expression = parser.parseExpression("[1]");
+
+ assertThat(stringify(expression.getValue(array))).isEqualTo("d e f");
+ assertCanCompile(expression);
+ assertThat(stringify(expression.getValue(array))).isEqualTo("d e f");
+ assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/util/List");
+
+ expression = parser.parseExpression("[1][2]");
+
+ assertThat(stringify(expression.getValue(array))).isEqualTo("f");
+ assertCanCompile(expression);
+ assertThat(stringify(expression.getValue(array))).isEqualTo("f");
+ assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ void indexIntoArrayOfMap() {
+ Map[] array = new Map[] { Map.of("key", "value1") };
+
+ expression = parser.parseExpression("[0]");
+
+ assertThat(stringify(expression.getValue(array))).isEqualTo("{key=value1}");
+ assertCanCompile(expression);
+ assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/util/Map");
+ assertThat(stringify(expression.getValue(array))).isEqualTo("{key=value1}");
+ assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/util/Map");
+
+ expression = parser.parseExpression("[0]['key']");
+
+ assertThat(stringify(expression.getValue(array))).isEqualTo("value1");
+ assertCanCompile(expression);
+ assertThat(stringify(expression.getValue(array))).isEqualTo("value1");
+ assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
+ }
+
+ @Test
+ void indexIntoListOfString() {
+ List list = List.of("aaa", "bbb", "ccc");
+
+ expression = parser.parseExpression("[1]");
+
+ assertThat(expression.getValue(list)).isEqualTo("bbb");
+ assertCanCompile(expression);
+ assertThat(expression.getValue(list)).isEqualTo("bbb");
+ assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
+ }
+
+ @Test
+ void indexIntoListOfInteger() {
+ List list = List.of(123, 456, 789);
+
+ expression = parser.parseExpression("[2]");
+
+ assertThat(expression.getValue(list)).isEqualTo(789);
+ assertCanCompile(expression);
+ assertThat(expression.getValue(list)).isEqualTo(789);
+ assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
+ }
+
+ @Test
+ void indexIntoListOfStringArray() {
+ List list = List.of(
+ new String[] { "a", "b", "c" },
+ new String[] { "d", "e", "f" }
+ );
+
+ expression = parser.parseExpression("[1]");
+
+ assertThat(stringify(expression.getValue(list))).isEqualTo("d e f");
+ assertCanCompile(expression);
+ assertThat(stringify(expression.getValue(list))).isEqualTo("d e f");
+ assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
+
+ expression = parser.parseExpression("[1][0]");
+
+ assertThat(stringify(expression.getValue(list))).isEqualTo("d");
+ assertCanCompile(expression);
+ assertThat(stringify(expression.getValue(list))).isEqualTo("d");
+ assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/String");
+ }
+
+ @Test
+ void indexIntoListOfIntegerArray() {
+ List list = List.of(
+ new Integer[] { 1, 2, 3 },
+ new Integer[] { 4, 5, 6 }
+ );
+
+ expression = parser.parseExpression("[0]");
+
+ assertThat(stringify(expression.getValue(list))).isEqualTo("1 2 3");
+ assertCanCompile(expression);
+ assertThat(stringify(expression.getValue(list))).isEqualTo("1 2 3");
+ assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
+
+ expression = parser.parseExpression("[0][1]");
+
+ assertThat(expression.getValue(list)).isEqualTo(2);
+ assertCanCompile(expression);
+ assertThat(expression.getValue(list)).isEqualTo(2);
+ assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Integer");
+ }
+
+ @Test
+ void indexIntoListOfListOfString() {
+ List> list = List.of(
+ List.of("a", "b", "c"),
+ List.of("d", "e", "f")
+ );
+
+ expression = parser.parseExpression("[1]");
+
+ assertThat(stringify(expression.getValue(list))).isEqualTo("d e f");
+ assertCanCompile(expression);
+ assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
+ assertThat(stringify(expression.getValue(list))).isEqualTo("d e f");
+ assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
+
+ expression = parser.parseExpression("[1][2]");
+
+ assertThat(stringify(expression.getValue(list))).isEqualTo("f");
+ assertCanCompile(expression);
+ assertThat(stringify(expression.getValue(list))).isEqualTo("f");
+ assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
+ }
+
+ @Test
+ void indexIntoMap() {
+ Map map = Map.of("aaa", 111);
+
+ expression = parser.parseExpression("['aaa']");
+
+ assertThat(expression.getValue(map)).isEqualTo(111);
+ assertCanCompile(expression);
+ assertThat(expression.getValue(map)).isEqualTo(111);
+ assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
+ }
+
+ @Test
+ void indexIntoMapOfListOfString() {
+ Map> map = Map.of("foo", List.of("a", "b", "c"));
+
+ expression = parser.parseExpression("['foo']");
+
+ assertThat(stringify(expression.getValue(map))).isEqualTo("a b c");
+ assertCanCompile(expression);
+ assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
+ assertThat(stringify(expression.getValue(map))).isEqualTo("a b c");
+ assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
+
+ expression = parser.parseExpression("['foo'][2]");
+
+ assertThat(stringify(expression.getValue(map))).isEqualTo("c");
+ assertCanCompile(expression);
+ assertThat(stringify(expression.getValue(map))).isEqualTo("c");
+ assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
+ }
+
+ @Test
+ void indexIntoMapOfPrimitiveIntArray() {
+ Map map = Map.of("foo", new int[] { 1, 2, 3 });
+
+ // map key access
+ expression = parser.parseExpression("['foo']");
+
+ assertThat(stringify(expression.getValue(map))).isEqualTo("1 2 3");
+ assertCanCompile(expression);
+ assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
+ assertThat(stringify(expression.getValue(map))).isEqualTo("1 2 3");
+ assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
+
+ // map key access & array index
+ expression = parser.parseExpression("['foo'][1]");
+
+ assertThat(expression.getValue(map)).isEqualTo(2);
+ assertCanCompile(expression);
+ assertThat(expression.getValue(map)).isEqualTo(2);
+ }
+
+ @Test
+ void indexIntoMapOfPrimitiveIntArrayWithCompilableMapAccessor() {
+ StandardEvaluationContext context = new StandardEvaluationContext();
+ context.addPropertyAccessor(new CompilableMapAccessor());
+
+ // Map map = Map.of("foo", new int[] { 1, 2, 3 });
+ Map map = new HashMap<>();
+ map.put("foo", new int[] { 1, 2, 3 });
+
+ // map key access
+ expression = parser.parseExpression("['foo']");
+
+ assertThat(stringify(expression.getValue(context, map))).isEqualTo("1 2 3");
+ assertCanCompile(expression);
+ assertThat(stringify(expression.getValue(context, map))).isEqualTo("1 2 3");
+ assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
+
+ // custom CompilableMapAccessor via implicit #root & array index
+ expression = parser.parseExpression("foo[1]");
+
+ assertThat(expression.getValue(context, map)).isEqualTo(2);
+ assertCanCompile(expression);
+ assertThat(expression.getValue(context, map)).isEqualTo(2);
+
+ // custom CompilableMapAccessor via explicit #root & array index
+ expression = parser.parseExpression("#root.foo[1]");
+
+ assertThat(expression.getValue(context, map)).isEqualTo(2);
+ assertCanCompile(expression);
+ // TODO If map is created via Map.of(), the following fails with an IllegalAccessError.
+ //
+ // IllegalAccessError: failed to access class java.util.ImmutableCollections$Map1 from class
+ // spel.Ex2774 (java.util.ImmutableCollections$Map1 is in module java.base of loader 'bootstrap';
+ // spel.Ex2774 is in unnamed module of loader
+ // org.springframework.expression.spel.standard.SpelCompiler$ChildClassLoader @359b650b)
+ assertThat(expression.getValue(context, map)).isEqualTo(2);
+
+ // map key access & array index
+ expression = parser.parseExpression("['foo'][2]");
+
+ assertThat(stringify(expression.getValue(context, map))).isEqualTo("3");
+ assertCanCompile(expression);
+ assertThat(stringify(expression.getValue(context, map))).isEqualTo("3");
+ assertThat(getAst().getExitDescriptor()).isEqualTo("I");
+ }
+
+ @Test
+ void indexIntoObject() {
+ TestClass6 tc = new TestClass6();
+
+ // field access
+ expression = parser.parseExpression("['orange']");
+
+ assertThat(expression.getValue(tc)).isEqualTo("value1");
+ assertCanCompile(expression);
+ assertThat(expression.getValue(tc)).isEqualTo("value1");
+ assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/String");
+
+ // field access
+ expression = parser.parseExpression("['peach']");
+
+ assertThat(expression.getValue(tc)).isEqualTo(34L);
+ assertCanCompile(expression);
+ assertThat(expression.getValue(tc)).isEqualTo(34L);
+ assertThat(getAst().getExitDescriptor()).isEqualTo("J");
+
+ // property access (getter)
+ expression = parser.parseExpression("['banana']");
+
+ assertThat(expression.getValue(tc)).isEqualTo("value3");
+ assertCanCompile(expression);
+ assertThat(expression.getValue(tc)).isEqualTo("value3");
+ assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/String");
+ }
+
+ private String stringify(Object object) {
+ Stream extends Object> stream;
+ if (object instanceof Collection> collection) {
+ stream = collection.stream();
+ }
+ else if (object instanceof Object[] objects) {
+ stream = Arrays.stream(objects);
+ }
+ else if (object instanceof int[] ints) {
+ stream = Arrays.stream(ints).mapToObj(Integer::valueOf);
+ }
+ else {
+ return String.valueOf(object);
+ }
+ return stream.map(Object::toString).collect(joining(" "));
+ }
+
+ }
+
@Test
void typeReference() {
expression = parse("T(String)");
@@ -4356,314 +4804,6 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
assertThat(expression.getValue(ctx)).isEqualTo("wibble");
}
- @SuppressWarnings("unchecked")
- @Test
- void indexer() {
- String[] sss = new String[] {"a","b","c"};
- Number[] ns = new Number[] {2,8,9};
- int[] is = new int[] {8,9,10};
- double[] ds = new double[] {3.0d,4.0d,5.0d};
- long[] ls = new long[] {2L,3L,4L};
- short[] ss = new short[] {(short)33,(short)44,(short)55};
- float[] fs = new float[] {6.0f,7.0f,8.0f};
- byte[] bs = new byte[] {(byte)2,(byte)3,(byte)4};
- char[] cs = new char[] {'a','b','c'};
-
- // Access String (reference type) array
- expression = parser.parseExpression("[0]");
- assertThat(expression.getValue(sss)).isEqualTo("a");
- assertCanCompile(expression);
- assertThat(expression.getValue(sss)).isEqualTo("a");
- assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/String");
-
- expression = parser.parseExpression("[1]");
- assertThat(expression.getValue(ns)).isEqualTo(8);
- assertCanCompile(expression);
- assertThat(expression.getValue(ns)).isEqualTo(8);
- assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Number");
-
- // Access int array
- expression = parser.parseExpression("[2]");
- assertThat(expression.getValue(is)).isEqualTo(10);
- assertCanCompile(expression);
- assertThat(expression.getValue(is)).isEqualTo(10);
- assertThat(getAst().getExitDescriptor()).isEqualTo("I");
-
- // Access double array
- expression = parser.parseExpression("[1]");
- assertThat(expression.getValue(ds)).isEqualTo(4.0d);
- assertCanCompile(expression);
- assertThat(expression.getValue(ds)).isEqualTo(4.0d);
- assertThat(getAst().getExitDescriptor()).isEqualTo("D");
-
- // Access long array
- expression = parser.parseExpression("[0]");
- assertThat(expression.getValue(ls)).isEqualTo(2L);
- assertCanCompile(expression);
- assertThat(expression.getValue(ls)).isEqualTo(2L);
- assertThat(getAst().getExitDescriptor()).isEqualTo("J");
-
- // Access short array
- expression = parser.parseExpression("[2]");
- assertThat(expression.getValue(ss)).isEqualTo((short)55);
- assertCanCompile(expression);
- assertThat(expression.getValue(ss)).isEqualTo((short)55);
- assertThat(getAst().getExitDescriptor()).isEqualTo("S");
-
- // Access float array
- expression = parser.parseExpression("[0]");
- assertThat(expression.getValue(fs)).isEqualTo(6.0f);
- assertCanCompile(expression);
- assertThat(expression.getValue(fs)).isEqualTo(6.0f);
- assertThat(getAst().getExitDescriptor()).isEqualTo("F");
-
- // Access byte array
- expression = parser.parseExpression("[2]");
- assertThat(expression.getValue(bs)).isEqualTo((byte)4);
- assertCanCompile(expression);
- assertThat(expression.getValue(bs)).isEqualTo((byte)4);
- assertThat(getAst().getExitDescriptor()).isEqualTo("B");
-
- // Access char array
- expression = parser.parseExpression("[1]");
- assertThat(expression.getValue(cs)).isEqualTo('b');
- assertCanCompile(expression);
- assertThat(expression.getValue(cs)).isEqualTo('b');
- assertThat(getAst().getExitDescriptor()).isEqualTo("C");
-
- // Collections
- List strings = new ArrayList<>();
- strings.add("aaa");
- strings.add("bbb");
- strings.add("ccc");
- expression = parser.parseExpression("[1]");
- assertThat(expression.getValue(strings)).isEqualTo("bbb");
- assertCanCompile(expression);
- assertThat(expression.getValue(strings)).isEqualTo("bbb");
- assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
-
- List ints = new ArrayList<>();
- ints.add(123);
- ints.add(456);
- ints.add(789);
- expression = parser.parseExpression("[2]");
- assertThat(expression.getValue(ints)).isEqualTo(789);
- assertCanCompile(expression);
- assertThat(expression.getValue(ints)).isEqualTo(789);
- assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
-
- // Maps
- Map map1 = new HashMap<>();
- map1.put("aaa", 111);
- map1.put("bbb", 222);
- map1.put("ccc", 333);
- expression = parser.parseExpression("['aaa']");
- assertThat(expression.getValue(map1)).isEqualTo(111);
- assertCanCompile(expression);
- assertThat(expression.getValue(map1)).isEqualTo(111);
- assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
-
- // Object
- TestClass6 tc = new TestClass6();
- expression = parser.parseExpression("['orange']");
- assertThat(expression.getValue(tc)).isEqualTo("value1");
- assertCanCompile(expression);
- assertThat(expression.getValue(tc)).isEqualTo("value1");
- assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/String");
-
- expression = parser.parseExpression("['peach']");
- assertThat(expression.getValue(tc)).isEqualTo(34L);
- assertCanCompile(expression);
- assertThat(expression.getValue(tc)).isEqualTo(34L);
- assertThat(getAst().getExitDescriptor()).isEqualTo("J");
-
- // getter
- expression = parser.parseExpression("['banana']");
- assertThat(expression.getValue(tc)).isEqualTo("value3");
- assertCanCompile(expression);
- assertThat(expression.getValue(tc)).isEqualTo("value3");
- assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/String");
-
- // list of arrays
-
- List listOfStringArrays = new ArrayList<>();
- listOfStringArrays.add(new String[] {"a","b","c"});
- listOfStringArrays.add(new String[] {"d","e","f"});
- expression = parser.parseExpression("[1]");
- assertThat(stringify(expression.getValue(listOfStringArrays))).isEqualTo("d e f");
- assertCanCompile(expression);
- assertThat(stringify(expression.getValue(listOfStringArrays))).isEqualTo("d e f");
- assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
-
- expression = parser.parseExpression("[1][0]");
- assertThat(stringify(expression.getValue(listOfStringArrays))).isEqualTo("d");
- assertCanCompile(expression);
- assertThat(stringify(expression.getValue(listOfStringArrays))).isEqualTo("d");
- assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/String");
-
- List listOfIntegerArrays = new ArrayList<>();
- listOfIntegerArrays.add(new Integer[] {1,2,3});
- listOfIntegerArrays.add(new Integer[] {4,5,6});
- expression = parser.parseExpression("[0]");
- assertThat(stringify(expression.getValue(listOfIntegerArrays))).isEqualTo("1 2 3");
- assertCanCompile(expression);
- assertThat(stringify(expression.getValue(listOfIntegerArrays))).isEqualTo("1 2 3");
- assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
-
- expression = parser.parseExpression("[0][1]");
- assertThat(expression.getValue(listOfIntegerArrays)).isEqualTo(2);
- assertCanCompile(expression);
- assertThat(expression.getValue(listOfIntegerArrays)).isEqualTo(2);
- assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Integer");
-
- // array of lists
- List[] stringArrayOfLists = new ArrayList[2];
- stringArrayOfLists[0] = new ArrayList<>();
- stringArrayOfLists[0].add("a");
- stringArrayOfLists[0].add("b");
- stringArrayOfLists[0].add("c");
- stringArrayOfLists[1] = new ArrayList<>();
- stringArrayOfLists[1].add("d");
- stringArrayOfLists[1].add("e");
- stringArrayOfLists[1].add("f");
- expression = parser.parseExpression("[1]");
- assertThat(stringify(expression.getValue(stringArrayOfLists))).isEqualTo("d e f");
- assertCanCompile(expression);
- assertThat(stringify(expression.getValue(stringArrayOfLists))).isEqualTo("d e f");
- assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/util/ArrayList");
-
- expression = parser.parseExpression("[1][2]");
- assertThat(stringify(expression.getValue(stringArrayOfLists))).isEqualTo("f");
- assertCanCompile(expression);
- assertThat(stringify(expression.getValue(stringArrayOfLists))).isEqualTo("f");
- assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
-
- // array of arrays
- String[][] referenceTypeArrayOfArrays = new String[][] {new String[] {"a","b","c"},new String[] {"d","e","f"}};
- expression = parser.parseExpression("[1]");
- assertThat(stringify(expression.getValue(referenceTypeArrayOfArrays))).isEqualTo("d e f");
- assertCanCompile(expression);
- assertThat(getAst().getExitDescriptor()).isEqualTo("[Ljava/lang/String");
- assertThat(stringify(expression.getValue(referenceTypeArrayOfArrays))).isEqualTo("d e f");
- assertThat(getAst().getExitDescriptor()).isEqualTo("[Ljava/lang/String");
-
- expression = parser.parseExpression("[1][2]");
- assertThat(stringify(expression.getValue(referenceTypeArrayOfArrays))).isEqualTo("f");
- assertCanCompile(expression);
- assertThat(stringify(expression.getValue(referenceTypeArrayOfArrays))).isEqualTo("f");
- assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/String");
-
- int[][] primitiveTypeArrayOfArrays = new int[][] {new int[] {1,2,3},new int[] {4,5,6}};
- expression = parser.parseExpression("[1]");
- assertThat(stringify(expression.getValue(primitiveTypeArrayOfArrays))).isEqualTo("4 5 6");
- assertCanCompile(expression);
- assertThat(stringify(expression.getValue(primitiveTypeArrayOfArrays))).isEqualTo("4 5 6");
- assertThat(getAst().getExitDescriptor()).isEqualTo("[I");
-
- expression = parser.parseExpression("[1][2]");
- assertThat(stringify(expression.getValue(primitiveTypeArrayOfArrays))).isEqualTo("6");
- assertCanCompile(expression);
- assertThat(stringify(expression.getValue(primitiveTypeArrayOfArrays))).isEqualTo("6");
- assertThat(getAst().getExitDescriptor()).isEqualTo("I");
-
- // list of lists of reference types
- List> listOfListOfStrings = new ArrayList<>();
- List list = new ArrayList<>();
- list.add("a");
- list.add("b");
- list.add("c");
- listOfListOfStrings.add(list);
- list = new ArrayList<>();
- list.add("d");
- list.add("e");
- list.add("f");
- listOfListOfStrings.add(list);
-
- expression = parser.parseExpression("[1]");
- assertThat(stringify(expression.getValue(listOfListOfStrings))).isEqualTo("d e f");
- assertCanCompile(expression);
- assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
- assertThat(stringify(expression.getValue(listOfListOfStrings))).isEqualTo("d e f");
- assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
-
- expression = parser.parseExpression("[1][2]");
- assertThat(stringify(expression.getValue(listOfListOfStrings))).isEqualTo("f");
- assertCanCompile(expression);
- assertThat(stringify(expression.getValue(listOfListOfStrings))).isEqualTo("f");
- assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
-
- // Map of lists
- Map> mapToLists = new HashMap<>();
- list = new ArrayList<>();
- list.add("a");
- list.add("b");
- list.add("c");
- mapToLists.put("foo", list);
- expression = parser.parseExpression("['foo']");
- assertThat(stringify(expression.getValue(mapToLists))).isEqualTo("a b c");
- assertCanCompile(expression);
- assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
- assertThat(stringify(expression.getValue(mapToLists))).isEqualTo("a b c");
- assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
-
- expression = parser.parseExpression("['foo'][2]");
- assertThat(stringify(expression.getValue(mapToLists))).isEqualTo("c");
- assertCanCompile(expression);
- assertThat(stringify(expression.getValue(mapToLists))).isEqualTo("c");
- assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
-
- // Map to array
- Map mapToIntArray = new HashMap<>();
- StandardEvaluationContext ctx = new StandardEvaluationContext();
- ctx.addPropertyAccessor(new CompilableMapAccessor());
- mapToIntArray.put("foo",new int[] {1,2,3});
- expression = parser.parseExpression("['foo']");
- assertThat(stringify(expression.getValue(mapToIntArray))).isEqualTo("1 2 3");
- assertCanCompile(expression);
- assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
- assertThat(stringify(expression.getValue(mapToIntArray))).isEqualTo("1 2 3");
- assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
-
- expression = parser.parseExpression("['foo'][1]");
- assertThat(expression.getValue(mapToIntArray)).isEqualTo(2);
- assertCanCompile(expression);
- assertThat(expression.getValue(mapToIntArray)).isEqualTo(2);
-
- expression = parser.parseExpression("foo");
- assertThat(stringify(expression.getValue(ctx, mapToIntArray))).isEqualTo("1 2 3");
- assertCanCompile(expression);
- assertThat(stringify(expression.getValue(ctx, mapToIntArray))).isEqualTo("1 2 3");
- assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
-
- expression = parser.parseExpression("foo[1]");
- assertThat(expression.getValue(ctx, mapToIntArray)).isEqualTo(2);
- assertCanCompile(expression);
- assertThat(expression.getValue(ctx, mapToIntArray)).isEqualTo(2);
-
- expression = parser.parseExpression("['foo'][2]");
- assertThat(stringify(expression.getValue(ctx, mapToIntArray))).isEqualTo("3");
- assertCanCompile(expression);
- assertThat(stringify(expression.getValue(ctx, mapToIntArray))).isEqualTo("3");
- assertThat(getAst().getExitDescriptor()).isEqualTo("I");
-
- // Map array
- Map[] mapArray = new Map[1];
- mapArray[0] = new HashMap<>();
- mapArray[0].put("key", "value1");
- expression = parser.parseExpression("[0]");
- assertThat(stringify(expression.getValue(mapArray))).isEqualTo("{key=value1}");
- assertCanCompile(expression);
- assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/util/Map");
- assertThat(stringify(expression.getValue(mapArray))).isEqualTo("{key=value1}");
- assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/util/Map");
-
- expression = parser.parseExpression("[0]['key']");
- assertThat(stringify(expression.getValue(mapArray))).isEqualTo("value1");
- assertCanCompile(expression);
- assertThat(stringify(expression.getValue(mapArray))).isEqualTo("value1");
- assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
- }
-
@Test
void plusNeedingCheckcast_SPR12426() {
expression = parser.parseExpression("object + ' world'");
@@ -5261,32 +5401,6 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
return (SpelNodeImpl)ast;
}
- private String stringify(Object object) {
- StringBuilder s = new StringBuilder();
- if (object instanceof List> list) {
- for (Object l: list) {
- s.append(l);
- s.append(' ');
- }
- }
- else if (object instanceof Object[] objects) {
- for (Object o: objects) {
- s.append(o);
- s.append(' ');
- }
- }
- else if (object instanceof int[] ints) {
- for (int i: ints) {
- s.append(i);
- s.append(' ');
- }
- }
- else {
- s.append(object);
- }
- return s.toString().trim();
- }
-
private void assertCanCompile(Expression expression) {
assertThat(SpelCompiler.compile(expression)).isTrue();
}
@@ -5447,84 +5561,6 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
}
- static class CompilableMapAccessor implements CompilablePropertyAccessor {
-
- @Override
- public boolean canRead(EvaluationContext context, Object target, String name) {
- Map,?> map = (Map,?>) target;
- return map.containsKey(name);
- }
-
- @Override
- public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
- Map,?> map = (Map,?>) target;
- Object value = map.get(name);
- if (value == null && !map.containsKey(name)) {
- throw new MapAccessException(name);
- }
- return new TypedValue(value);
- }
-
- @Override
- public boolean canWrite(EvaluationContext context, Object target, String name) {
- return true;
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public void write(EvaluationContext context, Object target, String name, Object newValue) {
- Map map = (Map) target;
- map.put(name, newValue);
- }
-
- @Override
- public Class>[] getSpecificTargetClasses() {
- return new Class>[] {Map.class};
- }
-
- @Override
- public boolean isCompilable() {
- return true;
- }
-
- @Override
- public Class> getPropertyType() {
- return Object.class;
- }
-
- @Override
- public void generateCode(String propertyName, MethodVisitor mv, CodeFlow cf) {
- String descriptor = cf.lastDescriptor();
- if (descriptor == null) {
- cf.loadTarget(mv);
- }
- mv.visitLdcInsn(propertyName);
- mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "get","(Ljava/lang/Object;)Ljava/lang/Object;",true);
- }
- }
-
-
- /**
- * Exception thrown from {@code read} in order to reset a cached
- * PropertyAccessor, allowing other accessors to have a try.
- */
- @SuppressWarnings("serial")
- private static class MapAccessException extends AccessException {
-
- private final String key;
-
- public MapAccessException(String key) {
- super(null);
- this.key = key;
- }
-
- @Override
- public String getMessage() {
- return "Map does not contain a value for key '" + this.key + "'";
- }
- }
-
-
public static class Greeter {
public String getWorld() {
@@ -5986,21 +6022,22 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
public static class TestClass6 {
- public String orange = "value1";
public static String apple = "value2";
+
+ public String orange = "value1";
public long peach = 34L;
public String getBanana() {
return "value3";
}
- public static String getPlum() {
- return "value4";
- }
-
public String strawberry() {
return "value5";
}
+
+ public static String getPlum() {
+ return "value4";
+ }
}
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelCompilerTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelCompilerTests.java
index 2346e01dc1..d0e05eb58d 100644
--- a/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelCompilerTests.java
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelCompilerTests.java
@@ -36,6 +36,7 @@ import static org.springframework.expression.spel.standard.SpelExpressionTestUti
* @author Sam Brannen
* @author Andy Clement
* @since 5.1.14
+ * @see org.springframework.expression.spel.SpelCompilationCoverageTests
*/
class SpelCompilerTests {