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