1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
36
37
38
39
40
41
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
66
67
68
69
70
71
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);
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 }