1 /*
2  * Copyright 2002-2010 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.ClassUtils;
33 import org.springframework.util.ObjectUtils;
34 
35 /**
36  * Represents selection over a map or collection.
37  * For example: {1,2,3,4,5,6,7,8,9,10}.?{#isEven(#this) == 'y'} returns [2, 4, 6, 8, 10]
38  *
39  * <p>Basically a subset of the input data is returned based on the
40  * evaluation of the expression supplied as selection criteria.
41  *
42  * @author Andy Clement
43  * @author Mark Fisher
44  * @author Sam Brannen
45  * @since 3.0
46  */
47 public class Selection extends SpelNodeImpl {
48 
49 	public final static int ALL = 0; // ?[]
50 	public final static int FIRST = 1; // ^[]
51 	public final static int LAST = 2; // $[]
52 
53 	private final int variant;
54 	private final boolean nullSafe;
55 
Selection(boolean nullSafe, int variant,int pos,SpelNodeImpl expression)56 	public Selection(boolean nullSafe, int variant,int pos,SpelNodeImpl expression) {
57 		super(pos,expression);
58 		this.nullSafe = nullSafe;
59 		this.variant = variant;
60 	}
61 
62 	@SuppressWarnings("unchecked")
63 	@Override
getValueInternal(ExpressionState state)64 	public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
65 		TypedValue op = state.getActiveContextObject();
66 		Object operand = op.getValue();
67 
68 		SpelNodeImpl selectionCriteria = children[0];
69 		if (operand instanceof Map) {
70 			Map<?, ?> mapdata = (Map<?, ?>) operand;
71 			// TODO don't lose generic info for the new map
72 			Map<Object,Object> result = new HashMap<Object,Object>();
73 			Object lastKey = null;
74 			for (Map.Entry entry : mapdata.entrySet()) {
75 				try {
76 					TypedValue kvpair = new TypedValue(entry);
77 					state.pushActiveContextObject(kvpair);
78 					Object o = selectionCriteria.getValueInternal(state).getValue();
79 					if (o instanceof Boolean) {
80 						if (((Boolean) o).booleanValue() == true) {
81 							if (variant == FIRST) {
82 								result.put(entry.getKey(),entry.getValue());
83 								return new TypedValue(result);
84 							}
85 							result.put(entry.getKey(),entry.getValue());
86 							lastKey = entry.getKey();
87 						}
88 					} else {
89 						throw new SpelEvaluationException(selectionCriteria.getStartPosition(),
90 								SpelMessage.RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN);// ,selectionCriteria.stringifyAST());
91 					}
92 				} finally {
93 					state.popActiveContextObject();
94 				}
95 			}
96 			if ((variant == FIRST || variant == LAST) && result.size() == 0) {
97 				return new TypedValue(null);
98 			}
99 			if (variant == LAST) {
100 				Map resultMap = new HashMap();
101 				Object lastValue = result.get(lastKey);
102 				resultMap.put(lastKey,lastValue);
103 				return new TypedValue(resultMap);
104 			}
105 			return new TypedValue(result);
106 		} else if ((operand instanceof Collection) || ObjectUtils.isArray(operand)) {
107 			List<Object> data = new ArrayList<Object>();
108 			Collection<?> c = (operand instanceof Collection) ?
109 					(Collection<?>) operand : Arrays.asList(ObjectUtils.toObjectArray(operand));
110 			data.addAll(c);
111 			List<Object> result = new ArrayList<Object>();
112 			int idx = 0;
113 			for (Object element : data) {
114 				try {
115 					state.pushActiveContextObject(new TypedValue(element));
116 					state.enterScope("index", idx);
117 					Object o = selectionCriteria.getValueInternal(state).getValue();
118 					if (o instanceof Boolean) {
119 						if (((Boolean) o).booleanValue() == true) {
120 							if (variant == FIRST) {
121 								return new TypedValue(element);
122 							}
123 							result.add(element);
124 						}
125 					} else {
126 						throw new SpelEvaluationException(selectionCriteria.getStartPosition(),
127 								SpelMessage.RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN);// ,selectionCriteria.stringifyAST());
128 					}
129 					idx++;
130 				} finally {
131 					state.exitScope();
132 					state.popActiveContextObject();
133 				}
134 			}
135 			if ((variant == FIRST || variant == LAST) && result.size() == 0) {
136 				return TypedValue.NULL;
137 			}
138 			if (variant == LAST) {
139 				return new TypedValue(result.get(result.size() - 1));
140 			}
141 			if (operand instanceof Collection) {
142 				return new TypedValue(result);
143 			}
144 			else {
145 				Class<?> elementType = ClassUtils.resolvePrimitiveIfNecessary(op.getTypeDescriptor().getElementTypeDescriptor().getType());
146 				Object resultArray = Array.newInstance(elementType, result.size());
147 				System.arraycopy(result.toArray(), 0, resultArray, 0, result.size());
148 				return new TypedValue(resultArray);
149 			}
150 		} else {
151 			if (operand==null) {
152 				if (nullSafe) {
153 					return TypedValue.NULL;
154 				} else {
155 					throw new SpelEvaluationException(getStartPosition(), SpelMessage.INVALID_TYPE_FOR_SELECTION,
156 							"null");
157 				}
158 			} else {
159 				throw new SpelEvaluationException(getStartPosition(), SpelMessage.INVALID_TYPE_FOR_SELECTION,
160 						operand.getClass().getName());
161 			}
162 		}
163 	}
164 
165 	@Override
toStringAST()166 	public String toStringAST() {
167 		StringBuilder sb = new StringBuilder();
168 		switch (variant) {
169 		case ALL:
170 			sb.append("?[");
171 			break;
172 		case FIRST:
173 			sb.append("^[");
174 			break;
175 		case LAST:
176 			sb.append("$[");
177 			break;
178 		}
179 		return sb.append(getChild(0).toStringAST()).append("]").toString();
180 	}
181 
182 }
183