initial typeDescriptor awareness in the EL. some basic testing of using GenericConversionService

This commit is contained in:
Andy Clement
2009-04-01 21:38:51 +00:00
parent 65afc80821
commit 54865c0c1f
18 changed files with 328 additions and 29 deletions

View File

@@ -16,6 +16,8 @@
package org.springframework.expression;
import org.springframework.core.convert.TypeDescriptor;
/**
* A property accessor is able to read (and possibly write) to object properties. The interface places no restrictions
* and so implementors are free to access properties directly as fields or through getters or in any other way they see
@@ -41,6 +43,15 @@ public interface PropertyAccessor {
* @return an array of classes that this resolver is suitable for (or null if a general resolver)
*/
Class[] getSpecificTargetClasses();
/**
* Called to retrieve a type descriptor that describes the type of the property.
* @param context the evaluation context in which the access is being attempted
* @param target the target object upon which the property is being accessed
* @param name the name of the property being accessed
* @return a type descriptor that describes the type of this property.
*/
TypeDescriptor getTypeDescriptor(EvaluationContext context, Object target, String name);
/**
* Called to determine if a resolver instance is able to access a specified property on a specified target object.

View File

@@ -16,6 +16,8 @@
package org.springframework.expression;
import org.springframework.core.convert.TypeDescriptor;
/**
* A type converter can convert values between different types encountered
* during expression evaluation.
@@ -26,6 +28,7 @@ package org.springframework.expression;
public interface TypeConverter {
// TODO replace this stuff with Keiths spring-binding conversion code
// TODO should ExpressionException be thrown for lost precision in the case of coercion?
// TODO could remove the methods where the target is Class and just keep the TypeDescriptor variants
/**
* Convert (may coerce) a value from one type to another, for example from a boolean to a string.
@@ -36,6 +39,17 @@ public interface TypeConverter {
*/
<T> T convertValue(Object value, Class<T> targetType) throws EvaluationException;
/**
* Convert (may coerce) a value from one type to another, for example from a boolean to a string.
* The typeDescriptor parameter enables support for typed collections - if the caller really wishes they
* can have a List<Integer> for example, rather than simply a List.
* @param value the value to be converted
* @param typeDescriptor a type descriptor that supplies extra information about the requested result type
* @return the converted value
* @throws EvaluationException if conversion is not possible
*/
<T> T convertValue(Object value, TypeDescriptor typeDescriptor) throws EvaluationException;
/**
* Return true if the type converter can convert the specified type to the desired target type.
* @param sourceType the type to be converted from
@@ -44,4 +58,12 @@ public interface TypeConverter {
*/
boolean canConvert(Class<?> sourceType, Class<?> targetType);
/**
* Return true if the type converter can convert the specified type to the desired target type.
* @param sourceType the type to be converted from
* @param typeDescriptor a type descriptor that supplies extra information about the requested result type
* @return true if that conversion can be performed
*/
boolean canConvert(Class<?> sourceType, TypeDescriptor typeDescriptor);
}

View File

@@ -20,6 +20,7 @@ import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Operation;
@@ -99,6 +100,10 @@ public class ExpressionState {
return this.relatedContext.getTypeLocator().findType(type);
}
public <T> T convertValue(Object value, TypeDescriptor targetTypeDescriptor) throws EvaluationException {
return this.relatedContext.getTypeConverter().convertValue(value, targetTypeDescriptor);
}
public <T> T convertValue(Object value, Class<T> targetType) throws EvaluationException {
return this.relatedContext.getTypeConverter().convertValue(value, targetType);
}

View File

@@ -21,6 +21,7 @@ import java.util.List;
import java.util.Map;
import org.antlr.runtime.Token;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelException;
@@ -51,7 +52,7 @@ public class Indexer extends SpelNodeImpl {
return ((Map<?, ?>) ctx).get(index);
}
int idx = state.convertValue(index, Integer.class);
int idx = state.convertValue(index, INTEGER_TYPE_DESCRIPTOR);
if (ctx.getClass().isArray()) {
return accessArrayElement(ctx, idx);
@@ -109,7 +110,7 @@ public class Indexer extends SpelNodeImpl {
return;
}
int idx = state.convertValue(index, Integer.class);
int idx = state.convertValue(index, INTEGER_TYPE_DESCRIPTOR);
if (ctx.getClass().isArray()) {
setArrayElement(state, ctx, idx, newValue);

View File

@@ -18,8 +18,8 @@ package org.springframework.expression.spel.ast;
import org.antlr.runtime.Token;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.SpelException;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelException;
/**
* Represents the boolean AND operation.
@@ -44,7 +44,7 @@ public class OperatorAnd extends Operator {
boolean rightValue;
try {
leftValue = state.convertValue(getLeftOperand().getValueInternal(state), Boolean.class);
leftValue = state.convertValue(getLeftOperand().getValueInternal(state), BOOLEAN_TYPE_DESCRIPTOR);
}
catch (SpelException ee) {
ee.setPosition(getLeftOperand().getCharPositionInLine());
@@ -56,7 +56,7 @@ public class OperatorAnd extends Operator {
}
try {
rightValue = state.convertValue(getRightOperand().getValueInternal(state), Boolean.class);
rightValue = state.convertValue(getRightOperand().getValueInternal(state), BOOLEAN_TYPE_DESCRIPTOR);
}
catch (SpelException ee) {
ee.setPosition(getRightOperand().getCharPositionInLine());

View File

@@ -18,8 +18,8 @@ package org.springframework.expression.spel.ast;
import org.antlr.runtime.Token;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.SpelException;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelException;
/**
* Represents a NOT operation.
@@ -36,7 +36,7 @@ public class OperatorNot extends SpelNodeImpl { // Not is a unary operator so do
@Override
public Object getValueInternal(ExpressionState state) throws EvaluationException {
try {
boolean value = state.convertValue(getChild(0).getValueInternal(state), Boolean.class);
boolean value = state.convertValue(getChild(0).getValueInternal(state), BOOLEAN_TYPE_DESCRIPTOR);
return !value;
}
catch (SpelException see) {

View File

@@ -18,8 +18,8 @@ package org.springframework.expression.spel.ast;
import org.antlr.runtime.Token;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.SpelException;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelException;
/**
* Represents the boolean OR operation.
@@ -43,7 +43,7 @@ public class OperatorOr extends Operator {
boolean leftValue;
boolean rightValue;
try {
leftValue = state.convertValue(getLeftOperand().getValueInternal(state), Boolean.class);
leftValue = state.convertValue(getLeftOperand().getValueInternal(state), BOOLEAN_TYPE_DESCRIPTOR);
}
catch (SpelException see) {
see.setPosition(getLeftOperand().getCharPositionInLine());
@@ -55,7 +55,7 @@ public class OperatorOr extends Operator {
}
try {
rightValue = state.convertValue(getRightOperand().getValueInternal(state), Boolean.class);
rightValue = state.convertValue(getRightOperand().getValueInternal(state), BOOLEAN_TYPE_DESCRIPTOR);
}
catch (SpelException see) {
see.setPosition(getRightOperand().getCharPositionInLine());

View File

@@ -20,8 +20,10 @@ import java.util.ArrayList;
import java.util.List;
import org.antlr.runtime.Token;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelException;
@@ -41,6 +43,8 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
private volatile PropertyAccessor cachedReadAccessor;
private volatile PropertyAccessor cachedWriteAccessor;
private volatile TypeDescriptor cachedTypeDescriptor;
public PropertyOrFieldReference(Token payload) {
@@ -121,8 +125,20 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
PropertyAccessor accessorToUse = this.cachedWriteAccessor;
if (accessorToUse != null) {
try {
accessorToUse.write(state.getEvaluationContext(), contextObject, name, newValue);
try {
Object possiblyConvertedValue = newValue;
if (cachedTypeDescriptor == null) {
cachedTypeDescriptor=accessorToUse.getTypeDescriptor(eContext, contextObject, name);
}
if (cachedTypeDescriptor != null) {
try {
possiblyConvertedValue = state.convertValue(newValue, cachedTypeDescriptor.getType());
} catch (EvaluationException evaluationException) {
throw new SpelException(getCharPositionInLine(), evaluationException, SpelMessages.TYPE_CONVERSION_ERROR,
newValue.getClass(), cachedTypeDescriptor.getType());
}
}
accessorToUse.write(state.getEvaluationContext(), contextObject, name, possiblyConvertedValue);
return;
}
catch (AccessException ae) {
@@ -140,7 +156,19 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
for (PropertyAccessor accessor : accessorsToTry) {
if (accessor.canWrite(eContext, contextObject, name)) {
this.cachedWriteAccessor = accessor;
accessor.write(eContext, contextObject, name, newValue); // TODO missing conversion of newValue to the type of the property
Object possiblyConvertedValue = newValue;
if (cachedTypeDescriptor == null) {
cachedTypeDescriptor=accessor.getTypeDescriptor(eContext, contextObject, name);
}
if (cachedTypeDescriptor != null) {
try {
possiblyConvertedValue = state.convertValue(newValue, cachedTypeDescriptor);
} catch (EvaluationException evaluationException) {
throw new SpelException(getCharPositionInLine(), evaluationException, SpelMessages.TYPE_CONVERSION_ERROR,
newValue.getClass(), cachedTypeDescriptor.getType());
}
}
accessor.write(eContext, contextObject, name, possiblyConvertedValue);
return;
}
}

View File

@@ -20,7 +20,7 @@ import java.io.Serializable;
import org.antlr.runtime.Token;
import org.antlr.runtime.tree.CommonTree;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.common.ExpressionUtils;
import org.springframework.expression.spel.ExpressionState;
@@ -38,6 +38,9 @@ import org.springframework.expression.spel.support.StandardEvaluationContext;
*/
public abstract class SpelNodeImpl extends CommonTree implements SpelNode, Serializable {
protected static TypeDescriptor BOOLEAN_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Boolean.class);
protected static TypeDescriptor INTEGER_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Integer.class);
/**
* The Antlr parser uses this constructor to build SpelNodes.
* @param payload the token for the node that has been parsed

View File

@@ -24,6 +24,8 @@ import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.PropertyAccessor;
@@ -32,9 +34,7 @@ import org.springframework.util.StringUtils;
/**
* Simple PropertyResolver that uses reflection to access properties for reading and writing. A property can be accessed
* if it is accessible as a field on the object or through a getter (if being read) or a setter (if being written). This
* implementation currently follows the Resolver/Executor model (it extends CacheablePropertyAccessor) - the code that
* would be used if it were a simple property accessor is shown at the end.
* if it is accessible as a field on the object or through a getter (if being read) or a setter (if being written).
*
* @author Andy Clement
* @author Juergen Hoeller
@@ -42,10 +42,11 @@ import org.springframework.util.StringUtils;
*/
public class ReflectivePropertyResolver implements PropertyAccessor {
private final Map<CacheKey, Member> readerCache = new ConcurrentHashMap<CacheKey, Member>();
private final Map<CacheKey, Member> writerCache = new ConcurrentHashMap<CacheKey, Member>();
protected final Map<CacheKey, Member> readerCache = new ConcurrentHashMap<CacheKey, Member>();
protected final Map<CacheKey, Member> writerCache = new ConcurrentHashMap<CacheKey, Member>();
protected final Map<CacheKey, TypeDescriptor> typeDescriptorCache = new ConcurrentHashMap<CacheKey,TypeDescriptor>();
/**
* @return null which means this is a general purpose accessor
@@ -69,12 +70,14 @@ public class ReflectivePropertyResolver implements PropertyAccessor {
Method method = findGetterForProperty(name, type, target instanceof Class);
if (method != null) {
this.readerCache.put(cacheKey, method);
this.typeDescriptorCache.put(cacheKey, new TypeDescriptor(new MethodParameter(method,0)));
return true;
}
else {
Field field = findField(name, type, target instanceof Class);
if (field != null) {
this.readerCache.put(cacheKey, field);
this.readerCache.put(cacheKey, field);
this.typeDescriptorCache.put(cacheKey, new TypeDescriptor(field));
return true;
}
}
@@ -152,12 +155,14 @@ public class ReflectivePropertyResolver implements PropertyAccessor {
Method method = findSetterForProperty(name, type, target instanceof Class);
if (method != null) {
this.writerCache.put(cacheKey, method);
this.typeDescriptorCache.put(cacheKey, new TypeDescriptor(new MethodParameter(method,0)));
return true;
}
else {
Field field = findField(name, type, target instanceof Class);
if (field != null) {
this.writerCache.put(cacheKey, field);
this.typeDescriptorCache.put(cacheKey, new TypeDescriptor(field));
return true;
}
}
@@ -217,7 +222,32 @@ public class ReflectivePropertyResolver implements PropertyAccessor {
throw new AccessException("Neither setter nor field found for property '" + name + "'");
}
public TypeDescriptor getTypeDescriptor(EvaluationContext context, Object target, String name) {
if (target == null) {
return null;
}
Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass());
if (type.isArray() && name.equals("length")) {
return TypeDescriptor.valueOf(Integer.TYPE);
}
CacheKey cacheKey = new CacheKey(type, name);
TypeDescriptor typeDescriptor = this.typeDescriptorCache.get(cacheKey);
if (typeDescriptor == null) {
// attempt to populate the cache entry
try {
if (canRead(context, target, name)) {
typeDescriptor = this.typeDescriptorCache.get(cacheKey);
} else if (canWrite(context, target, name)) {
typeDescriptor = this.typeDescriptorCache.get(cacheKey);
}
} catch (AccessException e) {
// continue with null typeDescriptor
}
}
return typeDescriptor;
}
/**
* Find a getter method for the specified property. A getter is defined as a method whose name start with the prefix

View File

@@ -16,6 +16,7 @@
package org.springframework.expression.spel.support;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.spel.SpelException;
@@ -25,6 +26,7 @@ import org.springframework.util.NumberUtils;
/**
* @author Juergen Hoeller
* @author Andy Clement
* @since 3.0
*/
public class StandardTypeConverter implements TypeConverter {
@@ -74,6 +76,11 @@ public class StandardTypeConverter implements TypeConverter {
throw new SpelException(SpelMessages.TYPE_CONVERSION_ERROR, value.getClass(), targetType);
}
@SuppressWarnings("unchecked")
public <T> T convertValue(Object value, TypeDescriptor typeDescriptor) throws EvaluationException {
return (T)convertValue(value,typeDescriptor.getType());
}
public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
if (ClassUtils.isAssignable(targetType, sourceType) || String.class.equals(targetType)) {
return true;
@@ -84,4 +91,8 @@ public class StandardTypeConverter implements TypeConverter {
(Boolean.class.equals(actualTargetType) && String.class.equals(sourceType)));
}
public boolean canConvert(Class<?> sourceType, TypeDescriptor typeDescriptor) {
return canConvert(sourceType,typeDescriptor.getType());
}
}