View Javadoc
1   /*
2    * Copyright 2002-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.expression.spel.ast;
18  
19  import java.lang.reflect.Array;
20  import java.lang.reflect.Constructor;
21  import java.lang.reflect.InvocationTargetException;
22  import java.lang.reflect.Modifier;
23  import java.util.ArrayList;
24  import java.util.List;
25  
26  import org.springframework.asm.MethodVisitor;
27  import org.springframework.core.convert.TypeDescriptor;
28  import org.springframework.expression.AccessException;
29  import org.springframework.expression.ConstructorExecutor;
30  import org.springframework.expression.ConstructorResolver;
31  import org.springframework.expression.EvaluationContext;
32  import org.springframework.expression.EvaluationException;
33  import org.springframework.expression.TypeConverter;
34  import org.springframework.expression.TypedValue;
35  import org.springframework.expression.common.ExpressionUtils;
36  import org.springframework.expression.spel.CodeFlow;
37  import org.springframework.expression.spel.ExpressionState;
38  import org.springframework.expression.spel.SpelEvaluationException;
39  import org.springframework.expression.spel.SpelMessage;
40  import org.springframework.expression.spel.SpelNode;
41  import org.springframework.expression.spel.support.ReflectiveConstructorExecutor;
42  
43  /**
44   * Represents the invocation of a constructor. Either a constructor on a regular type or
45   * construction of an array. When an array is constructed, an initializer can be specified.
46   *
47   * <p>Examples:<br>
48   * new String('hello world')<br>
49   * new int[]{1,2,3,4}<br>
50   * new int[3] new int[3]{1,2,3}
51   *
52   * @author Andy Clement
53   * @author Juergen Hoeller
54   * @since 3.0
55   */
56  public class ConstructorReference extends SpelNodeImpl {
57  
58  	private boolean isArrayConstructor = false;
59  
60  	private SpelNodeImpl[] dimensions;
61  
62  	// TODO is this caching safe - passing the expression around will mean this executor is also being passed around
63  	/**
64  	 * The cached executor that may be reused on subsequent evaluations.
65  	 */
66  	private volatile ConstructorExecutor cachedExecutor;
67  
68  
69  	/**
70  	 * Create a constructor reference. The first argument is the type, the rest are the parameters to the constructor
71  	 * call
72  	 */
73  	public ConstructorReference(int pos, SpelNodeImpl... arguments) {
74  		super(pos, arguments);
75  		this.isArrayConstructor = false;
76  	}
77  
78  	/**
79  	 * Create a constructor reference. The first argument is the type, the rest are the parameters to the constructor
80  	 * call
81  	 */
82  	public ConstructorReference(int pos, SpelNodeImpl[] dimensions, SpelNodeImpl... arguments) {
83  		super(pos, arguments);
84  		this.isArrayConstructor = true;
85  		this.dimensions = dimensions;
86  	}
87  
88  
89  	/**
90  	 * Implements getValue() - delegating to the code for building an array or a simple type.
91  	 */
92  	@Override
93  	public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
94  		if (this.isArrayConstructor) {
95  			return createArray(state);
96  		}
97  		else {
98  			return createNewInstance(state);
99  		}
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.INT) {
296 				populateIntArray(state, newArray, typeConverter, initializer);
297 			}
298 			else if (arrayTypeCode == TypeCode.BOOLEAN) {
299 				populateBooleanArray(state, newArray, typeConverter, initializer);
300 			}
301 			else if (arrayTypeCode == TypeCode.CHAR) {
302 				populateCharArray(state, newArray, typeConverter, initializer);
303 			}
304 			else if (arrayTypeCode == TypeCode.LONG) {
305 				populateLongArray(state, newArray, typeConverter, initializer);
306 			}
307 			else if (arrayTypeCode == TypeCode.SHORT) {
308 				populateShortArray(state, newArray, typeConverter, initializer);
309 			}
310 			else if (arrayTypeCode == TypeCode.DOUBLE) {
311 				populateDoubleArray(state, newArray, typeConverter, initializer);
312 			}
313 			else if (arrayTypeCode == TypeCode.FLOAT) {
314 				populateFloatArray(state, newArray, typeConverter, initializer);
315 			}
316 			else if (arrayTypeCode == TypeCode.BYTE) {
317 				populateByteArray(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 classSlashedDescriptor = constructor.getDeclaringClass().getName().replace('.', '/');
449 		mv.visitTypeInsn(NEW, classSlashedDescriptor);
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, classSlashedDescriptor, "<init>",
456 				CodeFlow.createSignatureDescriptor(constructor), false);
457 		cf.pushDescriptor(this.exitTypeDescriptor);
458 	}
459 
460 }