View Javadoc
1   /*
2    * Copyright 2002-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.expression.spel.ast;
18  
19  import java.lang.reflect.Array;
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.Collection;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  
27  import org.springframework.expression.EvaluationException;
28  import org.springframework.expression.TypedValue;
29  import org.springframework.expression.spel.ExpressionState;
30  import org.springframework.expression.spel.SpelEvaluationException;
31  import org.springframework.expression.spel.SpelMessage;
32  import org.springframework.util.Assert;
33  import org.springframework.util.ClassUtils;
34  import org.springframework.util.ObjectUtils;
35  
36  /**
37   * Represents selection over a map or collection.
38   * For example: {1,2,3,4,5,6,7,8,9,10}.?{#isEven(#this) == 'y'} returns [2, 4, 6, 8, 10]
39   *
40   * <p>Basically a subset of the input data is returned based on the
41   * evaluation of the expression supplied as selection criteria.
42   *
43   * @author Andy Clement
44   * @author Mark Fisher
45   * @author Sam Brannen
46   * @since 3.0
47   */
48  public class Selection extends SpelNodeImpl {
49  
50  	public static final int ALL = 0; // ?[]
51  
52  	public static final int FIRST = 1; // ^[]
53  
54  	public static final int LAST = 2; // $[]
55  
56  	private final int variant;
57  
58  	private final boolean nullSafe;
59  
60  
61  	public Selection(boolean nullSafe, int variant, int pos, SpelNodeImpl expression) {
62  		super(pos, expression);
63  		Assert.notNull(expression, "Expression must not be null");
64  		this.nullSafe = nullSafe;
65  		this.variant = variant;
66  	}
67  
68  
69  	@Override
70  	public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
71  		return getValueRef(state).getValue();
72  	}
73  
74  	@Override
75  	protected ValueRef getValueRef(ExpressionState state) throws EvaluationException {
76  		TypedValue op = state.getActiveContextObject();
77  		Object operand = op.getValue();
78  
79  		SpelNodeImpl selectionCriteria = this.children[0];
80  		if (operand instanceof Map) {
81  			Map<?, ?> mapdata = (Map<?, ?>) operand;
82  			// TODO don't lose generic info for the new map
83  			Map<Object, Object> result = new HashMap<Object, Object>();
84  			Object lastKey = null;
85  			for (Map.Entry<?, ?> entry : mapdata.entrySet()) {
86  				try {
87  					TypedValue kvPair = new TypedValue(entry);
88  					state.pushActiveContextObject(kvPair);
89  					Object val = selectionCriteria.getValueInternal(state).getValue();
90  					if (val instanceof Boolean) {
91  						if ((Boolean) val) {
92  							if (this.variant == FIRST) {
93  								result.put(entry.getKey(), entry.getValue());
94  								return new ValueRef.TypedValueHolderValueRef(new TypedValue(result), this);
95  							}
96  							result.put(entry.getKey(), entry.getValue());
97  							lastKey = entry.getKey();
98  						}
99  					}
100 					else {
101 						throw new SpelEvaluationException(selectionCriteria.getStartPosition(),
102 								SpelMessage.RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN);
103 					}
104 				}
105 				finally {
106 					state.popActiveContextObject();
107 				}
108 			}
109 			if ((this.variant == FIRST || this.variant == LAST) && result.isEmpty()) {
110 				return new ValueRef.TypedValueHolderValueRef(new TypedValue(null), this);
111 			}
112 
113 			if (this.variant == LAST) {
114 				Map<Object, Object> resultMap = new HashMap<Object, Object>();
115 				Object lastValue = result.get(lastKey);
116 				resultMap.put(lastKey,lastValue);
117 				return new ValueRef.TypedValueHolderValueRef(new TypedValue(resultMap),this);
118 			}
119 
120 			return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this);
121 		}
122 
123 		if ((operand instanceof Collection) || ObjectUtils.isArray(operand)) {
124 			List<Object> data = new ArrayList<Object>();
125 			Collection<?> coll = (operand instanceof Collection ?
126 					(Collection<?>) operand : Arrays.asList(ObjectUtils.toObjectArray(operand)));
127 			data.addAll(coll);
128 			List<Object> result = new ArrayList<Object>();
129 			int index = 0;
130 			for (Object element : data) {
131 				try {
132 					state.pushActiveContextObject(new TypedValue(element));
133 					state.enterScope("index", index);
134 					Object val = selectionCriteria.getValueInternal(state).getValue();
135 					if (val instanceof Boolean) {
136 						if ((Boolean) val) {
137 							if (this.variant == FIRST) {
138 								return new ValueRef.TypedValueHolderValueRef(new TypedValue(element), this);
139 							}
140 							result.add(element);
141 						}
142 					}
143 					else {
144 						throw new SpelEvaluationException(selectionCriteria.getStartPosition(),
145 								SpelMessage.RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN);
146 					}
147 					index++;
148 				}
149 				finally {
150 					state.exitScope();
151 					state.popActiveContextObject();
152 				}
153 			}
154 
155 			if ((this.variant == FIRST || this.variant == LAST) && result.size() == 0) {
156 				return ValueRef.NullValueRef.INSTANCE;
157 			}
158 
159 			if (this.variant == LAST) {
160 				return new ValueRef.TypedValueHolderValueRef(new TypedValue(result.get(result.size() - 1)),this);
161 			}
162 
163 			if (operand instanceof Collection) {
164 				return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this);
165 			}
166 			Class<?> elementType = ClassUtils.resolvePrimitiveIfNecessary(
167 					op.getTypeDescriptor().getElementTypeDescriptor().getType());
168 			Object resultArray = Array.newInstance(elementType, result.size());
169 			System.arraycopy(result.toArray(), 0, resultArray, 0, result.size());
170 			return new ValueRef.TypedValueHolderValueRef(new TypedValue(resultArray),this);
171 		}
172 		if (operand == null) {
173 			if (this.nullSafe) {
174 				return ValueRef.NullValueRef.INSTANCE;
175 			}
176 			throw new SpelEvaluationException(getStartPosition(), SpelMessage.INVALID_TYPE_FOR_SELECTION, "null");
177 		}
178 		throw new SpelEvaluationException(getStartPosition(), SpelMessage.INVALID_TYPE_FOR_SELECTION,
179 				operand.getClass().getName());
180 	}
181 
182 	@Override
183 	public String toStringAST() {
184 		StringBuilder sb = new StringBuilder();
185 		switch (this.variant) {
186 			case ALL:
187 				sb.append("?[");
188 				break;
189 			case FIRST:
190 				sb.append("^[");
191 				break;
192 			case LAST:
193 				sb.append("$[");
194 				break;
195 		}
196 		return sb.append(getChild(0).toStringAST()).append("]").toString();
197 	}
198 
199 }