Browse Source

Merge pull request #136 from philwebb/SPR-9017

* SPR-9017:
  SpEL support for methods and properties on class …
  Remove trailing whitespace
pull/136/merge
Phillip Webb 14 years ago
parent
commit
ea8b1327b4
  1. 20
      spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java
  2. 86
      spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java
  3. 80
      spring-expression/src/test/java/org/springframework/expression/spel/MethodInvocationTests.java
  4. 27
      spring-expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java

20
spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2012 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.
@ -21,8 +21,10 @@ import java.util.ArrayList; @@ -21,8 +21,10 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.TypeDescriptor;
@ -53,7 +55,7 @@ public class ReflectiveMethodResolver implements MethodResolver { @@ -53,7 +55,7 @@ public class ReflectiveMethodResolver implements MethodResolver {
private Map<Class<?>, MethodFilter> filters = null;
// Using distance will ensure a more accurate match is discovered,
// Using distance will ensure a more accurate match is discovered,
// more closely following the Java rules.
private boolean useDistance = false;
@ -66,7 +68,7 @@ public class ReflectiveMethodResolver implements MethodResolver { @@ -66,7 +68,7 @@ public class ReflectiveMethodResolver implements MethodResolver {
* This constructors allows the ReflectiveMethodResolver to be configured such that it will
* use a distance computation to check which is the better of two close matches (when there
* are multiple matches). Using the distance computation is intended to ensure matches
* are more closely representative of what a Java compiler would do when taking into
* are more closely representative of what a Java compiler would do when taking into
* account boxing/unboxing and whether the method candidates are declared to handle a
* supertype of the type (of the argument) being passed in.
* @param useDistance true if distance computation should be used when calculating matches
@ -90,7 +92,7 @@ public class ReflectiveMethodResolver implements MethodResolver { @@ -90,7 +92,7 @@ public class ReflectiveMethodResolver implements MethodResolver {
try {
TypeConverter typeConverter = context.getTypeConverter();
Class<?> type = (targetObject instanceof Class ? (Class<?>) targetObject : targetObject.getClass());
Method[] methods = getMethods(type);
Method[] methods = getMethods(type, targetObject);
// If a filter is registered for this type, call it
MethodFilter filter = (this.filters != null ? this.filters.get(type) : null);
@ -197,6 +199,16 @@ public class ReflectiveMethodResolver implements MethodResolver { @@ -197,6 +199,16 @@ public class ReflectiveMethodResolver implements MethodResolver {
}
}
private Method[] getMethods(Class<?> type, Object targetObject) {
if(targetObject instanceof Class) {
Set<Method> methods = new HashSet<Method>();
methods.addAll(Arrays.asList(getMethods(type)));
methods.addAll(Arrays.asList(getMethods(targetObject.getClass())));
return methods.toArray(new Method[methods.size()]);
}
return getMethods(type);
}
/**
* Return the set of methods for this type. The default implementation returns the
* result of Class#getMethods for the given {@code type}, but subclasses may override

86
spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2012 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.
@ -37,8 +37,8 @@ import org.springframework.util.StringUtils; @@ -37,8 +37,8 @@ import org.springframework.util.StringUtils;
/**
* Simple PropertyAccessor 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).
*
* 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
* @since 3.0
@ -48,7 +48,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -48,7 +48,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
protected final Map<CacheKey, InvokerPair> readerCache = new ConcurrentHashMap<CacheKey, InvokerPair>();
protected final Map<CacheKey, Member> writerCache = new ConcurrentHashMap<CacheKey, Member>();
protected final Map<CacheKey, TypeDescriptor> typeDescriptorCache = new ConcurrentHashMap<CacheKey, TypeDescriptor>();
@ -71,7 +71,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -71,7 +71,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
if (this.readerCache.containsKey(cacheKey)) {
return true;
}
Method method = findGetterForProperty(name, type, target instanceof Class);
Method method = findGetterForProperty(name, type, target);
if (method != null) {
// Treat it like a property
// The readerCache will only contain gettable properties (let's not worry about setters for now)
@ -82,10 +82,10 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -82,10 +82,10 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
return true;
}
else {
Field field = findField(name, type, target instanceof Class);
Field field = findField(name, type, target);
if (field != null) {
TypeDescriptor typeDescriptor = new TypeDescriptor(field);
this.readerCache.put(cacheKey, new InvokerPair(field,typeDescriptor));
this.readerCache.put(cacheKey, new InvokerPair(field,typeDescriptor));
this.typeDescriptorCache.put(cacheKey, typeDescriptor);
return true;
}
@ -112,7 +112,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -112,7 +112,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
if (invoker == null || invoker.member instanceof Method) {
Method method = (Method) (invoker != null ? invoker.member : null);
if (method == null) {
method = findGetterForProperty(name, type, target instanceof Class);
method = findGetterForProperty(name, type, target);
if (method != null) {
// TODO remove the duplication here between canRead and read
// Treat it like a property
@ -138,7 +138,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -138,7 +138,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
if (invoker == null || invoker.member instanceof Field) {
Field field = (Field) (invoker == null ? null : invoker.member);
if (field == null) {
field = findField(name, type, target instanceof Class);
field = findField(name, type, target);
if (field != null) {
invoker = new InvokerPair(field, new TypeDescriptor(field));
this.readerCache.put(cacheKey, invoker);
@ -168,7 +168,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -168,7 +168,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
if (this.writerCache.containsKey(cacheKey)) {
return true;
}
Method method = findSetterForProperty(name, type, target instanceof Class);
Method method = findSetterForProperty(name, type, target);
if (method != null) {
// Treat it like a property
Property property = new Property(type, null, method);
@ -178,7 +178,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -178,7 +178,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
return true;
}
else {
Field field = findField(name, type, target instanceof Class);
Field field = findField(name, type, target);
if (field != null) {
this.writerCache.put(cacheKey, field);
this.typeDescriptorCache.put(cacheKey, new TypeDescriptor(field));
@ -211,7 +211,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -211,7 +211,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
if (cachedMember == null || cachedMember instanceof Method) {
Method method = (Method) cachedMember;
if (method == null) {
method = findSetterForProperty(name, type, target instanceof Class);
method = findSetterForProperty(name, type, target);
if (method != null) {
cachedMember = method;
this.writerCache.put(cacheKey, cachedMember);
@ -232,7 +232,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -232,7 +232,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
if (cachedMember == null || cachedMember instanceof Field) {
Field field = (Field) cachedMember;
if (field == null) {
field = findField(name, type, target instanceof Class);
field = findField(name, type, target);
if (field != null) {
cachedMember = field;
this.writerCache.put(cacheKey, cachedMember);
@ -252,7 +252,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -252,7 +252,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
throw new AccessException("Neither setter nor field found for property '" + name + "'");
}
private TypeDescriptor getTypeDescriptor(EvaluationContext context, Object target, String name) {
if (target == null) {
return null;
@ -281,6 +281,30 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -281,6 +281,30 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
return typeDescriptor;
}
private Method findGetterForProperty(String propertyName, Class<?> clazz, Object target) {
Method method = findGetterForProperty(propertyName, clazz, target instanceof Class);
if(method == null && target instanceof Class) {
method = findGetterForProperty(propertyName, target.getClass(), false);
}
return method;
}
private Method findSetterForProperty(String propertyName, Class<?> clazz, Object target) {
Method method = findSetterForProperty(propertyName, clazz, target instanceof Class);
if(method == null && target instanceof Class) {
method = findSetterForProperty(propertyName, target.getClass(), false);
}
return method;
}
private Field findField(String name, Class<?> clazz, Object target) {
Field field = findField(name, clazz, target instanceof Class);
if(field == null && target instanceof Class) {
field = findField(name, target.getClass(), false);
}
return field;
}
/**
* Find a getter method for the specified property.
*/
@ -340,22 +364,22 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -340,22 +364,22 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
}
return null;
}
/**
* Captures the member (method/field) to call reflectively to access a property value and the type descriptor for the
* value returned by the reflective call.
*/
private static class InvokerPair {
final Member member;
final TypeDescriptor typeDescriptor;
public InvokerPair(Member member, TypeDescriptor typeDescriptor) {
this.member = member;
this.typeDescriptor = typeDescriptor;
}
}
}
private static class CacheKey {
@ -387,9 +411,9 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -387,9 +411,9 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
}
}
/**
/**
* Attempt to create an optimized property accessor tailored for a property of a particular name on
* a particular class. The general ReflectivePropertyAccessor will always work but is not optimal
* a particular class. The general ReflectivePropertyAccessor will always work but is not optimal
* due to the need to lookup which reflective member (method/field) to use each time read() is called.
* This method will just return the ReflectivePropertyAccessor instance if it is unable to build
* something more optimal.
@ -410,7 +434,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -410,7 +434,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
if (invocationTarget == null || invocationTarget.member instanceof Method) {
Method method = (Method) (invocationTarget==null?null:invocationTarget.member);
if (method == null) {
method = findGetterForProperty(name, type, target instanceof Class);
method = findGetterForProperty(name, type, target);
if (method != null) {
invocationTarget = new InvokerPair(method,new TypeDescriptor(new MethodParameter(method,-1)));
ReflectionUtils.makeAccessible(method);
@ -438,10 +462,10 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -438,10 +462,10 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
}
return this;
}
/**
* An optimized form of a PropertyAccessor that will use reflection but only knows how to access a particular property
* on a particular class. This is unlike the general ReflectivePropertyResolver which manages a cache of methods/fields that
* An optimized form of a PropertyAccessor that will use reflection but only knows how to access a particular property
* on a particular class. This is unlike the general ReflectivePropertyResolver which manages a cache of methods/fields that
* may be invoked to access different properties on different classes. This optimal accessor exists because looking up
* the appropriate reflective object by class/name on each read is not cheap.
*/
@ -449,13 +473,13 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -449,13 +473,13 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
private final Member member;
private final TypeDescriptor typeDescriptor;
private final boolean needsToBeMadeAccessible;
OptimalPropertyAccessor(InvokerPair target) {
this.member = target.member;
this.member = target.member;
this.typeDescriptor = target.typeDescriptor;
if (this.member instanceof Field) {
Field field = (Field)member;
needsToBeMadeAccessible = (!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()))
needsToBeMadeAccessible = (!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()))
&& !field.isAccessible();
}
else {
@ -468,7 +492,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -468,7 +492,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
public Class[] getSpecificTargetClasses() {
throw new UnsupportedOperationException("Should not be called on an OptimalPropertyAccessor");
}
public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
if (target == null) {
return false;
@ -504,7 +528,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -504,7 +528,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
catch (Exception ex) {
throw new AccessException("Unable to access property '" + name + "' through getter", ex);
}
}
}
if (member instanceof Field) {
try {
if (needsToBeMadeAccessible) {
@ -523,7 +547,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -523,7 +547,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
throw new UnsupportedOperationException("Should not be called on an OptimalPropertyAccessor");
}
public void write(EvaluationContext context, Object target, String name, Object newValue)
throws AccessException {
throw new UnsupportedOperationException("Should not be called on an OptimalPropertyAccessor");

80
spring-expression/src/test/java/org/springframework/expression/spel/MethodInvocationTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2012 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.
@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
package org.springframework.expression.spel;
import static org.junit.Assert.*;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@ -40,7 +42,7 @@ import org.springframework.expression.spel.testresources.PlaceOfBirth; @@ -40,7 +42,7 @@ import org.springframework.expression.spel.testresources.PlaceOfBirth;
/**
* Tests invocation of methods.
*
*
* @author Andy Clement
*/
public class MethodInvocationTests extends ExpressionTestCase {
@ -89,7 +91,7 @@ public class MethodInvocationTests extends ExpressionTestCase { @@ -89,7 +91,7 @@ public class MethodInvocationTests extends ExpressionTestCase {
evaluate("new String('hello 2.0 to you').startsWith(7.0d)", false, Boolean.class);
evaluate("new String('7.0 foobar').startsWith(7.0d)", true, Boolean.class);
}
@Test
public void testMethodThrowingException_SPR6760() {
// Test method on inventor: throwException()
@ -97,7 +99,7 @@ public class MethodInvocationTests extends ExpressionTestCase { @@ -97,7 +99,7 @@ public class MethodInvocationTests extends ExpressionTestCase {
// On 2 it will throw a RuntimeException
// On 3 it will exit normally
// In each case it increments the Inventor field 'counter' when invoked
SpelExpressionParser parser = new SpelExpressionParser();
Expression expr = parser.parseExpression("throwException(#bar)");
@ -115,18 +117,18 @@ public class MethodInvocationTests extends ExpressionTestCase { @@ -115,18 +117,18 @@ public class MethodInvocationTests extends ExpressionTestCase {
o = expr.getValue(eContext);
Assert.assertEquals("London", o);
// That confirms the logic to mark the cached reference stale and retry is working
// Now let's cause the method to exit via exception and ensure it doesn't cause
// a retry.
// First, switch back to throwException(int)
eContext.setVariable("bar",3);
o = expr.getValue(eContext);
Assert.assertEquals(3, o);
Assert.assertEquals(2,parser.parseExpression("counter").getValue(eContext));
// Now cause it to throw an exception:
eContext.setVariable("bar",1);
try {
@ -157,7 +159,7 @@ public class MethodInvocationTests extends ExpressionTestCase { @@ -157,7 +159,7 @@ public class MethodInvocationTests extends ExpressionTestCase {
// If counter is 5 then the method got called twice!
Assert.assertEquals(4,parser.parseExpression("counter").getValue(eContext));
}
/**
* Check on first usage (when the cachedExecutor in MethodReference is null) that the exception is not wrapped.
*/
@ -168,10 +170,10 @@ public class MethodInvocationTests extends ExpressionTestCase { @@ -168,10 +170,10 @@ public class MethodInvocationTests extends ExpressionTestCase {
// On 2 it will throw a RuntimeException
// On 3 it will exit normally
// In each case it increments the Inventor field 'counter' when invoked
SpelExpressionParser parser = new SpelExpressionParser();
Expression expr = parser.parseExpression("throwException(#bar)");
eContext.setVariable("bar",2);
try {
expr.getValue(eContext);
@ -184,7 +186,7 @@ public class MethodInvocationTests extends ExpressionTestCase { @@ -184,7 +186,7 @@ public class MethodInvocationTests extends ExpressionTestCase {
// normal
}
}
@Test
public void testMethodThrowingException_SPR6941_2() {
// Test method on inventor: throwException()
@ -192,10 +194,10 @@ public class MethodInvocationTests extends ExpressionTestCase { @@ -192,10 +194,10 @@ public class MethodInvocationTests extends ExpressionTestCase {
// On 2 it will throw a RuntimeException
// On 3 it will exit normally
// In each case it increments the Inventor field 'counter' when invoked
SpelExpressionParser parser = new SpelExpressionParser();
Expression expr = parser.parseExpression("throwException(#bar)");
eContext.setVariable("bar",4);
try {
expr.getValue(eContext);
@ -207,7 +209,7 @@ public class MethodInvocationTests extends ExpressionTestCase { @@ -207,7 +209,7 @@ public class MethodInvocationTests extends ExpressionTestCase {
}
Assert.fail("Should not be a SpelEvaluationException");
}
@Test
public void testMethodFiltering_SPR6764() {
SpelExpressionParser parser = new SpelExpressionParser();
@ -215,13 +217,13 @@ public class MethodInvocationTests extends ExpressionTestCase { @@ -215,13 +217,13 @@ public class MethodInvocationTests extends ExpressionTestCase {
context.setRootObject(new TestObject());
LocalFilter filter = new LocalFilter();
context.registerMethodFilter(TestObject.class,filter);
// Filter will be called but not do anything, so first doit() will be invoked
SpelExpression expr = (SpelExpression) parser.parseExpression("doit(1)");
String result = expr.getValue(context,String.class);
Assert.assertEquals("1",result);
Assert.assertTrue(filter.filterCalled);
// Filter will now remove non @Anno annotated methods
filter.removeIfNotAnnotated = true;
filter.filterCalled = false;
@ -229,7 +231,7 @@ public class MethodInvocationTests extends ExpressionTestCase { @@ -229,7 +231,7 @@ public class MethodInvocationTests extends ExpressionTestCase {
result = expr.getValue(context,String.class);
Assert.assertEquals("double 1.0",result);
Assert.assertTrue(filter.filterCalled);
// check not called for other types
filter.filterCalled=false;
context.setRootObject(new String("abc"));
@ -237,7 +239,7 @@ public class MethodInvocationTests extends ExpressionTestCase { @@ -237,7 +239,7 @@ public class MethodInvocationTests extends ExpressionTestCase {
result = expr.getValue(context,String.class);
Assert.assertEquals("a",result);
Assert.assertFalse(filter.filterCalled);
// check de-registration works
filter.filterCalled = false;
context.registerMethodFilter(TestObject.class,null);//clear filter
@ -247,14 +249,14 @@ public class MethodInvocationTests extends ExpressionTestCase { @@ -247,14 +249,14 @@ public class MethodInvocationTests extends ExpressionTestCase {
Assert.assertEquals("1",result);
Assert.assertFalse(filter.filterCalled);
}
// Simple filter
static class LocalFilter implements MethodFilter {
public boolean removeIfNotAnnotated = false;
public boolean filterCalled = false;
private boolean isAnnotated(Method m) {
Annotation[] annos = m.getAnnotations();
if (annos==null) {
@ -282,53 +284,53 @@ public class MethodInvocationTests extends ExpressionTestCase { @@ -282,53 +284,53 @@ public class MethodInvocationTests extends ExpressionTestCase {
}
return methods;
}
}
@Retention(RetentionPolicy.RUNTIME)
@interface Anno {}
class TestObject {
public int doit(int i) {
return i;
}
@Anno
public String doit(double d) {
return "double "+d;
}
}
@Test
public void testAddingMethodResolvers() {
StandardEvaluationContext ctx = new StandardEvaluationContext();
// reflective method accessor is the only one by default
List<MethodResolver> methodResolvers = ctx.getMethodResolvers();
Assert.assertEquals(1,methodResolvers.size());
MethodResolver dummy = new DummyMethodResolver();
ctx.addMethodResolver(dummy);
Assert.assertEquals(2,ctx.getMethodResolvers().size());
List<MethodResolver> copy = new ArrayList<MethodResolver>();
copy.addAll(ctx.getMethodResolvers());
Assert.assertTrue(ctx.removeMethodResolver(dummy));
Assert.assertFalse(ctx.removeMethodResolver(dummy));
Assert.assertEquals(1,ctx.getMethodResolvers().size());
ctx.setMethodResolvers(copy);
Assert.assertEquals(2,ctx.getMethodResolvers().size());
}
static class DummyMethodResolver implements MethodResolver {
public MethodExecutor resolve(EvaluationContext context, Object targetObject, String name,
List<TypeDescriptor> argumentTypes) throws AccessException {
throw new UnsupportedOperationException("Auto-generated method stub");
}
}
@ -355,10 +357,16 @@ public class MethodInvocationTests extends ExpressionTestCase { @@ -355,10 +357,16 @@ public class MethodInvocationTests extends ExpressionTestCase {
evaluate("aVarargsMethod2(2,'a',3.0d)", 4, Integer.class);
// evaluate("aVarargsMethod2(8,new String[]{'a','b','c'})", 11, Integer.class);
}
@Test
public void testInvocationOnNullContextObject() {
evaluateAndCheckError("null.toString()",SpelMessage.METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED);
}
@Test
public void testMethodOfClass() throws Exception {
Expression expression = parser.parseExpression("getName()");
Object value = expression.getValue(new StandardEvaluationContext(String.class));
assertEquals(value, "java.lang.String");
}
}

27
spring-expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2012 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.
@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
package org.springframework.expression.spel;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
@ -37,7 +39,7 @@ import org.springframework.expression.spel.support.StandardEvaluationContext; @@ -37,7 +39,7 @@ import org.springframework.expression.spel.support.StandardEvaluationContext;
/**
* Tests accessing of properties.
*
*
* @author Andy Clement
*/
public class PropertyAccessTests extends ExpressionTestCase {
@ -65,9 +67,9 @@ public class PropertyAccessTests extends ExpressionTestCase { @@ -65,9 +67,9 @@ public class PropertyAccessTests extends ExpressionTestCase {
// name is ok but foobar does not exist:
evaluateAndCheckError("name.foobar", SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE, 5);
}
/**
* The standard reflection resolver cannot find properties on null objects but some
* The standard reflection resolver cannot find properties on null objects but some
* supplied resolver might be able to - so null shouldn't crash the reflection resolver.
*/
@Test
@ -132,29 +134,36 @@ public class PropertyAccessTests extends ExpressionTestCase { @@ -132,29 +134,36 @@ public class PropertyAccessTests extends ExpressionTestCase {
// System.out.println(e.getMessage());
}
}
@Test
public void testAddingRemovingAccessors() {
StandardEvaluationContext ctx = new StandardEvaluationContext();
// reflective property accessor is the only one by default
List<PropertyAccessor> propertyAccessors = ctx.getPropertyAccessors();
Assert.assertEquals(1,propertyAccessors.size());
StringyPropertyAccessor spa = new StringyPropertyAccessor();
ctx.addPropertyAccessor(spa);
Assert.assertEquals(2,ctx.getPropertyAccessors().size());
List<PropertyAccessor> copy = new ArrayList<PropertyAccessor>();
copy.addAll(ctx.getPropertyAccessors());
Assert.assertTrue(ctx.removePropertyAccessor(spa));
Assert.assertFalse(ctx.removePropertyAccessor(spa));
Assert.assertEquals(1,ctx.getPropertyAccessors().size());
ctx.setPropertyAccessors(copy);
Assert.assertEquals(2,ctx.getPropertyAccessors().size());
}
@Test
public void testAccessingPropertyOfClass() throws Exception {
Expression expression = parser.parseExpression("name");
Object value = expression.getValue(new StandardEvaluationContext(String.class));
assertEquals(value, "java.lang.String");
}
// This can resolve the property 'flibbles' on any String (very useful...)
private static class StringyPropertyAccessor implements PropertyAccessor {

Loading…
Cancel
Save