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