001/*
002 * Copyright 2002-2015 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.lang.reflect.Array;
020import java.util.ArrayList;
021import java.util.Arrays;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025
026import org.springframework.expression.EvaluationException;
027import org.springframework.expression.TypedValue;
028import org.springframework.expression.spel.ExpressionState;
029import org.springframework.expression.spel.SpelEvaluationException;
030import org.springframework.expression.spel.SpelMessage;
031import org.springframework.util.Assert;
032import org.springframework.util.ClassUtils;
033import org.springframework.util.ObjectUtils;
034
035/**
036 * Represents selection over a map or collection.
037 * For example: {1,2,3,4,5,6,7,8,9,10}.?{#isEven(#this) == 'y'} returns [2, 4, 6, 8, 10]
038 *
039 * <p>Basically a subset of the input data is returned based on the
040 * evaluation of the expression supplied as selection criteria.
041 *
042 * @author Andy Clement
043 * @author Mark Fisher
044 * @author Sam Brannen
045 * @author Juergen Hoeller
046 * @since 3.0
047 */
048public class Selection extends SpelNodeImpl {
049
050        public static final int ALL = 0; // ?[]
051
052        public static final int FIRST = 1; // ^[]
053
054        public static final int LAST = 2; // $[]
055
056        private final int variant;
057
058        private final boolean nullSafe;
059
060
061        public Selection(boolean nullSafe, int variant, int pos, SpelNodeImpl expression) {
062                super(pos, expression);
063                Assert.notNull(expression, "Expression must not be null");
064                this.nullSafe = nullSafe;
065                this.variant = variant;
066        }
067
068
069        @Override
070        public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
071                return getValueRef(state).getValue();
072        }
073
074        @Override
075        protected ValueRef getValueRef(ExpressionState state) throws EvaluationException {
076                TypedValue op = state.getActiveContextObject();
077                Object operand = op.getValue();
078                SpelNodeImpl selectionCriteria = this.children[0];
079
080                if (operand instanceof Map) {
081                        Map<?, ?> mapdata = (Map<?, ?>) operand;
082                        // TODO don't lose generic info for the new map
083                        Map<Object, Object> result = new HashMap<Object, Object>();
084                        Object lastKey = null;
085
086                        for (Map.Entry<?, ?> entry : mapdata.entrySet()) {
087                                try {
088                                        TypedValue kvPair = new TypedValue(entry);
089                                        state.pushActiveContextObject(kvPair);
090                                        state.enterScope();
091                                        Object val = selectionCriteria.getValueInternal(state).getValue();
092                                        if (val instanceof Boolean) {
093                                                if ((Boolean) val) {
094                                                        if (this.variant == FIRST) {
095                                                                result.put(entry.getKey(), entry.getValue());
096                                                                return new ValueRef.TypedValueHolderValueRef(new TypedValue(result), this);
097                                                        }
098                                                        result.put(entry.getKey(), entry.getValue());
099                                                        lastKey = entry.getKey();
100                                                }
101                                        }
102                                        else {
103                                                throw new SpelEvaluationException(selectionCriteria.getStartPosition(),
104                                                                SpelMessage.RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN);
105                                        }
106                                }
107                                finally {
108                                        state.popActiveContextObject();
109                                        state.exitScope();
110                                }
111                        }
112
113                        if ((this.variant == FIRST || this.variant == LAST) && result.isEmpty()) {
114                                return new ValueRef.TypedValueHolderValueRef(new TypedValue(null), this);
115                        }
116
117                        if (this.variant == LAST) {
118                                Map<Object, Object> resultMap = new HashMap<Object, Object>();
119                                Object lastValue = result.get(lastKey);
120                                resultMap.put(lastKey,lastValue);
121                                return new ValueRef.TypedValueHolderValueRef(new TypedValue(resultMap),this);
122                        }
123
124                        return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this);
125                }
126
127                if (operand instanceof Iterable || ObjectUtils.isArray(operand)) {
128                        Iterable<?> data = (operand instanceof Iterable ?
129                                        (Iterable<?>) operand : Arrays.asList(ObjectUtils.toObjectArray(operand)));
130
131                        List<Object> result = new ArrayList<Object>();
132                        int index = 0;
133                        for (Object element : data) {
134                                try {
135                                        state.pushActiveContextObject(new TypedValue(element));
136                                        state.enterScope("index", index);
137                                        Object val = selectionCriteria.getValueInternal(state).getValue();
138                                        if (val instanceof Boolean) {
139                                                if ((Boolean) val) {
140                                                        if (this.variant == FIRST) {
141                                                                return new ValueRef.TypedValueHolderValueRef(new TypedValue(element), this);
142                                                        }
143                                                        result.add(element);
144                                                }
145                                        }
146                                        else {
147                                                throw new SpelEvaluationException(selectionCriteria.getStartPosition(),
148                                                                SpelMessage.RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN);
149                                        }
150                                        index++;
151                                }
152                                finally {
153                                        state.exitScope();
154                                        state.popActiveContextObject();
155                                }
156                        }
157
158                        if ((this.variant == FIRST || this.variant == LAST) && result.isEmpty()) {
159                                return ValueRef.NullValueRef.INSTANCE;
160                        }
161
162                        if (this.variant == LAST) {
163                                return new ValueRef.TypedValueHolderValueRef(new TypedValue(result.get(result.size() - 1)), this);
164                        }
165
166                        if (operand instanceof Iterable) {
167                                return new ValueRef.TypedValueHolderValueRef(new TypedValue(result), this);
168                        }
169
170                        Class<?> elementType = ClassUtils.resolvePrimitiveIfNecessary(
171                                        op.getTypeDescriptor().getElementTypeDescriptor().getType());
172                        Object resultArray = Array.newInstance(elementType, result.size());
173                        System.arraycopy(result.toArray(), 0, resultArray, 0, result.size());
174                        return new ValueRef.TypedValueHolderValueRef(new TypedValue(resultArray), this);
175                }
176                if (operand == null) {
177                        if (this.nullSafe) {
178                                return ValueRef.NullValueRef.INSTANCE;
179                        }
180                        throw new SpelEvaluationException(getStartPosition(), SpelMessage.INVALID_TYPE_FOR_SELECTION, "null");
181                }
182                throw new SpelEvaluationException(getStartPosition(), SpelMessage.INVALID_TYPE_FOR_SELECTION,
183                                operand.getClass().getName());
184        }
185
186        @Override
187        public String toStringAST() {
188                StringBuilder sb = new StringBuilder();
189                switch (this.variant) {
190                        case ALL:
191                                sb.append("?[");
192                                break;
193                        case FIRST:
194                                sb.append("^[");
195                                break;
196                        case LAST:
197                                sb.append("$[");
198                                break;
199                }
200                return sb.append(getChild(0).toStringAST()).append("]").toString();
201        }
202
203}