initial typeDescriptor awareness in the EL. some basic testing of using GenericConversionService
This commit is contained in:
parent
65afc80821
commit
54865c0c1f
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,13 +16,14 @@
|
|||
|
||||
package org.springframework.expression.spel;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.Color;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.expression.AccessException;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
|
|
@ -264,6 +265,10 @@ public class ExpressionLanguageScenarioTests extends ExpressionTestCase {
|
|||
public void write(EvaluationContext context, Object target, String name, Object newValue)
|
||||
throws AccessException {
|
||||
}
|
||||
|
||||
public TypeDescriptor getTypeDescriptor(EvaluationContext context, Object target, String name) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -301,6 +306,9 @@ public class ExpressionLanguageScenarioTests extends ExpressionTestCase {
|
|||
|
||||
public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException {
|
||||
}
|
||||
|
||||
public TypeDescriptor getTypeDescriptor(EvaluationContext context, Object target, String name) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.expression.spel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.convert.ConversionExecutor;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.service.DefaultConversionService;
|
||||
import org.springframework.core.convert.service.GenericConversionService;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.TypeConverter;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
|
||||
/**
|
||||
* Expression evaluation where the TypeConverter plugged in is the {@link GenericConversionService}
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class ExpressionTestsUsingCoreConversionService extends ExpressionTestCase {
|
||||
|
||||
private static List<String> listOfString = new ArrayList<String>();
|
||||
private static TypeDescriptor typeDescriptorForListOfString = null;
|
||||
private static List<Integer> listOfInteger = new ArrayList<Integer>();
|
||||
private static TypeDescriptor typeDescriptorForListOfInteger = null;
|
||||
|
||||
static {
|
||||
listOfString.add("1");
|
||||
listOfString.add("2");
|
||||
listOfString.add("3");
|
||||
listOfInteger.add(4);
|
||||
listOfInteger.add(5);
|
||||
listOfInteger.add(6);
|
||||
}
|
||||
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
typeDescriptorForListOfString = new TypeDescriptor(ExpressionTestsUsingCoreConversionService.class.getDeclaredField("listOfString"));
|
||||
typeDescriptorForListOfInteger = new TypeDescriptor(ExpressionTestsUsingCoreConversionService.class.getDeclaredField("listOfInteger"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test the service can convert what we are about to use in the expression evaluation tests.
|
||||
*/
|
||||
public void testConversionsAvailable() throws Exception {
|
||||
TypeConvertorUsingConversionService tcs = new TypeConvertorUsingConversionService();
|
||||
|
||||
// ArrayList containing List<Integer> to List<String>
|
||||
Class<?> clazz = typeDescriptorForListOfString.getElementType();
|
||||
assertEquals(String.class,clazz);
|
||||
ConversionExecutor executor = tcs.getConversionExecutor(ArrayList.class, typeDescriptorForListOfString);
|
||||
assertNotNull(executor);
|
||||
List l = (List)executor.execute(listOfInteger);
|
||||
assertNotNull(l);
|
||||
|
||||
// ArrayList containing List<String> to List<Integer>
|
||||
clazz = typeDescriptorForListOfInteger.getElementType();
|
||||
assertEquals(Integer.class,clazz);
|
||||
executor = tcs.getConversionExecutor(ArrayList.class, typeDescriptorForListOfInteger);
|
||||
assertNotNull(executor);
|
||||
l = (List)executor.execute(listOfString);
|
||||
assertNotNull(l);
|
||||
}
|
||||
|
||||
public void testSetParameterizedList() throws Exception {
|
||||
StandardEvaluationContext context = TestScenarioCreator.getTestEvaluationContext();
|
||||
Expression e = parser.parseExpression("listOfInteger.size()");
|
||||
assertEquals(0,e.getValue(context,Integer.class).intValue());
|
||||
context.setTypeConverter(new TypeConvertorUsingConversionService());
|
||||
// Assign a List<String> to the List<Integer> field - the component elements should be converted
|
||||
parser.parseExpression("listOfInteger").setValue(context,listOfString);
|
||||
assertEquals(3,e.getValue(context,Integer.class).intValue()); // size now 3
|
||||
Class clazz = parser.parseExpression("listOfInteger[1].getClass()").getValue(context,Class.class); // element type correctly Integer
|
||||
assertEquals(Integer.class,clazz);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Type convertor that uses the core conversion service.
|
||||
*/
|
||||
private static class TypeConvertorUsingConversionService extends DefaultConversionService implements TypeConverter {
|
||||
|
||||
public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
|
||||
return super.canConvert(sourceType, TypeDescriptor.valueOf(targetType));
|
||||
}
|
||||
|
||||
public boolean canConvert(Class<?> sourceType, TypeDescriptor typeDescriptor) {
|
||||
return super.canConvert(sourceType, typeDescriptor);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T convertValue(Object value, Class<T> targetType) throws EvaluationException {
|
||||
return (T)super.executeConversion(value,TypeDescriptor.valueOf(targetType));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T convertValue(Object value, TypeDescriptor typeDescriptor)
|
||||
throws EvaluationException {
|
||||
return (T)super.executeConversion(value, typeDescriptor);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ package org.springframework.expression.spel;
|
|||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.expression.AccessException;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
|
|
@ -61,7 +62,7 @@ public class MapAccessTests extends ExpressionTestCase {
|
|||
}
|
||||
|
||||
|
||||
private static class MapAccessor implements PropertyAccessor {
|
||||
public static class MapAccessor implements PropertyAccessor {
|
||||
|
||||
public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
return (((Map) target).containsKey(name));
|
||||
|
|
@ -75,6 +76,7 @@ public class MapAccessTests extends ExpressionTestCase {
|
|||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void write(EvaluationContext context, Object target, String name, Object newValue)
|
||||
throws AccessException {
|
||||
((Map) target).put(name, newValue);
|
||||
|
|
@ -83,6 +85,10 @@ public class MapAccessTests extends ExpressionTestCase {
|
|||
public Class<?>[] getSpecificTargetClasses() {
|
||||
return new Class[] { Map.class };
|
||||
}
|
||||
|
||||
public TypeDescriptor getTypeDescriptor(EvaluationContext context, Object target, String name) {
|
||||
return TypeDescriptor.valueOf(Map.class);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.springframework.expression.spel;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.expression.AccessException;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
|
|
@ -87,6 +88,10 @@ public class PropertyAccessTests extends ExpressionTestCase {
|
|||
|
||||
int flibbles = 7;
|
||||
|
||||
public TypeDescriptor getTypeDescriptor(EvaluationContext context, Object target, String name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Class<?>[] getSpecificTargetClasses() {
|
||||
return new Class[] { String.class };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package org.springframework.expression.spel;
|
|||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.expression.AccessException;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
|
|
@ -224,6 +225,11 @@ public class ScenariosForSpringSecurity extends ExpressionTestCase {
|
|||
public Class<?>[] getSpecificTargetClasses() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public TypeDescriptor getTypeDescriptor(EvaluationContext context, Object target, String name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -252,6 +258,10 @@ public class ScenariosForSpringSecurity extends ExpressionTestCase {
|
|||
public Class<?>[] getSpecificTargetClasses() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public TypeDescriptor getTypeDescriptor(EvaluationContext context, Object target, String name) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -98,11 +98,14 @@ public class SetValueTests extends ExpressionTestCase {
|
|||
setValueExpectError("'hello'[3]", 'p');
|
||||
}
|
||||
|
||||
// public void testSetPropertyTypeCoersion() {
|
||||
// setValue("publicBoolean", "true");
|
||||
// }
|
||||
|
||||
public void testSetPropertyTypeCoersion() {
|
||||
setValue("publicBoolean", "true", Boolean.TRUE);
|
||||
}
|
||||
|
||||
public void testSetPropertyTypeCoersionThroughSetter() {
|
||||
setValue("SomeProperty", "true", Boolean.TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call setValue() but expect it to fail.
|
||||
*/
|
||||
|
|
@ -116,7 +119,6 @@ public class SetValueTests extends ExpressionTestCase {
|
|||
SpelUtilities.printAbstractSyntaxTree(System.out, e);
|
||||
}
|
||||
StandardEvaluationContext lContext = TestScenarioCreator.getTestEvaluationContext();
|
||||
// assertTrue("Expression is not writeable but should be", e.isWritable(lContext));
|
||||
e.setValue(lContext, value);
|
||||
fail("expected an error");
|
||||
} catch (ParseException pe) {
|
||||
|
|
@ -148,4 +150,30 @@ public class SetValueTests extends ExpressionTestCase {
|
|||
fail("Unexpected Exception: " + pe.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For use when coercion is happening during a setValue(). The expectedValue should be
|
||||
* the coerced form of the value.
|
||||
*/
|
||||
protected void setValue(String expression, Object value, Object expectedValue) {
|
||||
try {
|
||||
Expression e = parser.parseExpression(expression);
|
||||
if (e == null) {
|
||||
fail("Parser returned null for expression");
|
||||
}
|
||||
if (DEBUG) {
|
||||
SpelUtilities.printAbstractSyntaxTree(System.out, e);
|
||||
}
|
||||
StandardEvaluationContext lContext = TestScenarioCreator.getTestEvaluationContext();
|
||||
assertTrue("Expression is not writeable but should be", e.isWritable(lContext));
|
||||
e.setValue(lContext, value);
|
||||
assertEquals("Retrieved value was not equal to set value", expectedValue, e.getValue(lContext));
|
||||
} catch (EvaluationException ee) {
|
||||
ee.printStackTrace();
|
||||
fail("Unexpected Exception: " + ee.getMessage());
|
||||
} catch (ParseException pe) {
|
||||
pe.printStackTrace();
|
||||
fail("Unexpected Exception: " + pe.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ public class Inventor {
|
|||
private List<PlaceOfBirth> placesLivedList = new ArrayList<PlaceOfBirth>();
|
||||
public ArrayContainer arrayContainer;
|
||||
public boolean publicBoolean;
|
||||
private boolean accessedThroughGetSet;
|
||||
public List<Integer> listOfInteger = new ArrayList<Integer>();
|
||||
|
||||
public Inventor(String name, Date birthdate, String nationality) {
|
||||
this.name = name;
|
||||
|
|
@ -110,4 +112,12 @@ public class Inventor {
|
|||
public Inventor(String... strings) {
|
||||
|
||||
}
|
||||
|
||||
public boolean getSomeProperty() {
|
||||
return accessedThroughGetSet;
|
||||
}
|
||||
|
||||
public void setSomeProperty(boolean b) {
|
||||
this.accessedThroughGetSet = b;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue