001/*
002 * Copyright 2002-2019 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.expression.spel.ast;
018
019import java.util.Collections;
020import java.util.LinkedHashMap;
021import java.util.Map;
022
023import org.springframework.expression.EvaluationException;
024import org.springframework.expression.TypedValue;
025import org.springframework.expression.spel.ExpressionState;
026import org.springframework.expression.spel.SpelNode;
027import org.springframework.lang.Nullable;
028import org.springframework.util.Assert;
029
030/**
031 * Represent a map in an expression, e.g. '{name:'foo',age:12}'
032 *
033 * @author Andy Clement
034 * @since 4.1
035 */
036public class InlineMap extends SpelNodeImpl {
037
038        // If the map is purely literals, it is a constant value and can be computed and cached
039        @Nullable
040        private TypedValue constant;
041
042
043        public InlineMap(int startPos, int endPos, SpelNodeImpl... args) {
044                super(startPos, endPos, args);
045                checkIfConstant();
046        }
047
048
049        /**
050         * If all the components of the map are constants, or lists/maps that themselves
051         * contain constants, then a constant list can be built to represent this node.
052         * This will speed up later getValue calls and reduce the amount of garbage created.
053         */
054        private void checkIfConstant() {
055                boolean isConstant = true;
056                for (int c = 0, max = getChildCount(); c < max; c++) {
057                        SpelNode child = getChild(c);
058                        if (!(child instanceof Literal)) {
059                                if (child instanceof InlineList) {
060                                        InlineList inlineList = (InlineList) child;
061                                        if (!inlineList.isConstant()) {
062                                                isConstant = false;
063                                                break;
064                                        }
065                                }
066                                else if (child instanceof InlineMap) {
067                                        InlineMap inlineMap = (InlineMap) child;
068                                        if (!inlineMap.isConstant()) {
069                                                isConstant = false;
070                                                break;
071                                        }
072                                }
073                                else if (!(c % 2 == 0 && child instanceof PropertyOrFieldReference)) {
074                                        isConstant = false;
075                                        break;
076                                }
077                        }
078                }
079                if (isConstant) {
080                        Map<Object, Object> constantMap = new LinkedHashMap<>();
081                        int childCount = getChildCount();
082                        for (int c = 0; c < childCount; c++) {
083                                SpelNode keyChild = getChild(c++);
084                                SpelNode valueChild = getChild(c);
085                                Object key = null;
086                                Object value = null;
087                                if (keyChild instanceof Literal) {
088                                        key = ((Literal) keyChild).getLiteralValue().getValue();
089                                }
090                                else if (keyChild instanceof PropertyOrFieldReference) {
091                                        key = ((PropertyOrFieldReference) keyChild).getName();
092                                }
093                                else {
094                                        return;
095                                }
096                                if (valueChild instanceof Literal) {
097                                        value = ((Literal) valueChild).getLiteralValue().getValue();
098                                }
099                                else if (valueChild instanceof InlineList) {
100                                        value = ((InlineList) valueChild).getConstantValue();
101                                }
102                                else if (valueChild instanceof InlineMap) {
103                                        value = ((InlineMap) valueChild).getConstantValue();
104                                }
105                                constantMap.put(key, value);
106                        }
107                        this.constant = new TypedValue(Collections.unmodifiableMap(constantMap));
108                }
109        }
110
111        @Override
112        public TypedValue getValueInternal(ExpressionState expressionState) throws EvaluationException {
113                if (this.constant != null) {
114                        return this.constant;
115                }
116                else {
117                        Map<Object, Object> returnValue = new LinkedHashMap<>();
118                        int childcount = getChildCount();
119                        for (int c = 0; c < childcount; c++) {
120                                // TODO allow for key being PropertyOrFieldReference like Indexer on maps
121                                SpelNode keyChild = getChild(c++);
122                                Object key = null;
123                                if (keyChild instanceof PropertyOrFieldReference) {
124                                        PropertyOrFieldReference reference = (PropertyOrFieldReference) keyChild;
125                                        key = reference.getName();
126                                }
127                                else {
128                                        key = keyChild.getValue(expressionState);
129                                }
130                                Object value = getChild(c).getValue(expressionState);
131                                returnValue.put(key,  value);
132                        }
133                        return new TypedValue(returnValue);
134                }
135        }
136
137        @Override
138        public String toStringAST() {
139                StringBuilder sb = new StringBuilder("{");
140                int count = getChildCount();
141                for (int c = 0; c < count; c++) {
142                        if (c > 0) {
143                                sb.append(",");
144                        }
145                        sb.append(getChild(c++).toStringAST());
146                        sb.append(":");
147                        sb.append(getChild(c).toStringAST());
148                }
149                sb.append("}");
150                return sb.toString();
151        }
152
153        /**
154         * Return whether this list is a constant value.
155         */
156        public boolean isConstant() {
157                return this.constant != null;
158        }
159
160        @SuppressWarnings("unchecked")
161        @Nullable
162        public Map<Object, Object> getConstantValue() {
163                Assert.state(this.constant != null, "No constant");
164                return (Map<Object, Object>) this.constant.getValue();
165        }
166
167}