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.List;
24  import java.util.Map;
25  
26  import org.springframework.expression.EvaluationException;
27  import org.springframework.expression.TypedValue;
28  import org.springframework.expression.spel.ExpressionState;
29  import org.springframework.expression.spel.SpelEvaluationException;
30  import org.springframework.expression.spel.SpelMessage;
31  import org.springframework.util.ClassUtils;
32  import org.springframework.util.ObjectUtils;
33  
34  /**
35   * Represents projection, where a given operation is performed on all elements in some
36   * input sequence, returning a new sequence of the same size. For example:
37   * "{1,2,3,4,5,6,7,8,9,10}.!{#isEven(#this)}" returns "[n, y, n, y, n, y, n, y, n, y]"
38   *
39   * @author Andy Clement
40   * @author Mark Fisher
41   * @since 3.0
42   */
43  public class Projection extends SpelNodeImpl {
44  
45  	private final boolean nullSafe;
46  
47  
48  	public Projection(boolean nullSafe, int pos, SpelNodeImpl expression) {
49  		super(pos, expression);
50  		this.nullSafe = nullSafe;
51  	}
52  
53  
54  	@Override
55  	public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
56  		return getValueRef(state).getValue();
57  	}
58  
59  	@Override
60  	protected ValueRef getValueRef(ExpressionState state) throws EvaluationException {
61  		TypedValue op = state.getActiveContextObject();
62  
63  		Object operand = op.getValue();
64  		boolean operandIsArray = ObjectUtils.isArray(operand);
65  		// TypeDescriptor operandTypeDescriptor = op.getTypeDescriptor();
66  
67  		// When the input is a map, we push a special context object on the stack
68  		// before calling the specified operation. This special context object
69  		// has two fields 'key' and 'value' that refer to the map entries key
70  		// and value, and they can be referenced in the operation
71  		// eg. {'a':'y','b':'n'}.!{value=='y'?key:null}" == ['a', null]
72  		if (operand instanceof Map) {
73  			Map<?, ?> mapData = (Map<?, ?>) operand;
74  			List<Object> result = new ArrayList<Object>();
75  			for (Map.Entry<?, ?> entry : mapData.entrySet()) {
76  				try {
77  					state.pushActiveContextObject(new TypedValue(entry));
78  					result.add(this.children[0].getValueInternal(state).getValue());
79  				}
80  				finally {
81  					state.popActiveContextObject();
82  				}
83  			}
84  			return new ValueRef.TypedValueHolderValueRef(new TypedValue(result), this);  // TODO unable to build correct type descriptor
85  		}
86  
87  		if (operand instanceof Collection || operandIsArray) {
88  			Collection<?> data = (operand instanceof Collection ? (Collection<?>) operand :
89  					Arrays.asList(ObjectUtils.toObjectArray(operand)));
90  			List<Object> result = new ArrayList<Object>();
91  			int idx = 0;
92  			Class<?> arrayElementType = null;
93  			for (Object element : data) {
94  				try {
95  					state.pushActiveContextObject(new TypedValue(element));
96  					state.enterScope("index", idx);
97  					Object value = this.children[0].getValueInternal(state).getValue();
98  					if (value != null && operandIsArray) {
99  						arrayElementType = determineCommonType(arrayElementType, value.getClass());
100 					}
101 					result.add(value);
102 				}
103 				finally {
104 					state.exitScope();
105 					state.popActiveContextObject();
106 				}
107 				idx++;
108 			}
109 			if (operandIsArray) {
110 				if (arrayElementType == null) {
111 					arrayElementType = Object.class;
112 				}
113 				Object resultArray = Array.newInstance(arrayElementType, result.size());
114 				System.arraycopy(result.toArray(), 0, resultArray, 0, result.size());
115 				return new ValueRef.TypedValueHolderValueRef(new TypedValue(resultArray),this);
116 			}
117 			return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this);
118 		}
119 
120 		if (operand==null) {
121 			if (this.nullSafe) {
122 				return ValueRef.NullValueRef.INSTANCE;
123 			}
124 			throw new SpelEvaluationException(getStartPosition(), SpelMessage.PROJECTION_NOT_SUPPORTED_ON_TYPE, "null");
125 		}
126 
127 		throw new SpelEvaluationException(getStartPosition(), SpelMessage.PROJECTION_NOT_SUPPORTED_ON_TYPE,
128 				operand.getClass().getName());
129 	}
130 
131 	@Override
132 	public String toStringAST() {
133 		return "![" + getChild(0).toStringAST() + "]";
134 	}
135 
136 	private Class<?> determineCommonType(Class<?> oldType, Class<?> newType) {
137 		if (oldType == null) {
138 			return newType;
139 		}
140 		if (oldType.isAssignableFrom(newType)) {
141 			return oldType;
142 		}
143 		Class<?> nextType = newType;
144 		while (nextType != Object.class) {
145 			if (nextType.isAssignableFrom(oldType)) {
146 				return nextType;
147 			}
148 			nextType = nextType.getSuperclass();
149 		}
150 		Class<?>[] interfaces = ClassUtils.getAllInterfacesForClass(newType);
151 		for (Class<?> nextInterface : interfaces) {
152 			if (nextInterface.isAssignableFrom(oldType)) {
153 				return nextInterface;
154 			}
155 		}
156 		return Object.class;
157 	}
158 
159 }