001/* 002 * Copyright 2002-2018 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.Constructor; 020import java.lang.reflect.Member; 021import java.lang.reflect.Method; 022 023import org.springframework.asm.MethodVisitor; 024import org.springframework.asm.Opcodes; 025import org.springframework.expression.EvaluationException; 026import org.springframework.expression.TypedValue; 027import org.springframework.expression.common.ExpressionUtils; 028import org.springframework.expression.spel.CodeFlow; 029import org.springframework.expression.spel.ExpressionState; 030import org.springframework.expression.spel.SpelEvaluationException; 031import org.springframework.expression.spel.SpelMessage; 032import org.springframework.expression.spel.SpelNode; 033import org.springframework.expression.spel.support.StandardEvaluationContext; 034import org.springframework.util.Assert; 035import org.springframework.util.ObjectUtils; 036 037/** 038 * The common supertype of all AST nodes in a parsed Spring Expression Language 039 * format expression. 040 * 041 * @author Andy Clement 042 * @since 3.0 043 */ 044public abstract class SpelNodeImpl implements SpelNode, Opcodes { 045 046 private static final SpelNodeImpl[] NO_CHILDREN = new SpelNodeImpl[0]; 047 048 049 protected int pos; // start = top 16bits, end = bottom 16bits 050 051 protected SpelNodeImpl[] children = SpelNodeImpl.NO_CHILDREN; 052 053 private SpelNodeImpl parent; 054 055 /** 056 * Indicates the type descriptor for the result of this expression node. 057 * This is set as soon as it is known. For a literal node it is known immediately. 058 * For a property access or method invocation it is known after one evaluation of 059 * that node. 060 * <p>The descriptor is like the bytecode form but is slightly easier to work with. 061 * It does not include the trailing semicolon (for non array reference types). 062 * Some examples: Ljava/lang/String, I, [I 063 */ 064 protected volatile String exitTypeDescriptor; 065 066 067 public SpelNodeImpl(int pos, SpelNodeImpl... operands) { 068 this.pos = pos; 069 if (!ObjectUtils.isEmpty(operands)) { 070 this.children = operands; 071 for (SpelNodeImpl operand : operands) { 072 operand.parent = this; 073 } 074 } 075 } 076 077 078 @Deprecated 079 protected SpelNodeImpl getPreviousChild() { 080 SpelNodeImpl result = null; 081 if (this.parent != null) { 082 for (SpelNodeImpl child : this.parent.children) { 083 if (this == child) { 084 break; 085 } 086 result = child; 087 } 088 } 089 return result; 090 } 091 092 /** 093 * @return true if the next child is one of the specified classes 094 */ 095 protected boolean nextChildIs(Class<?>... clazzes) { 096 if (this.parent != null) { 097 SpelNodeImpl[] peers = this.parent.children; 098 for (int i = 0, max = peers.length; i < max; i++) { 099 if (this == peers[i]) { 100 if (i + 1 >= max) { 101 return false; 102 } 103 Class<?> clazz = peers[i + 1].getClass(); 104 for (Class<?> desiredClazz : clazzes) { 105 if (clazz.equals(desiredClazz)) { 106 return true; 107 } 108 } 109 return false; 110 } 111 } 112 } 113 return false; 114 } 115 116 @Override 117 public final Object getValue(ExpressionState expressionState) throws EvaluationException { 118 if (expressionState != null) { 119 return getValueInternal(expressionState).getValue(); 120 } 121 else { 122 // configuration not set - does that matter? 123 return getValue(new ExpressionState(new StandardEvaluationContext())); 124 } 125 } 126 127 @Override 128 public final TypedValue getTypedValue(ExpressionState expressionState) throws EvaluationException { 129 if (expressionState != null) { 130 return getValueInternal(expressionState); 131 } 132 else { 133 // configuration not set - does that matter? 134 return getTypedValue(new ExpressionState(new StandardEvaluationContext())); 135 } 136 } 137 138 // by default Ast nodes are not writable 139 @Override 140 public boolean isWritable(ExpressionState expressionState) throws EvaluationException { 141 return false; 142 } 143 144 @Override 145 public void setValue(ExpressionState expressionState, Object newValue) throws EvaluationException { 146 throw new SpelEvaluationException(getStartPosition(), 147 SpelMessage.SETVALUE_NOT_SUPPORTED, getClass()); 148 } 149 150 @Override 151 public SpelNode getChild(int index) { 152 return this.children[index]; 153 } 154 155 @Override 156 public int getChildCount() { 157 return this.children.length; 158 } 159 160 @Override 161 public Class<?> getObjectClass(Object obj) { 162 if (obj == null) { 163 return null; 164 } 165 return (obj instanceof Class ? ((Class<?>) obj) : obj.getClass()); 166 } 167 168 protected final <T> T getValue(ExpressionState state, Class<T> desiredReturnType) throws EvaluationException { 169 return ExpressionUtils.convertTypedValue(state.getEvaluationContext(), getValueInternal(state), desiredReturnType); 170 } 171 172 @Override 173 public int getStartPosition() { 174 return (this.pos >> 16); 175 } 176 177 @Override 178 public int getEndPosition() { 179 return (this.pos & 0xffff); 180 } 181 182 protected ValueRef getValueRef(ExpressionState state) throws EvaluationException { 183 throw new SpelEvaluationException(this.pos, SpelMessage.NOT_ASSIGNABLE, toStringAST()); 184 } 185 186 /** 187 * Check whether a node can be compiled to bytecode. The reasoning in each node may 188 * be different but will typically involve checking whether the exit type descriptor 189 * of the node is known and any relevant child nodes are compilable. 190 * @return {@code true} if this node can be compiled to bytecode 191 */ 192 public boolean isCompilable() { 193 return false; 194 } 195 196 /** 197 * Generate the bytecode for this node into the supplied visitor. Context info about 198 * the current expression being compiled is available in the codeflow object. For 199 * example it will include information about the type of the object currently 200 * on the stack. 201 * @param mv the ASM MethodVisitor into which code should be generated 202 * @param cf a context object with info about what is on the stack 203 */ 204 public void generateCode(MethodVisitor mv, CodeFlow cf) { 205 throw new IllegalStateException(getClass().getName() +" has no generateCode(..) method"); 206 } 207 208 public String getExitDescriptor() { 209 return this.exitTypeDescriptor; 210 } 211 212 public abstract TypedValue getValueInternal(ExpressionState expressionState) throws EvaluationException; 213 214 215 /** 216 * Generate code that handles building the argument values for the specified method. 217 * This method will take account of whether the invoked method is a varargs method 218 * and if it is then the argument values will be appropriately packaged into an array. 219 * @param mv the method visitor where code should be generated 220 * @param cf the current codeflow 221 * @param member the method or constructor for which arguments are being setup 222 * @param arguments the expression nodes for the expression supplied argument values 223 */ 224 protected static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Member member, SpelNodeImpl[] arguments) { 225 String[] paramDescriptors = null; 226 boolean isVarargs = false; 227 if (member instanceof Constructor) { 228 Constructor<?> ctor = (Constructor<?>) member; 229 paramDescriptors = CodeFlow.toDescriptors(ctor.getParameterTypes()); 230 isVarargs = ctor.isVarArgs(); 231 } 232 else { // Method 233 Method method = (Method)member; 234 paramDescriptors = CodeFlow.toDescriptors(method.getParameterTypes()); 235 isVarargs = method.isVarArgs(); 236 } 237 if (isVarargs) { 238 // The final parameter may or may not need packaging into an array, or nothing may 239 // have been passed to satisfy the varargs and so something needs to be built. 240 int p = 0; // Current supplied argument being processed 241 int childCount = arguments.length; 242 243 // Fulfill all the parameter requirements except the last one 244 for (p = 0; p < paramDescriptors.length - 1; p++) { 245 generateCodeForArgument(mv, cf, arguments[p], paramDescriptors[p]); 246 } 247 248 SpelNodeImpl lastChild = (childCount == 0 ? null : arguments[childCount - 1]); 249 String arrayType = paramDescriptors[paramDescriptors.length - 1]; 250 // Determine if the final passed argument is already suitably packaged in array 251 // form to be passed to the method 252 if (lastChild != null && arrayType.equals(lastChild.getExitDescriptor())) { 253 generateCodeForArgument(mv, cf, lastChild, paramDescriptors[p]); 254 } 255 else { 256 arrayType = arrayType.substring(1); // trim the leading '[', may leave other '[' 257 // build array big enough to hold remaining arguments 258 CodeFlow.insertNewArrayCode(mv, childCount - p, arrayType); 259 // Package up the remaining arguments into the array 260 int arrayindex = 0; 261 while (p < childCount) { 262 SpelNodeImpl child = arguments[p]; 263 mv.visitInsn(DUP); 264 CodeFlow.insertOptimalLoad(mv, arrayindex++); 265 generateCodeForArgument(mv, cf, child, arrayType); 266 CodeFlow.insertArrayStore(mv, arrayType); 267 p++; 268 } 269 } 270 } 271 else { 272 for (int i = 0; i < paramDescriptors.length;i++) { 273 generateCodeForArgument(mv, cf, arguments[i], paramDescriptors[i]); 274 } 275 } 276 } 277 278 /** 279 * Ask an argument to generate its bytecode and then follow it up 280 * with any boxing/unboxing/checkcasting to ensure it matches the expected parameter descriptor. 281 */ 282 protected static void generateCodeForArgument(MethodVisitor mv, CodeFlow cf, SpelNodeImpl argument, String paramDesc) { 283 cf.enterCompilationScope(); 284 argument.generateCode(mv, cf); 285 String lastDesc = cf.lastDescriptor(); 286 boolean primitiveOnStack = CodeFlow.isPrimitive(lastDesc); 287 // Check if need to box it for the method reference? 288 if (primitiveOnStack && paramDesc.charAt(0) == 'L') { 289 CodeFlow.insertBoxIfNecessary(mv, lastDesc.charAt(0)); 290 } 291 else if (paramDesc.length() == 1 && !primitiveOnStack) { 292 CodeFlow.insertUnboxInsns(mv, paramDesc.charAt(0), lastDesc); 293 } 294 else if (!paramDesc.equals(lastDesc)) { 295 // This would be unnecessary in the case of subtyping (e.g. method takes Number but Integer passed in) 296 CodeFlow.insertCheckCast(mv, paramDesc); 297 } 298 cf.exitCompilationScope(); 299 } 300}