1 /* 2 * Copyright 2002-2010 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 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.HashMap; 24 import java.util.List; 25 import java.util.Map; 26 27 import org.springframework.expression.EvaluationException; 28 import org.springframework.expression.TypedValue; 29 import org.springframework.expression.spel.ExpressionState; 30 import org.springframework.expression.spel.SpelEvaluationException; 31 import org.springframework.expression.spel.SpelMessage; 32 import org.springframework.util.ClassUtils; 33 import org.springframework.util.ObjectUtils; 34 35 /** 36 * Represents selection over a map or collection. 37 * For example: {1,2,3,4,5,6,7,8,9,10}.?{#isEven(#this) == 'y'} returns [2, 4, 6, 8, 10] 38 * 39 * <p>Basically a subset of the input data is returned based on the 40 * evaluation of the expression supplied as selection criteria. 41 * 42 * @author Andy Clement 43 * @author Mark Fisher 44 * @author Sam Brannen 45 * @since 3.0 46 */ 47 public class Selection extends SpelNodeImpl { 48 49 public final static int ALL = 0; // ?[] 50 public final static int FIRST = 1; // ^[] 51 public final static int LAST = 2; // $[] 52 53 private final int variant; 54 private final boolean nullSafe; 55 Selection(boolean nullSafe, int variant,int pos,SpelNodeImpl expression)56 public Selection(boolean nullSafe, int variant,int pos,SpelNodeImpl expression) { 57 super(pos,expression); 58 this.nullSafe = nullSafe; 59 this.variant = variant; 60 } 61 62 @SuppressWarnings("unchecked") 63 @Override getValueInternal(ExpressionState state)64 public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { 65 TypedValue op = state.getActiveContextObject(); 66 Object operand = op.getValue(); 67 68 SpelNodeImpl selectionCriteria = children[0]; 69 if (operand instanceof Map) { 70 Map<?, ?> mapdata = (Map<?, ?>) operand; 71 // TODO don't lose generic info for the new map 72 Map<Object,Object> result = new HashMap<Object,Object>(); 73 Object lastKey = null; 74 for (Map.Entry entry : mapdata.entrySet()) { 75 try { 76 TypedValue kvpair = new TypedValue(entry); 77 state.pushActiveContextObject(kvpair); 78 Object o = selectionCriteria.getValueInternal(state).getValue(); 79 if (o instanceof Boolean) { 80 if (((Boolean) o).booleanValue() == true) { 81 if (variant == FIRST) { 82 result.put(entry.getKey(),entry.getValue()); 83 return new TypedValue(result); 84 } 85 result.put(entry.getKey(),entry.getValue()); 86 lastKey = entry.getKey(); 87 } 88 } else { 89 throw new SpelEvaluationException(selectionCriteria.getStartPosition(), 90 SpelMessage.RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN);// ,selectionCriteria.stringifyAST()); 91 } 92 } finally { 93 state.popActiveContextObject(); 94 } 95 } 96 if ((variant == FIRST || variant == LAST) && result.size() == 0) { 97 return new TypedValue(null); 98 } 99 if (variant == LAST) { 100 Map resultMap = new HashMap(); 101 Object lastValue = result.get(lastKey); 102 resultMap.put(lastKey,lastValue); 103 return new TypedValue(resultMap); 104 } 105 return new TypedValue(result); 106 } else if ((operand instanceof Collection) || ObjectUtils.isArray(operand)) { 107 List<Object> data = new ArrayList<Object>(); 108 Collection<?> c = (operand instanceof Collection) ? 109 (Collection<?>) operand : Arrays.asList(ObjectUtils.toObjectArray(operand)); 110 data.addAll(c); 111 List<Object> result = new ArrayList<Object>(); 112 int idx = 0; 113 for (Object element : data) { 114 try { 115 state.pushActiveContextObject(new TypedValue(element)); 116 state.enterScope("index", idx); 117 Object o = selectionCriteria.getValueInternal(state).getValue(); 118 if (o instanceof Boolean) { 119 if (((Boolean) o).booleanValue() == true) { 120 if (variant == FIRST) { 121 return new TypedValue(element); 122 } 123 result.add(element); 124 } 125 } else { 126 throw new SpelEvaluationException(selectionCriteria.getStartPosition(), 127 SpelMessage.RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN);// ,selectionCriteria.stringifyAST()); 128 } 129 idx++; 130 } finally { 131 state.exitScope(); 132 state.popActiveContextObject(); 133 } 134 } 135 if ((variant == FIRST || variant == LAST) && result.size() == 0) { 136 return TypedValue.NULL; 137 } 138 if (variant == LAST) { 139 return new TypedValue(result.get(result.size() - 1)); 140 } 141 if (operand instanceof Collection) { 142 return new TypedValue(result); 143 } 144 else { 145 Class<?> elementType = ClassUtils.resolvePrimitiveIfNecessary(op.getTypeDescriptor().getElementTypeDescriptor().getType()); 146 Object resultArray = Array.newInstance(elementType, result.size()); 147 System.arraycopy(result.toArray(), 0, resultArray, 0, result.size()); 148 return new TypedValue(resultArray); 149 } 150 } else { 151 if (operand==null) { 152 if (nullSafe) { 153 return TypedValue.NULL; 154 } else { 155 throw new SpelEvaluationException(getStartPosition(), SpelMessage.INVALID_TYPE_FOR_SELECTION, 156 "null"); 157 } 158 } else { 159 throw new SpelEvaluationException(getStartPosition(), SpelMessage.INVALID_TYPE_FOR_SELECTION, 160 operand.getClass().getName()); 161 } 162 } 163 } 164 165 @Override toStringAST()166 public String toStringAST() { 167 StringBuilder sb = new StringBuilder(); 168 switch (variant) { 169 case ALL: 170 sb.append("?["); 171 break; 172 case FIRST: 173 sb.append("^["); 174 break; 175 case LAST: 176 sb.append("$["); 177 break; 178 } 179 return sb.append(getChild(0).toStringAST()).append("]").toString(); 180 } 181 182 } 183