revised TypeDescriptor NULL and element/mapKey/mapValue type semantics

This commit is contained in:
Keith Donald
2011-06-05 04:43:18 +00:00
parent a2a4929c14
commit c84cccf06d
42 changed files with 732 additions and 758 deletions

View File

@@ -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;
}
}
}

View File

@@ -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();

View File

@@ -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,

View File

@@ -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);

View File

@@ -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);

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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);