revised TypeDescriptor NULL and element/mapKey/mapValue type semantics
This commit is contained in:
@@ -16,9 +16,6 @@
|
||||
|
||||
package org.springframework.expression;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
|
||||
/**
|
||||
@@ -56,7 +53,7 @@ public class TypedValue {
|
||||
*/
|
||||
public TypedValue(Object value, TypeDescriptor typeDescriptor) {
|
||||
this.value = value;
|
||||
this.typeDescriptor = initTypeDescriptor(value, typeDescriptor);
|
||||
this.typeDescriptor = typeDescriptor;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
@@ -77,20 +74,4 @@ public class TypedValue {
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
// interal helpers
|
||||
|
||||
private static TypeDescriptor initTypeDescriptor(Object value, TypeDescriptor typeDescriptor) {
|
||||
if (value == null) {
|
||||
return typeDescriptor;
|
||||
}
|
||||
if (typeDescriptor.isCollection() && Object.class.equals(typeDescriptor.getElementType())) {
|
||||
return typeDescriptor.resolveCollectionElementType((Collection<?>) value);
|
||||
} else if (typeDescriptor.isMap() && Object.class.equals(typeDescriptor.getMapKeyType())
|
||||
&& Object.class.equals(typeDescriptor.getMapValueType())){
|
||||
return typeDescriptor.resolveMapKeyValueTypes((Map<?, ?>) value);
|
||||
} else {
|
||||
return typeDescriptor;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -41,7 +41,12 @@ public class FormatHelper {
|
||||
if (i > 0) {
|
||||
sb.append(",");
|
||||
}
|
||||
sb.append(formatClassNameForMessage(argumentTypes.get(i).getType()));
|
||||
TypeDescriptor typeDescriptor = argumentTypes.get(i);
|
||||
if (typeDescriptor != null) {
|
||||
sb.append(formatClassNameForMessage(typeDescriptor.getClass()));
|
||||
} else {
|
||||
sb.append(formatClassNameForMessage(null));
|
||||
}
|
||||
}
|
||||
sb.append(")");
|
||||
return sb.toString();
|
||||
|
||||
@@ -106,7 +106,7 @@ public class FunctionReference extends SpelNodeImpl {
|
||||
try {
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
Object result = method.invoke(method.getClass(), functionArgs);
|
||||
return new TypedValue(result, new TypeDescriptor(new MethodParameter(method,-1)));
|
||||
return new TypedValue(result, new TypeDescriptor(new MethodParameter(method,-1)).narrowType(result));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.EXCEPTION_DURING_FUNCTION_CALL,
|
||||
|
||||
@@ -90,9 +90,12 @@ public class Indexer extends SpelNodeImpl {
|
||||
|
||||
// Indexing into a Map
|
||||
if (targetObject instanceof Map) {
|
||||
Object possiblyConvertedKey = state.convertValue(index, targetObjectTypeDescriptor.getMapKeyTypeDescriptor());
|
||||
Object o = ((Map<?, ?>) targetObject).get(possiblyConvertedKey);
|
||||
return new TypedValue(o, targetObjectTypeDescriptor.getMapValueTypeDescriptor());
|
||||
Object key = index;
|
||||
if (targetObjectTypeDescriptor.getMapKeyType() != null) {
|
||||
key = state.convertValue(key, targetObjectTypeDescriptor.getMapKeyType());
|
||||
}
|
||||
Object value = ((Map<?, ?>) targetObject).get(key);
|
||||
return new TypedValue(value, targetObjectTypeDescriptor.mapValueType(value));
|
||||
}
|
||||
|
||||
if (targetObject == null) {
|
||||
@@ -100,22 +103,22 @@ public class Indexer extends SpelNodeImpl {
|
||||
}
|
||||
|
||||
// if the object is something that looks indexable by an integer, attempt to treat the index value as a number
|
||||
if ((targetObject instanceof Collection ) || targetObject.getClass().isArray() || targetObject instanceof String) {
|
||||
int idx = (Integer)state.convertValue(index, TypeDescriptor.valueOf(Integer.class));
|
||||
if (targetObject instanceof Collection || targetObject.getClass().isArray() || targetObject instanceof String) {
|
||||
int idx = (Integer) state.convertValue(index, TypeDescriptor.valueOf(Integer.class));
|
||||
if (targetObject.getClass().isArray()) {
|
||||
Object arrayElement = accessArrayElement(targetObject, idx);
|
||||
return new TypedValue(arrayElement, targetObjectTypeDescriptor.getElementTypeDescriptor());
|
||||
return new TypedValue(arrayElement, targetObjectTypeDescriptor.elementType(arrayElement));
|
||||
} else if (targetObject instanceof Collection) {
|
||||
Collection c = (Collection) targetObject;
|
||||
if (idx >= c.size()) {
|
||||
if (!growCollection(state, targetObjectTypeDescriptor.getElementType(), idx, c)) {
|
||||
if (!growCollection(state, targetObjectTypeDescriptor, idx, c)) {
|
||||
throw new SpelEvaluationException(getStartPosition(),SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS, c.size(), idx);
|
||||
}
|
||||
}
|
||||
int pos = 0;
|
||||
for (Object o : c) {
|
||||
if (pos == idx) {
|
||||
return new TypedValue(o, targetObjectTypeDescriptor.getElementTypeDescriptor());
|
||||
return new TypedValue(o, targetObjectTypeDescriptor.elementType(o));
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
@@ -181,33 +184,38 @@ public class Indexer extends SpelNodeImpl {
|
||||
throw new SpelEvaluationException(SpelMessage.CANNOT_INDEX_INTO_NULL_VALUE);
|
||||
}
|
||||
// Indexing into a Map
|
||||
if (targetObjectTypeDescriptor.isMap()) {
|
||||
Map map = (Map)targetObject;
|
||||
Object possiblyConvertedKey = index;
|
||||
Object possiblyConvertedValue = newValue;
|
||||
possiblyConvertedKey = state.convertValue(index.getValue(), targetObjectTypeDescriptor.getMapKeyTypeDescriptor());
|
||||
possiblyConvertedValue = state.convertValue(newValue, targetObjectTypeDescriptor.getMapValueTypeDescriptor());
|
||||
map.put(possiblyConvertedKey,possiblyConvertedValue);
|
||||
if (targetObject instanceof Map) {
|
||||
Map map = (Map) targetObject;
|
||||
Object key = index.getValue();
|
||||
if (targetObjectTypeDescriptor.getMapKeyType() != null) {
|
||||
key = state.convertValue(index, targetObjectTypeDescriptor.getMapKeyType());
|
||||
}
|
||||
if (targetObjectTypeDescriptor.getMapValueType() != null) {
|
||||
newValue = state.convertValue(newValue, targetObjectTypeDescriptor.getMapValueType());
|
||||
}
|
||||
map.put(key, newValue);
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetObjectTypeDescriptor.isArray()) {
|
||||
int idx = (Integer)state.convertValue(index, TypeDescriptor.valueOf(Integer.class));
|
||||
setArrayElement(state, contextObject.getValue(), idx, newValue, targetObjectTypeDescriptor.getElementType());
|
||||
setArrayElement(state, contextObject.getValue(), idx, newValue, targetObjectTypeDescriptor.getElementType().getType());
|
||||
return;
|
||||
}
|
||||
else if (targetObjectTypeDescriptor.isCollection()) {
|
||||
int idx = (Integer)state.convertValue(index, TypeDescriptor.valueOf(Integer.class));
|
||||
else if (targetObject instanceof Collection) {
|
||||
int idx = (Integer) state.convertValue(index, TypeDescriptor.valueOf(Integer.class));
|
||||
Collection c = (Collection) targetObject;
|
||||
if (idx >= c.size()) {
|
||||
if (!growCollection(state, targetObjectTypeDescriptor.getElementType(), idx, c)) {
|
||||
if (!growCollection(state, targetObjectTypeDescriptor, idx, c)) {
|
||||
throw new SpelEvaluationException(getStartPosition(),SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS, c.size(), idx);
|
||||
}
|
||||
}
|
||||
if (targetObject instanceof List) {
|
||||
List list = (List)targetObject;
|
||||
Object possiblyConvertedValue = state.convertValue(newValue, targetObjectTypeDescriptor.getElementTypeDescriptor());
|
||||
list.set(idx,possiblyConvertedValue);
|
||||
List list = (List) targetObject;
|
||||
if (targetObjectTypeDescriptor.getElementType() != null) {
|
||||
newValue = state.convertValue(newValue, targetObjectTypeDescriptor.getElementType());
|
||||
}
|
||||
list.set(idx, newValue);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
@@ -217,7 +225,7 @@ public class Indexer extends SpelNodeImpl {
|
||||
|
||||
// Try and treat the index value as a property of the context object
|
||||
// TODO could call the conversion service to convert the value to a String
|
||||
if (index.getTypeDescriptor().getType()==String.class) {
|
||||
if (index.getTypeDescriptor().getType() == String.class) {
|
||||
Class<?> contextObjectClass = getObjectClass(contextObject.getValue());
|
||||
String name = (String)index.getValue();
|
||||
EvaluationContext eContext = state.getEvaluationContext();
|
||||
@@ -260,20 +268,21 @@ public class Indexer extends SpelNodeImpl {
|
||||
* @return true if collection growing succeeded, otherwise false
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private boolean growCollection(ExpressionState state, Class<?> elementType, int index,
|
||||
private boolean growCollection(ExpressionState state, TypeDescriptor targetType, int index,
|
||||
Collection collection) {
|
||||
if (state.getConfiguration().isAutoGrowCollections()) {
|
||||
if (targetType.getElementType() == null) {
|
||||
throw new SpelEvaluationException(getStartPosition(), SpelMessage.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE);
|
||||
}
|
||||
TypeDescriptor elementType = targetType.getElementType();
|
||||
Object newCollectionElement = null;
|
||||
try {
|
||||
int newElements = index-collection.size();
|
||||
if (elementType == null || elementType == Object.class) {
|
||||
throw new SpelEvaluationException(getStartPosition(), SpelMessage.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE);
|
||||
}
|
||||
int newElements = index - collection.size();
|
||||
while (newElements>0) {
|
||||
collection.add(elementType.newInstance());
|
||||
collection.add(elementType.getType().newInstance());
|
||||
newElements--;
|
||||
}
|
||||
newCollectionElement = elementType.newInstance();
|
||||
newCollectionElement = elementType.getType().newInstance();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.UNABLE_TO_GROW_COLLECTION);
|
||||
|
||||
@@ -142,7 +142,7 @@ public class Selection extends SpelNodeImpl {
|
||||
return new TypedValue(result);
|
||||
}
|
||||
else {
|
||||
Class<?> elementType = ClassUtils.resolvePrimitiveIfNecessary(op.getTypeDescriptor().getElementType());
|
||||
Class<?> elementType = ClassUtils.resolvePrimitiveIfNecessary(op.getTypeDescriptor().getElementType().getType());
|
||||
Object resultArray = Array.newInstance(elementType, result.size());
|
||||
System.arraycopy(result.toArray(), 0, resultArray, 0, result.size());
|
||||
return new TypedValue(resultArray);
|
||||
|
||||
@@ -103,30 +103,29 @@ public class SpelExpression implements Expression {
|
||||
return ExpressionUtils.convertTypedValue(context, typedResultValue, expectedResultType);
|
||||
}
|
||||
|
||||
|
||||
public Class getValueType() throws EvaluationException {
|
||||
return ast.getValueInternal(new ExpressionState(getEvaluationContext(), configuration)).getTypeDescriptor().getType();
|
||||
return getValueType(getEvaluationContext());
|
||||
}
|
||||
|
||||
public Class getValueType(Object rootObject) throws EvaluationException {
|
||||
return getValueType(getEvaluationContext(), rootObject);
|
||||
}
|
||||
|
||||
public Class getValueType(EvaluationContext context) throws EvaluationException {
|
||||
Assert.notNull(context, "The EvaluationContext is required");
|
||||
ExpressionState eState = new ExpressionState(context, configuration);
|
||||
TypeDescriptor typeDescriptor = ast.getValueInternal(eState).getTypeDescriptor();
|
||||
return typeDescriptor.getType();
|
||||
return typeDescriptor != null ? typeDescriptor.getType() : null;
|
||||
}
|
||||
|
||||
public Class getValueType(EvaluationContext context, Object rootObject) throws EvaluationException {
|
||||
ExpressionState eState = new ExpressionState(context, toTypedValue(rootObject), configuration);
|
||||
TypeDescriptor typeDescriptor = ast.getValueInternal(eState).getTypeDescriptor();
|
||||
return typeDescriptor.getType();
|
||||
}
|
||||
|
||||
public Class getValueType(Object rootObject) throws EvaluationException {
|
||||
return ast.getValueInternal(new ExpressionState(getEvaluationContext(), configuration)).getTypeDescriptor().getType();
|
||||
return typeDescriptor != null ? typeDescriptor.getType() : null;
|
||||
}
|
||||
|
||||
public TypeDescriptor getValueTypeDescriptor() throws EvaluationException {
|
||||
return ast.getValueInternal(new ExpressionState(getEvaluationContext(), configuration)).getTypeDescriptor();
|
||||
return getValueTypeDescriptor(getEvaluationContext());
|
||||
}
|
||||
|
||||
public TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException {
|
||||
@@ -177,7 +176,6 @@ public class SpelExpression implements Expression {
|
||||
Assert.notNull(context, "The EvaluationContext is required");
|
||||
ast.setValue(new ExpressionState(context, toTypedValue(rootObject), configuration), value);
|
||||
}
|
||||
|
||||
|
||||
// impl only
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.springframework.expression.spel.SpelMessage;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.MethodInvoker;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Utility methods used by the reflection resolver code to discover the appropriate
|
||||
@@ -63,7 +64,7 @@ public class ReflectionHelper {
|
||||
TypeDescriptor expectedArg = expectedArgTypes.get(i);
|
||||
if (!expectedArg.equals(suppliedArg)) {
|
||||
// The user may supply null - and that will be ok unless a primitive is expected
|
||||
if (suppliedArg == TypeDescriptor.NULL) {
|
||||
if (suppliedArg == null) {
|
||||
if (expectedArg.isPrimitive()) {
|
||||
match = null;
|
||||
}
|
||||
@@ -112,7 +113,7 @@ public class ReflectionHelper {
|
||||
for (int i = 0,max=paramTypes.size(); i < max; i++) {
|
||||
TypeDescriptor argType = argTypes.get(i);
|
||||
TypeDescriptor paramType = paramTypes.get(i);
|
||||
if (argType==TypeDescriptor.NULL) {
|
||||
if (argType == null) {
|
||||
if (paramType.isPrimitive()) {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
@@ -120,7 +121,7 @@ public class ReflectionHelper {
|
||||
if (!ClassUtils.isAssignable(paramType.getClass(), argType.getClass())) {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
if (argType != TypeDescriptor.NULL) {
|
||||
if (argType != null) {
|
||||
Class paramTypeClazz = paramType.getType();
|
||||
if (paramTypeClazz.isPrimitive()) {
|
||||
paramTypeClazz = Object.class;
|
||||
@@ -174,7 +175,7 @@ public class ReflectionHelper {
|
||||
for (int i = 0; i < argCountUpToVarargs && match != null; i++) {
|
||||
TypeDescriptor suppliedArg = suppliedArgTypes.get(i);
|
||||
TypeDescriptor expectedArg = expectedArgTypes.get(i);
|
||||
if (suppliedArg == TypeDescriptor.NULL) {
|
||||
if (suppliedArg == null) {
|
||||
if (expectedArg.isPrimitive()) {
|
||||
match = null;
|
||||
}
|
||||
@@ -213,13 +214,13 @@ public class ReflectionHelper {
|
||||
else {
|
||||
// Now... we have the final argument in the method we are checking as a match and we have 0 or more other
|
||||
// arguments left to pass to it.
|
||||
Class varargsParameterType = expectedArgTypes.get(expectedArgTypes.size() - 1).getElementType();
|
||||
Class varargsParameterType = expectedArgTypes.get(expectedArgTypes.size() - 1).getElementType().getType();
|
||||
|
||||
// All remaining parameters must be of this type or convertable to this type
|
||||
for (int i = expectedArgTypes.size() - 1; i < suppliedArgTypes.size(); i++) {
|
||||
TypeDescriptor suppliedArg = suppliedArgTypes.get(i);
|
||||
if (varargsParameterType != suppliedArg.getType()) {
|
||||
if (suppliedArg == TypeDescriptor.NULL) {
|
||||
if (!ObjectUtils.nullSafeEquals(varargsParameterType, suppliedArg)) {
|
||||
if (suppliedArg == null) {
|
||||
if (varargsParameterType.isPrimitive()) {
|
||||
match = null;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,8 @@ class ReflectiveMethodExecutor implements MethodExecutor {
|
||||
arguments = ReflectionHelper.setupArgumentsForVarargsInvocation(this.method.getParameterTypes(), arguments);
|
||||
}
|
||||
ReflectionUtils.makeAccessible(this.method);
|
||||
return new TypedValue(this.method.invoke(target, arguments), new TypeDescriptor(new MethodParameter(this.method, -1)));
|
||||
Object value = this.method.invoke(target, arguments);
|
||||
return new TypedValue(value, new TypeDescriptor(new MethodParameter(this.method, -1)).narrowType(value));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new AccessException("Problem invoking method: " + this.method, ex);
|
||||
|
||||
@@ -109,7 +109,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||
if (target instanceof Class) {
|
||||
throw new AccessException("Cannot access length on array class itself");
|
||||
}
|
||||
return new TypedValue(Array.getLength(target),TypeDescriptor.valueOf(Integer.TYPE));
|
||||
return new TypedValue(Array.getLength(target));
|
||||
}
|
||||
|
||||
CacheKey cacheKey = new CacheKey(type, name);
|
||||
@@ -508,7 +508,8 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||
if (needsToBeMadeAccessible) {
|
||||
ReflectionUtils.makeAccessible((Method) member);
|
||||
}
|
||||
return new TypedValue(((Method) member).invoke(target), typeDescriptor);
|
||||
Object value = ((Method) member).invoke(target);
|
||||
return new TypedValue(value, typeDescriptor.narrowType(value));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new AccessException("Unable to access property '" + name + "' through getter", ex);
|
||||
@@ -519,7 +520,8 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||
if (needsToBeMadeAccessible) {
|
||||
ReflectionUtils.makeAccessible((Field)member);
|
||||
}
|
||||
return new TypedValue(((Field)member).get(target),typeDescriptor);
|
||||
Object value = ((Field)member).get(target);
|
||||
return new TypedValue(value, typeDescriptor.narrowType(value));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new AccessException("Unable to access field: " + name, ex);
|
||||
|
||||
Reference in New Issue
Block a user