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.lang.reflect.Constructor; 021import java.lang.reflect.InvocationTargetException; 022import java.lang.reflect.Modifier; 023import java.util.ArrayList; 024import java.util.List; 025 026import org.springframework.asm.MethodVisitor; 027import org.springframework.core.convert.TypeDescriptor; 028import org.springframework.expression.AccessException; 029import org.springframework.expression.ConstructorExecutor; 030import org.springframework.expression.ConstructorResolver; 031import org.springframework.expression.EvaluationContext; 032import org.springframework.expression.EvaluationException; 033import org.springframework.expression.TypeConverter; 034import org.springframework.expression.TypedValue; 035import org.springframework.expression.common.ExpressionUtils; 036import org.springframework.expression.spel.CodeFlow; 037import org.springframework.expression.spel.ExpressionState; 038import org.springframework.expression.spel.SpelEvaluationException; 039import org.springframework.expression.spel.SpelMessage; 040import org.springframework.expression.spel.SpelNode; 041import org.springframework.expression.spel.support.ReflectiveConstructorExecutor; 042import org.springframework.lang.Nullable; 043import org.springframework.util.Assert; 044 045/** 046 * Represents the invocation of a constructor. Either a constructor on a regular type or 047 * construction of an array. When an array is constructed, an initializer can be specified. 048 * 049 * <p>Examples:<br> 050 * new String('hello world')<br> 051 * new int[]{1,2,3,4}<br> 052 * new int[3] new int[3]{1,2,3} 053 * 054 * @author Andy Clement 055 * @author Juergen Hoeller 056 * @since 3.0 057 */ 058public class ConstructorReference extends SpelNodeImpl { 059 060 private boolean isArrayConstructor = false; 061 062 @Nullable 063 private SpelNodeImpl[] dimensions; 064 065 // TODO is this caching safe - passing the expression around will mean this executor is also being passed around 066 /** The cached executor that may be reused on subsequent evaluations. */ 067 @Nullable 068 private volatile ConstructorExecutor cachedExecutor; 069 070 071 /** 072 * Create a constructor reference. The first argument is the type, the rest are the parameters to the constructor 073 * call 074 */ 075 public ConstructorReference(int startPos, int endPos, SpelNodeImpl... arguments) { 076 super(startPos, endPos, arguments); 077 this.isArrayConstructor = false; 078 } 079 080 /** 081 * Create a constructor reference. The first argument is the type, the rest are the parameters to the constructor 082 * call 083 */ 084 public ConstructorReference(int startPos, int endPos, SpelNodeImpl[] dimensions, SpelNodeImpl... arguments) { 085 super(startPos, endPos, arguments); 086 this.isArrayConstructor = true; 087 this.dimensions = dimensions; 088 } 089 090 091 /** 092 * Implements getValue() - delegating to the code for building an array or a simple type. 093 */ 094 @Override 095 public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { 096 if (this.isArrayConstructor) { 097 return createArray(state); 098 } 099 else { 100 return createNewInstance(state); 101 } 102 } 103 104 /** 105 * Create a new ordinary object and return it. 106 * @param state the expression state within which this expression is being evaluated 107 * @return the new object 108 * @throws EvaluationException if there is a problem creating the object 109 */ 110 private TypedValue createNewInstance(ExpressionState state) throws EvaluationException { 111 Object[] arguments = new Object[getChildCount() - 1]; 112 List<TypeDescriptor> argumentTypes = new ArrayList<>(getChildCount() - 1); 113 for (int i = 0; i < arguments.length; i++) { 114 TypedValue childValue = this.children[i + 1].getValueInternal(state); 115 Object value = childValue.getValue(); 116 arguments[i] = value; 117 argumentTypes.add(TypeDescriptor.forObject(value)); 118 } 119 120 ConstructorExecutor executorToUse = this.cachedExecutor; 121 if (executorToUse != null) { 122 try { 123 return executorToUse.execute(state.getEvaluationContext(), arguments); 124 } 125 catch (AccessException ex) { 126 // Two reasons this can occur: 127 // 1. the method invoked actually threw a real exception 128 // 2. the method invoked was not passed the arguments it expected and has become 'stale' 129 130 // In the first case we should not retry, in the second case we should see if there is a 131 // better suited method. 132 133 // To determine which situation it is, the AccessException will contain a cause. 134 // If the cause is an InvocationTargetException, a user exception was thrown inside the constructor. 135 // Otherwise the constructor could not be invoked. 136 if (ex.getCause() instanceof InvocationTargetException) { 137 // User exception was the root cause - exit now 138 Throwable rootCause = ex.getCause().getCause(); 139 if (rootCause instanceof RuntimeException) { 140 throw (RuntimeException) rootCause; 141 } 142 else { 143 String typeName = (String) this.children[0].getValueInternal(state).getValue(); 144 throw new SpelEvaluationException(getStartPosition(), rootCause, 145 SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM, typeName, 146 FormatHelper.formatMethodForMessage("", argumentTypes)); 147 } 148 } 149 150 // At this point we know it wasn't a user problem so worth a retry if a better candidate can be found 151 this.cachedExecutor = null; 152 } 153 } 154 155 // Either there was no accessor or it no longer exists 156 String typeName = (String) this.children[0].getValueInternal(state).getValue(); 157 Assert.state(typeName != null, "No type name"); 158 executorToUse = findExecutorForConstructor(typeName, argumentTypes, state); 159 try { 160 this.cachedExecutor = executorToUse; 161 if (executorToUse instanceof ReflectiveConstructorExecutor) { 162 this.exitTypeDescriptor = CodeFlow.toDescriptor( 163 ((ReflectiveConstructorExecutor) executorToUse).getConstructor().getDeclaringClass()); 164 165 } 166 return executorToUse.execute(state.getEvaluationContext(), arguments); 167 } 168 catch (AccessException ex) { 169 throw new SpelEvaluationException(getStartPosition(), ex, 170 SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM, typeName, 171 FormatHelper.formatMethodForMessage("", argumentTypes)); 172 } 173 } 174 175 /** 176 * Go through the list of registered constructor resolvers and see if any can find a 177 * constructor that takes the specified set of arguments. 178 * @param typeName the type trying to be constructed 179 * @param argumentTypes the types of the arguments supplied that the constructor must take 180 * @param state the current state of the expression 181 * @return a reusable ConstructorExecutor that can be invoked to run the constructor or null 182 * @throws SpelEvaluationException if there is a problem locating the constructor 183 */ 184 private ConstructorExecutor findExecutorForConstructor(String typeName, 185 List<TypeDescriptor> argumentTypes, ExpressionState state) throws SpelEvaluationException { 186 187 EvaluationContext evalContext = state.getEvaluationContext(); 188 List<ConstructorResolver> ctorResolvers = evalContext.getConstructorResolvers(); 189 for (ConstructorResolver ctorResolver : ctorResolvers) { 190 try { 191 ConstructorExecutor ce = ctorResolver.resolve(state.getEvaluationContext(), typeName, argumentTypes); 192 if (ce != null) { 193 return ce; 194 } 195 } 196 catch (AccessException ex) { 197 throw new SpelEvaluationException(getStartPosition(), ex, 198 SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM, typeName, 199 FormatHelper.formatMethodForMessage("", argumentTypes)); 200 } 201 } 202 throw new SpelEvaluationException(getStartPosition(), SpelMessage.CONSTRUCTOR_NOT_FOUND, typeName, 203 FormatHelper.formatMethodForMessage("", argumentTypes)); 204 } 205 206 @Override 207 public String toStringAST() { 208 StringBuilder sb = new StringBuilder("new "); 209 int index = 0; 210 sb.append(getChild(index++).toStringAST()); 211 sb.append("("); 212 for (int i = index; i < getChildCount(); i++) { 213 if (i > index) { 214 sb.append(","); 215 } 216 sb.append(getChild(i).toStringAST()); 217 } 218 sb.append(")"); 219 return sb.toString(); 220 } 221 222 /** 223 * Create an array and return it. 224 * @param state the expression state within which this expression is being evaluated 225 * @return the new array 226 * @throws EvaluationException if there is a problem creating the array 227 */ 228 private TypedValue createArray(ExpressionState state) throws EvaluationException { 229 // First child gives us the array type which will either be a primitive or reference type 230 Object intendedArrayType = getChild(0).getValue(state); 231 if (!(intendedArrayType instanceof String)) { 232 throw new SpelEvaluationException(getChild(0).getStartPosition(), 233 SpelMessage.TYPE_NAME_EXPECTED_FOR_ARRAY_CONSTRUCTION, 234 FormatHelper.formatClassNameForMessage( 235 intendedArrayType != null ? intendedArrayType.getClass() : null)); 236 } 237 String type = (String) intendedArrayType; 238 Class<?> componentType; 239 TypeCode arrayTypeCode = TypeCode.forName(type); 240 if (arrayTypeCode == TypeCode.OBJECT) { 241 componentType = state.findType(type); 242 } 243 else { 244 componentType = arrayTypeCode.getType(); 245 } 246 Object newArray; 247 if (!hasInitializer()) { 248 // Confirm all dimensions were specified (for example [3][][5] is missing the 2nd dimension) 249 if (this.dimensions != null) { 250 for (SpelNodeImpl dimension : this.dimensions) { 251 if (dimension == null) { 252 throw new SpelEvaluationException(getStartPosition(), SpelMessage.MISSING_ARRAY_DIMENSION); 253 } 254 } 255 } 256 TypeConverter typeConverter = state.getEvaluationContext().getTypeConverter(); 257 258 // Shortcut for 1 dimensional 259 if (this.dimensions.length == 1) { 260 TypedValue o = this.dimensions[0].getTypedValue(state); 261 int arraySize = ExpressionUtils.toInt(typeConverter, o); 262 newArray = Array.newInstance(componentType, arraySize); 263 } 264 else { 265 // Multi-dimensional - hold onto your hat! 266 int[] dims = new int[this.dimensions.length]; 267 for (int d = 0; d < this.dimensions.length; d++) { 268 TypedValue o = this.dimensions[d].getTypedValue(state); 269 dims[d] = ExpressionUtils.toInt(typeConverter, o); 270 } 271 newArray = Array.newInstance(componentType, dims); 272 } 273 } 274 else { 275 // There is an initializer 276 if (this.dimensions == null || this.dimensions.length > 1) { 277 // There is an initializer but this is a multi-dimensional array (e.g. new int[][]{{1,2},{3,4}}) - this 278 // is not currently supported 279 throw new SpelEvaluationException(getStartPosition(), 280 SpelMessage.MULTIDIM_ARRAY_INITIALIZER_NOT_SUPPORTED); 281 } 282 TypeConverter typeConverter = state.getEvaluationContext().getTypeConverter(); 283 InlineList initializer = (InlineList) getChild(1); 284 // If a dimension was specified, check it matches the initializer length 285 if (this.dimensions[0] != null) { 286 TypedValue dValue = this.dimensions[0].getTypedValue(state); 287 int i = ExpressionUtils.toInt(typeConverter, dValue); 288 if (i != initializer.getChildCount()) { 289 throw new SpelEvaluationException(getStartPosition(), SpelMessage.INITIALIZER_LENGTH_INCORRECT); 290 } 291 } 292 // Build the array and populate it 293 int arraySize = initializer.getChildCount(); 294 newArray = Array.newInstance(componentType, arraySize); 295 if (arrayTypeCode == TypeCode.OBJECT) { 296 populateReferenceTypeArray(state, newArray, typeConverter, initializer, componentType); 297 } 298 else if (arrayTypeCode == TypeCode.BOOLEAN) { 299 populateBooleanArray(state, newArray, typeConverter, initializer); 300 } 301 else if (arrayTypeCode == TypeCode.BYTE) { 302 populateByteArray(state, newArray, typeConverter, initializer); 303 } 304 else if (arrayTypeCode == TypeCode.CHAR) { 305 populateCharArray(state, newArray, typeConverter, initializer); 306 } 307 else if (arrayTypeCode == TypeCode.DOUBLE) { 308 populateDoubleArray(state, newArray, typeConverter, initializer); 309 } 310 else if (arrayTypeCode == TypeCode.FLOAT) { 311 populateFloatArray(state, newArray, typeConverter, initializer); 312 } 313 else if (arrayTypeCode == TypeCode.INT) { 314 populateIntArray(state, newArray, typeConverter, initializer); 315 } 316 else if (arrayTypeCode == TypeCode.LONG) { 317 populateLongArray(state, newArray, typeConverter, initializer); 318 } 319 else if (arrayTypeCode == TypeCode.SHORT) { 320 populateShortArray(state, newArray, typeConverter, initializer); 321 } 322 else { 323 throw new IllegalStateException(arrayTypeCode.name()); 324 } 325 } 326 return new TypedValue(newArray); 327 } 328 329 private void populateReferenceTypeArray(ExpressionState state, Object newArray, TypeConverter typeConverter, 330 InlineList initializer, Class<?> componentType) { 331 332 TypeDescriptor toTypeDescriptor = TypeDescriptor.valueOf(componentType); 333 Object[] newObjectArray = (Object[]) newArray; 334 for (int i = 0; i < newObjectArray.length; i++) { 335 SpelNode elementNode = initializer.getChild(i); 336 Object arrayEntry = elementNode.getValue(state); 337 newObjectArray[i] = typeConverter.convertValue(arrayEntry, 338 TypeDescriptor.forObject(arrayEntry), toTypeDescriptor); 339 } 340 } 341 342 private void populateByteArray(ExpressionState state, Object newArray, TypeConverter typeConverter, 343 InlineList initializer) { 344 345 byte[] newByteArray = (byte[]) newArray; 346 for (int i = 0; i < newByteArray.length; i++) { 347 TypedValue typedValue = initializer.getChild(i).getTypedValue(state); 348 newByteArray[i] = ExpressionUtils.toByte(typeConverter, typedValue); 349 } 350 } 351 352 private void populateFloatArray(ExpressionState state, Object newArray, TypeConverter typeConverter, 353 InlineList initializer) { 354 355 float[] newFloatArray = (float[]) newArray; 356 for (int i = 0; i < newFloatArray.length; i++) { 357 TypedValue typedValue = initializer.getChild(i).getTypedValue(state); 358 newFloatArray[i] = ExpressionUtils.toFloat(typeConverter, typedValue); 359 } 360 } 361 362 private void populateDoubleArray(ExpressionState state, Object newArray, TypeConverter typeConverter, 363 InlineList initializer) { 364 365 double[] newDoubleArray = (double[]) newArray; 366 for (int i = 0; i < newDoubleArray.length; i++) { 367 TypedValue typedValue = initializer.getChild(i).getTypedValue(state); 368 newDoubleArray[i] = ExpressionUtils.toDouble(typeConverter, typedValue); 369 } 370 } 371 372 private void populateShortArray(ExpressionState state, Object newArray, TypeConverter typeConverter, 373 InlineList initializer) { 374 375 short[] newShortArray = (short[]) newArray; 376 for (int i = 0; i < newShortArray.length; i++) { 377 TypedValue typedValue = initializer.getChild(i).getTypedValue(state); 378 newShortArray[i] = ExpressionUtils.toShort(typeConverter, typedValue); 379 } 380 } 381 382 private void populateLongArray(ExpressionState state, Object newArray, TypeConverter typeConverter, 383 InlineList initializer) { 384 385 long[] newLongArray = (long[]) newArray; 386 for (int i = 0; i < newLongArray.length; i++) { 387 TypedValue typedValue = initializer.getChild(i).getTypedValue(state); 388 newLongArray[i] = ExpressionUtils.toLong(typeConverter, typedValue); 389 } 390 } 391 392 private void populateCharArray(ExpressionState state, Object newArray, TypeConverter typeConverter, 393 InlineList initializer) { 394 395 char[] newCharArray = (char[]) newArray; 396 for (int i = 0; i < newCharArray.length; i++) { 397 TypedValue typedValue = initializer.getChild(i).getTypedValue(state); 398 newCharArray[i] = ExpressionUtils.toChar(typeConverter, typedValue); 399 } 400 } 401 402 private void populateBooleanArray(ExpressionState state, Object newArray, TypeConverter typeConverter, 403 InlineList initializer) { 404 405 boolean[] newBooleanArray = (boolean[]) newArray; 406 for (int i = 0; i < newBooleanArray.length; i++) { 407 TypedValue typedValue = initializer.getChild(i).getTypedValue(state); 408 newBooleanArray[i] = ExpressionUtils.toBoolean(typeConverter, typedValue); 409 } 410 } 411 412 private void populateIntArray(ExpressionState state, Object newArray, TypeConverter typeConverter, 413 InlineList initializer) { 414 415 int[] newIntArray = (int[]) newArray; 416 for (int i = 0; i < newIntArray.length; i++) { 417 TypedValue typedValue = initializer.getChild(i).getTypedValue(state); 418 newIntArray[i] = ExpressionUtils.toInt(typeConverter, typedValue); 419 } 420 } 421 422 private boolean hasInitializer() { 423 return (getChildCount() > 1); 424 } 425 426 @Override 427 public boolean isCompilable() { 428 if (!(this.cachedExecutor instanceof ReflectiveConstructorExecutor) || 429 this.exitTypeDescriptor == null) { 430 return false; 431 } 432 433 if (getChildCount() > 1) { 434 for (int c = 1, max = getChildCount();c < max; c++) { 435 if (!this.children[c].isCompilable()) { 436 return false; 437 } 438 } 439 } 440 441 ReflectiveConstructorExecutor executor = (ReflectiveConstructorExecutor) this.cachedExecutor; 442 if (executor == null) { 443 return false; 444 } 445 Constructor<?> constructor = executor.getConstructor(); 446 return (Modifier.isPublic(constructor.getModifiers()) && 447 Modifier.isPublic(constructor.getDeclaringClass().getModifiers())); 448 } 449 450 @Override 451 public void generateCode(MethodVisitor mv, CodeFlow cf) { 452 ReflectiveConstructorExecutor executor = ((ReflectiveConstructorExecutor) this.cachedExecutor); 453 Assert.state(executor != null, "No cached executor"); 454 455 Constructor<?> constructor = executor.getConstructor(); 456 String classDesc = constructor.getDeclaringClass().getName().replace('.', '/'); 457 mv.visitTypeInsn(NEW, classDesc); 458 mv.visitInsn(DUP); 459 460 // children[0] is the type of the constructor, don't want to include that in argument processing 461 SpelNodeImpl[] arguments = new SpelNodeImpl[this.children.length - 1]; 462 System.arraycopy(this.children, 1, arguments, 0, this.children.length - 1); 463 generateCodeForArguments(mv, cf, constructor, arguments); 464 mv.visitMethodInsn(INVOKESPECIAL, classDesc, "<init>", CodeFlow.createSignatureDescriptor(constructor), false); 465 cf.pushDescriptor(this.exitTypeDescriptor); 466 } 467 468}