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;
027
028/**
029 * Represent a map in an expression, e.g. '{name:'foo',age:12}'
030 *
031 * @author Andy Clement
032 * @since 4.1
033 */
034public class InlineMap extends SpelNodeImpl {
035
036        // If the map is purely literals, it is a constant value and can be computed and cached
037        private TypedValue constant;
038
039
040        public InlineMap(int pos, SpelNodeImpl... args) {
041                super(pos, args);
042                checkIfConstant();
043        }
044
045
046        /**
047         * If all the components of the map are constants, or lists/maps that themselves
048         * contain constants, then a constant list can be built to represent this node.
049         * This will speed up later getValue calls and reduce the amount of garbage created.
050         */
051        private void checkIfConstant() {
052                boolean isConstant = true;
053                for (int c = 0, max = getChildCount(); c < max; c++) {
054                        SpelNode child = getChild(c);
055                        if (!(child instanceof Literal)) {
056                                if (child instanceof InlineList) {
057                                        InlineList inlineList = (InlineList) child;
058                                        if (!inlineList.isConstant()) {
059                                                isConstant = false;
060                                                break;
061                                        }
062                                }
063                                else if (child instanceof InlineMap) {
064                                        InlineMap inlineMap = (InlineMap) child;
065                                        if (!inlineMap.isConstant()) {
066                                                isConstant = false;
067                                                break;
068                                        }
069                                }
070                                else if (!(c % 2 == 0 && child instanceof PropertyOrFieldReference)) {
071                                        isConstant = false;
072                                        break;
073                                }
074                        }
075                }
076                if (isConstant) {
077                        Map<Object, Object> constantMap = new LinkedHashMap<Object, Object>();
078                        int childCount = getChildCount();
079                        for (int c = 0; c < childCount; c++) {
080                                SpelNode keyChild = getChild(c++);
081                                SpelNode valueChild = getChild(c);
082                                Object key = null;
083                                Object value = null;
084                                if (keyChild instanceof Literal) {
085                                        key = ((Literal) keyChild).getLiteralValue().getValue();
086                                }
087                                else if (keyChild instanceof PropertyOrFieldReference) {
088                                        key = ((PropertyOrFieldReference) keyChild).getName();
089                                }
090                                else {
091                                        return;
092                                }
093                                if (valueChild instanceof Literal) {
094                                        value = ((Literal) valueChild).getLiteralValue().getValue();
095                                }
096                                else if (valueChild instanceof InlineList) {
097                                        value = ((InlineList) valueChild).getConstantValue();
098                                }
099                                else if (valueChild instanceof InlineMap) {
100                                        value = ((InlineMap) valueChild).getConstantValue();
101                                }
102                                constantMap.put(key, value);
103                        }
104                        this.constant = new TypedValue(Collections.unmodifiableMap(constantMap));
105                }
106        }
107
108        @Override
109        public TypedValue getValueInternal(ExpressionState expressionState) throws EvaluationException {
110                if (this.constant != null) {
111                        return this.constant;
112                }
113                else {
114                        Map<Object, Object> returnValue = new LinkedHashMap<Object, Object>();
115                        int childcount = getChildCount();
116                        for (int c = 0; c < childcount; c++) {
117                                // TODO allow for key being PropertyOrFieldReference like Indexer on maps
118                                SpelNode keyChild = getChild(c++);
119                                Object key = null;
120                                if (keyChild instanceof PropertyOrFieldReference) {
121                                        PropertyOrFieldReference reference = (PropertyOrFieldReference) keyChild;
122                                        key = reference.getName();
123                                }
124                                else {
125                                        key = keyChild.getValue(expressionState);
126                                }
127                                Object value = getChild(c).getValue(expressionState);
128                                returnValue.put(key,  value);
129                        }
130                        return new TypedValue(returnValue);
131                }
132        }
133
134        @Override
135        public String toStringAST() {
136                StringBuilder sb = new StringBuilder("{");
137                int count = getChildCount();
138                for (int c = 0; c < count; c++) {
139                        if (c > 0) {
140                                sb.append(",");
141                        }
142                        sb.append(getChild(c++).toStringAST());
143                        sb.append(":");
144                        sb.append(getChild(c).toStringAST());
145                }
146                sb.append("}");
147                return sb.toString();
148        }
149
150        /**
151         * Return whether this list is a constant value.
152         */
153        public boolean isConstant() {
154                return this.constant != null;
155        }
156
157        @SuppressWarnings("unchecked")
158        public Map<Object, Object> getConstantValue() {
159                return (Map<Object, Object>) this.constant.getValue();
160        }
161
162}