001/* 002 * Copyright 2002-2017 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.List; 023import java.util.Map; 024 025import org.springframework.expression.EvaluationException; 026import org.springframework.expression.TypedValue; 027import org.springframework.expression.spel.ExpressionState; 028import org.springframework.expression.spel.SpelEvaluationException; 029import org.springframework.expression.spel.SpelMessage; 030import org.springframework.util.ClassUtils; 031import org.springframework.util.ObjectUtils; 032 033/** 034 * Represents projection, where a given operation is performed on all elements in some 035 * input sequence, returning a new sequence of the same size. For example: 036 * "{1,2,3,4,5,6,7,8,9,10}.!{#isEven(#this)}" returns "[n, y, n, y, n, y, n, y, n, y]" 037 * 038 * @author Andy Clement 039 * @author Mark Fisher 040 * @author Juergen Hoeller 041 * @since 3.0 042 */ 043public class Projection extends SpelNodeImpl { 044 045 private final boolean nullSafe; 046 047 048 public Projection(boolean nullSafe, int pos, SpelNodeImpl expression) { 049 super(pos, expression); 050 this.nullSafe = nullSafe; 051 } 052 053 054 @Override 055 public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { 056 return getValueRef(state).getValue(); 057 } 058 059 @Override 060 protected ValueRef getValueRef(ExpressionState state) throws EvaluationException { 061 TypedValue op = state.getActiveContextObject(); 062 063 Object operand = op.getValue(); 064 boolean operandIsArray = ObjectUtils.isArray(operand); 065 // TypeDescriptor operandTypeDescriptor = op.getTypeDescriptor(); 066 067 // When the input is a map, we push a special context object on the stack 068 // before calling the specified operation. This special context object 069 // has two fields 'key' and 'value' that refer to the map entries key 070 // and value, and they can be referenced in the operation 071 // eg. {'a':'y','b':'n'}.![value=='y'?key:null]" == ['a', null] 072 if (operand instanceof Map) { 073 Map<?, ?> mapData = (Map<?, ?>) operand; 074 List<Object> result = new ArrayList<Object>(); 075 for (Map.Entry<?, ?> entry : mapData.entrySet()) { 076 try { 077 state.pushActiveContextObject(new TypedValue(entry)); 078 state.enterScope(); 079 result.add(this.children[0].getValueInternal(state).getValue()); 080 } 081 finally { 082 state.popActiveContextObject(); 083 state.exitScope(); 084 } 085 } 086 return new ValueRef.TypedValueHolderValueRef(new TypedValue(result), this); // TODO unable to build correct type descriptor 087 } 088 089 if (operand instanceof Iterable || operandIsArray) { 090 Iterable<?> data = (operand instanceof Iterable ? 091 (Iterable<?>) operand : Arrays.asList(ObjectUtils.toObjectArray(operand))); 092 093 List<Object> result = new ArrayList<Object>(); 094 int idx = 0; 095 Class<?> arrayElementType = null; 096 for (Object element : data) { 097 try { 098 state.pushActiveContextObject(new TypedValue(element)); 099 state.enterScope("index", idx); 100 Object value = this.children[0].getValueInternal(state).getValue(); 101 if (value != null && operandIsArray) { 102 arrayElementType = determineCommonType(arrayElementType, value.getClass()); 103 } 104 result.add(value); 105 } 106 finally { 107 state.exitScope(); 108 state.popActiveContextObject(); 109 } 110 idx++; 111 } 112 113 if (operandIsArray) { 114 if (arrayElementType == null) { 115 arrayElementType = Object.class; 116 } 117 Object resultArray = Array.newInstance(arrayElementType, result.size()); 118 System.arraycopy(result.toArray(), 0, resultArray, 0, result.size()); 119 return new ValueRef.TypedValueHolderValueRef(new TypedValue(resultArray),this); 120 } 121 122 return new ValueRef.TypedValueHolderValueRef(new TypedValue(result),this); 123 } 124 125 if (operand == null) { 126 if (this.nullSafe) { 127 return ValueRef.NullValueRef.INSTANCE; 128 } 129 throw new SpelEvaluationException(getStartPosition(), SpelMessage.PROJECTION_NOT_SUPPORTED_ON_TYPE, "null"); 130 } 131 132 throw new SpelEvaluationException(getStartPosition(), SpelMessage.PROJECTION_NOT_SUPPORTED_ON_TYPE, 133 operand.getClass().getName()); 134 } 135 136 @Override 137 public String toStringAST() { 138 return "![" + getChild(0).toStringAST() + "]"; 139 } 140 141 private Class<?> determineCommonType(Class<?> oldType, Class<?> newType) { 142 if (oldType == null) { 143 return newType; 144 } 145 if (oldType.isAssignableFrom(newType)) { 146 return oldType; 147 } 148 Class<?> nextType = newType; 149 while (nextType != Object.class) { 150 if (nextType.isAssignableFrom(oldType)) { 151 return nextType; 152 } 153 nextType = nextType.getSuperclass(); 154 } 155 for (Class<?> nextInterface : ClassUtils.getAllInterfacesForClassAsSet(newType)) { 156 if (nextInterface.isAssignableFrom(oldType)) { 157 return nextInterface; 158 } 159 } 160 return Object.class; 161 } 162 163}