Add support for inline maps in SpEL expressions
This commit introduces the ability to specify an inline map in
an expression. The syntax is similar to inline lists and of
the form: "{key:value,key2:value}". The keys can optionally
be quoted. The documentation is also updated with information
on the syntax.
Issue: SPR-9472
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -119,7 +119,7 @@ public class InlineList extends SpelNodeImpl {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<Object> getConstantValue() {
|
||||
public List<Object> getConstantValue() {
|
||||
return (List<Object>) this.constant.getValue();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright 2014 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
|
||||
*
|
||||
* http://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.ast;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.expression.EvaluationException;
|
||||
import org.springframework.expression.TypedValue;
|
||||
import org.springframework.expression.spel.ExpressionState;
|
||||
import org.springframework.expression.spel.SpelNode;
|
||||
|
||||
/**
|
||||
* Represent a map in an expression, e.g. '{name:'foo',age:12}'
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 4.1
|
||||
*/
|
||||
public class InlineMap extends SpelNodeImpl {
|
||||
|
||||
// if the map is purely literals, it is a constant value and can be computed and cached
|
||||
TypedValue constant = null;
|
||||
|
||||
public InlineMap(int pos, SpelNodeImpl... args) {
|
||||
super(pos, args);
|
||||
checkIfConstant();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If all the components of the list are constants, or lists/maps that themselves contain constants, then a constant list
|
||||
* can be built to represent this node. This will speed up later getValue calls and reduce the amount of garbage
|
||||
* created.
|
||||
*/
|
||||
private void checkIfConstant() {
|
||||
boolean isConstant = true;
|
||||
for (int c = 0, max = getChildCount(); c < max; c++) {
|
||||
SpelNode child = getChild(c);
|
||||
if (!(child instanceof Literal)) {
|
||||
if (child instanceof InlineList) {
|
||||
InlineList inlineList = (InlineList) child;
|
||||
if (!inlineList.isConstant()) {
|
||||
isConstant = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (child instanceof InlineMap) {
|
||||
InlineMap inlineMap = (InlineMap) child;
|
||||
if (!inlineMap.isConstant()) {
|
||||
isConstant = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (!((c%2)==0 && (child instanceof PropertyOrFieldReference))) {
|
||||
isConstant = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isConstant) {
|
||||
Map<Object,Object> constantMap = new LinkedHashMap<Object,Object>();
|
||||
int childcount = getChildCount();
|
||||
for (int c = 0; c < childcount; c++) {
|
||||
SpelNode keyChild = getChild(c++);
|
||||
SpelNode valueChild = getChild(c);
|
||||
Object key = null;
|
||||
Object value = null;
|
||||
if ((keyChild instanceof Literal)) {
|
||||
key = ((Literal) keyChild).getLiteralValue().getValue();
|
||||
}
|
||||
else if (keyChild instanceof PropertyOrFieldReference) {
|
||||
key = ((PropertyOrFieldReference) keyChild).getName();
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
if (valueChild instanceof Literal) {
|
||||
value = ((Literal) valueChild).getLiteralValue().getValue();
|
||||
}
|
||||
else if (valueChild instanceof InlineList) {
|
||||
value = ((InlineList) valueChild).getConstantValue();
|
||||
}
|
||||
else if (valueChild instanceof InlineMap) {
|
||||
value = ((InlineMap) valueChild).getConstantValue();
|
||||
}
|
||||
constantMap.put(key, value);
|
||||
}
|
||||
this.constant = new TypedValue(Collections.unmodifiableMap(constantMap));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypedValue getValueInternal(ExpressionState expressionState) throws EvaluationException {
|
||||
if (this.constant != null) {
|
||||
return this.constant;
|
||||
}
|
||||
else {
|
||||
Map<Object, Object> returnValue = new LinkedHashMap<Object, Object>();
|
||||
int childcount = getChildCount();
|
||||
for (int c = 0; c < childcount; c++) {
|
||||
// TODO allow for key being PropertyOrFieldReference like Indexer on maps
|
||||
SpelNode keyChild = getChild(c++);
|
||||
Object key = null;
|
||||
if (keyChild instanceof PropertyOrFieldReference) {
|
||||
PropertyOrFieldReference reference = (PropertyOrFieldReference) keyChild;
|
||||
key = reference.getName();
|
||||
}
|
||||
else {
|
||||
key = keyChild.getValue(expressionState);
|
||||
}
|
||||
Object value = getChild(c).getValue(expressionState);
|
||||
returnValue.put(key, value);
|
||||
}
|
||||
return new TypedValue(returnValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toStringAST() {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append('{');
|
||||
int count = getChildCount();
|
||||
for (int c = 0; c < count; c++) {
|
||||
if (c > 0) {
|
||||
s.append(',');
|
||||
}
|
||||
s.append(getChild(c++).toStringAST());
|
||||
s.append(':');
|
||||
s.append(getChild(c).toStringAST());
|
||||
}
|
||||
s.append('}');
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether this list is a constant value
|
||||
*/
|
||||
public boolean isConstant() {
|
||||
return this.constant != null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<Object,Object> getConstantValue() {
|
||||
return (Map<Object,Object>) this.constant.getValue();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -39,6 +39,7 @@ import org.springframework.expression.spel.ast.FunctionReference;
|
||||
import org.springframework.expression.spel.ast.Identifier;
|
||||
import org.springframework.expression.spel.ast.Indexer;
|
||||
import org.springframework.expression.spel.ast.InlineList;
|
||||
import org.springframework.expression.spel.ast.InlineMap;
|
||||
import org.springframework.expression.spel.ast.Literal;
|
||||
import org.springframework.expression.spel.ast.MethodReference;
|
||||
import org.springframework.expression.spel.ast.NullLiteral;
|
||||
@@ -515,7 +516,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
||||
|| maybeEatIndexer()) {
|
||||
return pop();
|
||||
}
|
||||
else if (maybeEatInlineList()) {
|
||||
else if (maybeEatInlineListOrMap()) {
|
||||
return pop();
|
||||
}
|
||||
else {
|
||||
@@ -600,7 +601,8 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
||||
}
|
||||
|
||||
// list = LCURLY (element (COMMA element)*) RCURLY
|
||||
private boolean maybeEatInlineList() {
|
||||
// map = LCURLY (key ':' value (COMMA key ':' value)*) RCURLY
|
||||
private boolean maybeEatInlineListOrMap() {
|
||||
Token t = peekToken();
|
||||
if (!peekToken(TokenKind.LCURLY, true)) {
|
||||
return false;
|
||||
@@ -608,18 +610,53 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
||||
SpelNodeImpl expr = null;
|
||||
Token closingCurly = peekToken();
|
||||
if (peekToken(TokenKind.RCURLY, true)) {
|
||||
// empty list '[]'
|
||||
// empty list '{}'
|
||||
expr = new InlineList(toPos(t.startpos,closingCurly.endpos));
|
||||
}
|
||||
else {
|
||||
List<SpelNodeImpl> listElements = new ArrayList<SpelNodeImpl>();
|
||||
do {
|
||||
listElements.add(eatExpression());
|
||||
}
|
||||
while (peekToken(TokenKind.COMMA,true));
|
||||
|
||||
else if (peekToken(TokenKind.COLON,true)) {
|
||||
closingCurly = eatToken(TokenKind.RCURLY);
|
||||
expr = new InlineList(toPos(t.startpos,closingCurly.endpos),listElements.toArray(new SpelNodeImpl[listElements.size()]));
|
||||
// empty map '{:}'
|
||||
expr = new InlineMap(toPos(t.startpos,closingCurly.endpos));
|
||||
}
|
||||
else {
|
||||
SpelNodeImpl firstExpression = eatExpression();
|
||||
// Next is either:
|
||||
// '}' - end of list
|
||||
// ',' - more expressions in this list
|
||||
// ':' - this is a map!
|
||||
|
||||
if (peekToken(TokenKind.RCURLY)) { // list with one item in it
|
||||
List<SpelNodeImpl> listElements = new ArrayList<SpelNodeImpl>();
|
||||
listElements.add(firstExpression);
|
||||
closingCurly = eatToken(TokenKind.RCURLY);
|
||||
expr = new InlineList(toPos(t.startpos,closingCurly.endpos),listElements.toArray(new SpelNodeImpl[listElements.size()]));
|
||||
}
|
||||
else if (peekToken(TokenKind.COMMA, true)) { // multi item list
|
||||
List<SpelNodeImpl> listElements = new ArrayList<SpelNodeImpl>();
|
||||
listElements.add(firstExpression);
|
||||
do {
|
||||
listElements.add(eatExpression());
|
||||
}
|
||||
while (peekToken(TokenKind.COMMA,true));
|
||||
closingCurly = eatToken(TokenKind.RCURLY);
|
||||
expr = new InlineList(toPos(t.startpos,closingCurly.endpos),listElements.toArray(new SpelNodeImpl[listElements.size()]));
|
||||
|
||||
}
|
||||
else if (peekToken(TokenKind.COLON, true)) { // map!
|
||||
List<SpelNodeImpl> mapElements = new ArrayList<SpelNodeImpl>();
|
||||
mapElements.add(firstExpression);
|
||||
mapElements.add(eatExpression());
|
||||
while (peekToken(TokenKind.COMMA,true)) {
|
||||
mapElements.add(eatExpression());
|
||||
eatToken(TokenKind.COLON);
|
||||
mapElements.add(eatExpression());
|
||||
}
|
||||
closingCurly = eatToken(TokenKind.RCURLY);
|
||||
expr = new InlineMap(toPos(t.startpos,closingCurly.endpos),mapElements.toArray(new SpelNodeImpl[mapElements.size()]));
|
||||
}
|
||||
else {
|
||||
raiseInternalException(t.startpos, SpelMessage.OOD);
|
||||
}
|
||||
}
|
||||
this.constructedNodes.push(expr);
|
||||
return true;
|
||||
@@ -734,7 +771,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
||||
}
|
||||
eatToken(TokenKind.RSQUARE);
|
||||
}
|
||||
if (maybeEatInlineList()) {
|
||||
if (maybeEatInlineListOrMap()) {
|
||||
nodes.add(pop());
|
||||
}
|
||||
push(new ConstructorReference(toPos(newToken), dimensions.toArray(new SpelNodeImpl[dimensions.size()]),
|
||||
|
||||
Reference in New Issue
Block a user