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