From 54865c0c1f03a486b04731049db620fb28af3f51 Mon Sep 17 00:00:00 2001 From: Andy Clement Date: Wed, 1 Apr 2009 21:38:51 +0000 Subject: [PATCH] initial typeDescriptor awareness in the EL. some basic testing of using GenericConversionService --- .../expression/PropertyAccessor.java | 11 ++ .../expression/TypeConverter.java | 22 ++++ .../expression/spel/ExpressionState.java | 5 + .../expression/spel/ast/Indexer.java | 5 +- .../expression/spel/ast/OperatorAnd.java | 6 +- .../expression/spel/ast/OperatorNot.java | 4 +- .../expression/spel/ast/OperatorOr.java | 6 +- .../spel/ast/PropertyOrFieldReference.java | 34 ++++- .../expression/spel/ast/SpelNodeImpl.java | 5 +- .../support/ReflectivePropertyResolver.java | 44 ++++++- .../spel/support/StandardTypeConverter.java | 11 ++ .../spel/ExpressionLanguageScenarioTests.java | 12 +- ...essionTestsUsingCoreConversionService.java | 121 ++++++++++++++++++ .../expression/spel/MapAccessTests.java | 8 +- .../expression/spel/PropertyAccessTests.java | 5 + .../spel/ScenariosForSpringSecurity.java | 10 ++ .../expression/spel/SetValueTests.java | 38 +++++- .../spel/testresources/Inventor.java | 10 ++ 18 files changed, 328 insertions(+), 29 deletions(-) create mode 100644 org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionTestsUsingCoreConversionService.java diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/PropertyAccessor.java b/org.springframework.expression/src/main/java/org/springframework/expression/PropertyAccessor.java index 04a4a32e446..0692e82500c 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/PropertyAccessor.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/PropertyAccessor.java @@ -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. diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/TypeConverter.java b/org.springframework.expression/src/main/java/org/springframework/expression/TypeConverter.java index b1f896015e1..ce3b3bedeec 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/TypeConverter.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/TypeConverter.java @@ -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 convertValue(Object value, Class 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 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 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); + } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ExpressionState.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ExpressionState.java index f3546dfbd61..5474a880f07 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ExpressionState.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ExpressionState.java @@ -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 convertValue(Object value, TypeDescriptor targetTypeDescriptor) throws EvaluationException { + return this.relatedContext.getTypeConverter().convertValue(value, targetTypeDescriptor); + } + public T convertValue(Object value, Class targetType) throws EvaluationException { return this.relatedContext.getTypeConverter().convertValue(value, targetType); } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java index 5f8fc6b072f..1156c7b6d04 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java @@ -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); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorAnd.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorAnd.java index 9217a44f235..e4b83e666e3 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorAnd.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorAnd.java @@ -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()); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorNot.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorNot.java index 9534a245d22..d9942b44649 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorNot.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorNot.java @@ -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) { diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorOr.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorOr.java index 7825f903205..044f1ff6e30 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorOr.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/OperatorOr.java @@ -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()); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java index fc26c3a7738..c4dbcfdf564 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java @@ -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; } } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java index 43fcefc3fad..2e620cf01cd 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java @@ -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 diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyResolver.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyResolver.java index 7305e57c060..56cbf213014 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyResolver.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyResolver.java @@ -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 readerCache = new ConcurrentHashMap(); - - private final Map writerCache = new ConcurrentHashMap(); + protected final Map readerCache = new ConcurrentHashMap(); + protected final Map writerCache = new ConcurrentHashMap(); + + protected final Map typeDescriptorCache = new ConcurrentHashMap(); /** * @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 diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java index 28304f42ce8..7ecb75941bb 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java @@ -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 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()); + } + } diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionLanguageScenarioTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionLanguageScenarioTests.java index 8da7803ee84..aa03b5731fb 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionLanguageScenarioTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionLanguageScenarioTests.java @@ -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; + } } - } diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionTestsUsingCoreConversionService.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionTestsUsingCoreConversionService.java new file mode 100644 index 00000000000..96d4356419d --- /dev/null +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionTestsUsingCoreConversionService.java @@ -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 listOfString = new ArrayList(); + private static TypeDescriptor typeDescriptorForListOfString = null; + private static List listOfInteger = new ArrayList(); + 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 to List + 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 to List + 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 to the List 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 convertValue(Object value, Class targetType) throws EvaluationException { + return (T)super.executeConversion(value,TypeDescriptor.valueOf(targetType)); + } + + @SuppressWarnings("unchecked") + public T convertValue(Object value, TypeDescriptor typeDescriptor) + throws EvaluationException { + return (T)super.executeConversion(value, typeDescriptor); + } + + } + +} diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/MapAccessTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/MapAccessTests.java index ddc0173ba7d..49e164df93d 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/MapAccessTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/MapAccessTests.java @@ -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); + } } } diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java index def350ecd9c..3ce722a539e 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java @@ -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 }; } diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/ScenariosForSpringSecurity.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ScenariosForSpringSecurity.java index dd788cfad81..93fbc4fb013 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/ScenariosForSpringSecurity.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ScenariosForSpringSecurity.java @@ -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; + } } diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/SetValueTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/SetValueTests.java index 8e7bdefc321..a91ea40800d 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/SetValueTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/SetValueTests.java @@ -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()); + } + } } diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/Inventor.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/Inventor.java index bebb1a6cd05..4a72f2b6419 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/Inventor.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/Inventor.java @@ -21,6 +21,8 @@ public class Inventor { private List placesLivedList = new ArrayList(); public ArrayContainer arrayContainer; public boolean publicBoolean; + private boolean accessedThroughGetSet; + public List listOfInteger = new ArrayList(); 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; + } }