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}