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}