Skip to content

Commit c33df59

Browse files
committed
pass full TypeDescriptor context through to ConversionService calls (SPR-7519)
1 parent 6f69b7b commit c33df59

19 files changed

+334
-178
lines changed

org.springframework.expression/src/main/java/org/springframework/expression/ConstructorResolver.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2010 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,25 +16,29 @@
1616

1717
package org.springframework.expression;
1818

19+
import java.util.List;
20+
21+
import org.springframework.core.convert.TypeDescriptor;
22+
1923
/**
2024
* A constructor resolver attempts locate a constructor and returns a ConstructorExecutor that can be used to invoke
2125
* that constructor. The ConstructorExecutor will be cached but if it 'goes stale' the resolvers will be called again.
22-
*
26+
*
2327
* @author Andy Clement
2428
* @since 3.0
2529
*/
2630
public interface ConstructorResolver {
2731

2832
/**
29-
* Within the supplied context determine a suitable constructor on the supplied type that can handle the specified
30-
* arguments. Return a ConstructorExecutor that can be used to invoke that constructor (or null if no constructor
31-
* could be found).
33+
* Within the supplied context determine a suitable constructor on the supplied type that can handle the
34+
* specified arguments. Return a ConstructorExecutor that can be used to invoke that constructor
35+
* (or <code>null</code> if no constructor could be found).
3236
* @param context the current evaluation context
3337
* @param typeName the type upon which to look for the constructor
3438
* @param argumentTypes the arguments that the constructor must be able to handle
3539
* @return a ConstructorExecutor that can invoke the constructor, or null if non found
3640
*/
37-
ConstructorExecutor resolve(EvaluationContext context, String typeName, Class<?>[] argumentTypes)
41+
ConstructorExecutor resolve(EvaluationContext context, String typeName, List<TypeDescriptor> argumentTypes)
3842
throws AccessException;
3943

4044
}

org.springframework.expression/src/main/java/org/springframework/expression/MethodResolver.java

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2010 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,24 +16,29 @@
1616

1717
package org.springframework.expression;
1818

19+
import java.util.List;
20+
21+
import org.springframework.core.convert.TypeDescriptor;
22+
1923
/**
20-
* A method resolver attempts locate a method and returns a command executor that can be used to invoke that method. The
21-
* command executor will be cached but if it 'goes stale' the resolvers will be called again.
22-
*
24+
* A method resolver attempts locate a method and returns a command executor that can be used to invoke that method.
25+
* The command executor will be cached but if it 'goes stale' the resolvers will be called again.
26+
*
2327
* @author Andy Clement
2428
* @since 3.0
2529
*/
2630
public interface MethodResolver {
2731

2832
/**
29-
* Within the supplied context determine a suitable method on the supplied object that can handle the specified
30-
* arguments. Return a MethodExecutor that can be used to invoke that method (or null if no method
31-
* could be found).
33+
* Within the supplied context determine a suitable method on the supplied object that can handle the
34+
* specified arguments. Return a MethodExecutor that can be used to invoke that method
35+
* (or <code>null</code> if no method could be found).
3236
* @param context the current evaluation context
3337
* @param targetObject the object upon which the method is being called
3438
* @param argumentTypes the arguments that the constructor must be able to handle
3539
* @return a MethodExecutor that can invoke the method, or null if the method cannot be found
3640
*/
37-
MethodExecutor resolve(EvaluationContext context, Object targetObject, String name, Class<?>[] argumentTypes) throws AccessException;
41+
MethodExecutor resolve(EvaluationContext context, Object targetObject, String name,
42+
List<TypeDescriptor> argumentTypes) throws AccessException;
3843

3944
}

org.springframework.expression/src/main/java/org/springframework/expression/TypeConverter.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2010 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,14 +30,6 @@
3030
*/
3131
public interface TypeConverter {
3232

33-
/**
34-
* Return true if the type converter can convert the specified type to the desired target type.
35-
* @param sourceType the type to be converted from
36-
* @param targetType the type to be converted to
37-
* @return true if that conversion can be performed
38-
*/
39-
boolean canConvert(Class<?> sourceType, Class<?> targetType);
40-
4133
/**
4234
* Return true if the type converter can convert the specified type to the desired target type.
4335
* @param sourceType a type descriptor that describes the source type

org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java

Lines changed: 61 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2010 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
1818

1919
import java.lang.reflect.Array;
2020
import java.util.List;
21+
import java.util.ArrayList;
2122

2223
import org.springframework.core.convert.TypeDescriptor;
2324
import org.springframework.expression.AccessException;
@@ -48,14 +49,16 @@
4849
*/
4950
public class ConstructorReference extends SpelNodeImpl {
5051

52+
private boolean isArrayConstructor = false;
53+
54+
private SpelNodeImpl[] dimensions;
55+
5156
// TODO is this caching safe - passing the expression around will mean this executor is also being passed around
5257
/**
5358
* The cached executor that may be reused on subsequent evaluations.
5459
*/
5560
private volatile ConstructorExecutor cachedExecutor;
5661

57-
private boolean isArrayConstructor = false;
58-
private SpelNodeImpl[] dimensions;
5962

6063
/**
6164
* Create a constructor reference. The first argument is the type, the rest are the parameters to the constructor
@@ -76,40 +79,42 @@ public ConstructorReference(int pos, SpelNodeImpl[] dimensions, SpelNodeImpl...
7679
this.dimensions = dimensions;
7780
}
7881

82+
7983
/**
8084
* Implements getValue() - delegating to the code for building an array or a simple type.
8185
*/
8286
@Override
8387
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
84-
if (isArrayConstructor) {
88+
if (this.isArrayConstructor) {
8589
return createArray(state);
86-
} else {
90+
}
91+
else {
8792
return createNewInstance(state);
8893
}
8994
}
9095

9196
/**
9297
* Create a new ordinary object and return it.
93-
*
9498
* @param state the expression state within which this expression is being evaluated
9599
* @return the new object
96100
* @throws EvaluationException if there is a problem creating the object
97101
*/
98102
private TypedValue createNewInstance(ExpressionState state) throws EvaluationException {
99103
Object[] arguments = new Object[getChildCount() - 1];
100-
Class<?>[] argumentTypes = new Class[getChildCount() - 1];
104+
List<TypeDescriptor> argumentTypes = new ArrayList<TypeDescriptor>(getChildCount() - 1);
101105
for (int i = 0; i < arguments.length; i++) {
102-
TypedValue childValue = children[i + 1].getValueInternal(state);
106+
TypedValue childValue = this.children[i + 1].getValueInternal(state);
103107
Object value = childValue.getValue();
104108
arguments[i] = value;
105-
argumentTypes[i] = (value == null ? null : value.getClass());
109+
argumentTypes.add(TypeDescriptor.forObject(value));
106110
}
107111

108112
ConstructorExecutor executorToUse = this.cachedExecutor;
109113
if (executorToUse != null) {
110114
try {
111115
return executorToUse.execute(state.getEvaluationContext(), arguments);
112-
} catch (AccessException ae) {
116+
}
117+
catch (AccessException ae) {
113118
// Two reasons this can occur:
114119
// 1. the method invoked actually threw a real exception
115120
// 2. the method invoked was not passed the arguments it expected and has become 'stale'
@@ -129,7 +134,7 @@ private TypedValue createNewInstance(ExpressionState state) throws EvaluationExc
129134
if (rootCause instanceof RuntimeException) {
130135
throw (RuntimeException) rootCause;
131136
} else {
132-
String typename = (String) children[0].getValueInternal(state).getValue();
137+
String typename = (String) this.children[0].getValueInternal(state).getValue();
133138
throw new SpelEvaluationException(getStartPosition(), rootCause,
134139
SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM, typename, FormatHelper
135140
.formatMethodForMessage("", argumentTypes));
@@ -142,13 +147,13 @@ private TypedValue createNewInstance(ExpressionState state) throws EvaluationExc
142147
}
143148

144149
// either there was no accessor or it no longer exists
145-
String typename = (String) children[0].getValueInternal(state).getValue();
150+
String typename = (String) this.children[0].getValueInternal(state).getValue();
146151
executorToUse = findExecutorForConstructor(typename, argumentTypes, state);
147152
try {
148153
this.cachedExecutor = executorToUse;
149-
TypedValue result = executorToUse.execute(state.getEvaluationContext(), arguments);
150-
return result;
151-
} catch (AccessException ae) {
154+
return executorToUse.execute(state.getEvaluationContext(), arguments);
155+
}
156+
catch (AccessException ae) {
152157
throw new SpelEvaluationException(getStartPosition(), ae, SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM,
153158
typename, FormatHelper.formatMethodForMessage("", argumentTypes));
154159

@@ -158,14 +163,13 @@ private TypedValue createNewInstance(ExpressionState state) throws EvaluationExc
158163
/**
159164
* Go through the list of registered constructor resolvers and see if any can find a constructor that takes the
160165
* specified set of arguments.
161-
*
162166
* @param typename the type trying to be constructed
163167
* @param argumentTypes the types of the arguments supplied that the constructor must take
164168
* @param state the current state of the expression
165169
* @return a reusable ConstructorExecutor that can be invoked to run the constructor or null
166170
* @throws SpelEvaluationException if there is a problem locating the constructor
167171
*/
168-
private ConstructorExecutor findExecutorForConstructor(String typename, Class<?>[] argumentTypes,
172+
private ConstructorExecutor findExecutorForConstructor(String typename, List<TypeDescriptor> argumentTypes,
169173
ExpressionState state) throws SpelEvaluationException {
170174

171175
EvaluationContext eContext = state.getEvaluationContext();
@@ -178,10 +182,11 @@ private ConstructorExecutor findExecutorForConstructor(String typename, Class<?>
178182
if (cEx != null) {
179183
return cEx;
180184
}
181-
} catch (AccessException ex) {
185+
}
186+
catch (AccessException ex) {
182187
throw new SpelEvaluationException(getStartPosition(), ex,
183-
SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM, typename, FormatHelper.formatMethodForMessage(
184-
"", argumentTypes));
188+
SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM, typename,
189+
FormatHelper.formatMethodForMessage("", argumentTypes));
185190
}
186191
}
187192
}
@@ -208,13 +213,11 @@ public String toStringAST() {
208213

209214
/**
210215
* Create an array and return it.
211-
*
212216
* @param state the expression state within which this expression is being evaluated
213217
* @return the new array
214218
* @throws EvaluationException if there is a problem creating the array
215219
*/
216220
private TypedValue createArray(ExpressionState state) throws EvaluationException {
217-
218221
// First child gives us the array type which will either be a primitive or reference type
219222
Object intendedArrayType = getChild(0).getValue(state);
220223
if (!(intendedArrayType instanceof String)) {
@@ -223,44 +226,44 @@ private TypedValue createArray(ExpressionState state) throws EvaluationException
223226
.formatClassNameForMessage(intendedArrayType.getClass()));
224227
}
225228
String type = (String) intendedArrayType;
226-
Class<?> componentType = null;
229+
Class<?> componentType;
227230
TypeCode arrayTypeCode = TypeCode.forName(type);
228231
if (arrayTypeCode == TypeCode.OBJECT) {
229232
componentType = state.findType(type);
230-
} else {
233+
}
234+
else {
231235
componentType = arrayTypeCode.getType();
232236
}
233-
234237
TypeDescriptor td = TypeDescriptor.valueOf(componentType);
235-
236-
Object newArray = null;
237-
238+
Object newArray;
238239
if (!hasInitializer()) {
239240
// Confirm all dimensions were specified (for example [3][][5] is missing the 2nd dimension)
240-
for (int i = 0; i < dimensions.length; i++) {
241-
if (dimensions[i] == null) {
241+
for (SpelNodeImpl dimension : this.dimensions) {
242+
if (dimension == null) {
242243
throw new SpelEvaluationException(getStartPosition(), SpelMessage.MISSING_ARRAY_DIMENSION);
243244
}
244245
}
245246
TypeConverter typeConverter = state.getEvaluationContext().getTypeConverter();
246247

247248
// Shortcut for 1 dimensional
248-
if (dimensions.length == 1) {
249-
TypedValue o = dimensions[0].getTypedValue(state);
249+
if (this.dimensions.length == 1) {
250+
TypedValue o = this.dimensions[0].getTypedValue(state);
250251
int arraySize = ExpressionUtils.toInt(typeConverter, o);
251252
newArray = Array.newInstance(componentType, arraySize);
252-
} else {
253+
}
254+
else {
253255
// Multi-dimensional - hold onto your hat!
254-
int[] dims = new int[dimensions.length];
255-
for (int d = 0; d < dimensions.length; d++) {
256-
TypedValue o = dimensions[d].getTypedValue(state);
256+
int[] dims = new int[this.dimensions.length];
257+
for (int d = 0; d < this.dimensions.length; d++) {
258+
TypedValue o = this.dimensions[d].getTypedValue(state);
257259
dims[d] = ExpressionUtils.toInt(typeConverter, o);
258260
}
259261
newArray = Array.newInstance(componentType, dims);
260262
}
261-
} else {
263+
}
264+
else {
262265
// There is an initializer
263-
if (dimensions.length > 1) {
266+
if (this.dimensions.length > 1) {
264267
// There is an initializer but this is a multi-dimensional array (e.g. new int[][]{{1,2},{3,4}}) - this
265268
// is not currently supported
266269
throw new SpelEvaluationException(getStartPosition(),
@@ -269,8 +272,8 @@ private TypedValue createArray(ExpressionState state) throws EvaluationException
269272
TypeConverter typeConverter = state.getEvaluationContext().getTypeConverter();
270273
InlineList initializer = (InlineList) getChild(1);
271274
// If a dimension was specified, check it matches the initializer length
272-
if (dimensions[0] != null) {
273-
TypedValue dValue = dimensions[0].getTypedValue(state);
275+
if (this.dimensions[0] != null) {
276+
TypedValue dValue = this.dimensions[0].getTypedValue(state);
274277
int i = ExpressionUtils.toInt(typeConverter, dValue);
275278
if (i != initializer.getChildCount()) {
276279
throw new SpelEvaluationException(getStartPosition(), SpelMessage.INITIALIZER_LENGTH_INCORRECT);
@@ -281,27 +284,35 @@ private TypedValue createArray(ExpressionState state) throws EvaluationException
281284
newArray = Array.newInstance(componentType, arraySize);
282285
if (arrayTypeCode == TypeCode.OBJECT) {
283286
populateReferenceTypeArray(state, newArray, typeConverter, initializer, componentType);
284-
} else if (arrayTypeCode == TypeCode.INT) {
287+
}
288+
else if (arrayTypeCode == TypeCode.INT) {
285289
populateIntArray(state, newArray, typeConverter, initializer);
286-
} else if (arrayTypeCode == TypeCode.BOOLEAN) {
290+
}
291+
else if (arrayTypeCode == TypeCode.BOOLEAN) {
287292
populateBooleanArray(state, newArray, typeConverter, initializer);
288-
} else if (arrayTypeCode == TypeCode.CHAR) {
293+
}
294+
else if (arrayTypeCode == TypeCode.CHAR) {
289295
populateCharArray(state, newArray, typeConverter, initializer);
290-
} else if (arrayTypeCode == TypeCode.LONG) {
296+
}
297+
else if (arrayTypeCode == TypeCode.LONG) {
291298
populateLongArray(state, newArray, typeConverter, initializer);
292-
} else if (arrayTypeCode == TypeCode.SHORT) {
299+
}
300+
else if (arrayTypeCode == TypeCode.SHORT) {
293301
populateShortArray(state, newArray, typeConverter, initializer);
294-
} else if (arrayTypeCode == TypeCode.DOUBLE) {
302+
}
303+
else if (arrayTypeCode == TypeCode.DOUBLE) {
295304
populateDoubleArray(state, newArray, typeConverter, initializer);
296-
} else if (arrayTypeCode == TypeCode.FLOAT) {
305+
}
306+
else if (arrayTypeCode == TypeCode.FLOAT) {
297307
populateFloatArray(state, newArray, typeConverter, initializer);
298-
} else if (arrayTypeCode == TypeCode.BYTE) {
308+
}
309+
else if (arrayTypeCode == TypeCode.BYTE) {
299310
populateByteArray(state, newArray, typeConverter, initializer);
300-
} else {
311+
}
312+
else {
301313
throw new IllegalStateException(arrayTypeCode.name());
302314
}
303315
}
304-
305316
return new TypedValue(newArray, td);
306317
}
307318

0 commit comments

Comments
 (0)