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}