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