Backported DataBindingPropertyAccessor and DataBindingMethodResolver
Issue: SPR-16588
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2018 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.
|
||||
@@ -22,9 +22,9 @@ import java.util.List;
|
||||
* Expressions are executed in an evaluation context. It is in this context that
|
||||
* references are resolved when encountered during expression evaluation.
|
||||
*
|
||||
* <p>There is a default implementation of the EvaluationContext,
|
||||
* {@link org.springframework.expression.spel.support.StandardEvaluationContext} that can
|
||||
* be extended, rather than having to implement everything.
|
||||
* <p>There is a default implementation of this EvaluationContext interface:
|
||||
* {@link org.springframework.expression.spel.support.StandardEvaluationContext}
|
||||
* which can be extended, rather than having to implement everything manually.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Juergen Hoeller
|
||||
@@ -39,6 +39,11 @@ public interface EvaluationContext {
|
||||
*/
|
||||
TypedValue getRootObject();
|
||||
|
||||
/**
|
||||
* Return a list of accessors that will be asked in turn to read/write a property.
|
||||
*/
|
||||
List<PropertyAccessor> getPropertyAccessors();
|
||||
|
||||
/**
|
||||
* Return a list of resolvers that will be asked in turn to locate a constructor.
|
||||
*/
|
||||
@@ -50,9 +55,9 @@ public interface EvaluationContext {
|
||||
List<MethodResolver> getMethodResolvers();
|
||||
|
||||
/**
|
||||
* Return a list of accessors that will be asked in turn to read/write a property.
|
||||
* Return a bean resolver that can look up beans by name.
|
||||
*/
|
||||
List<PropertyAccessor> getPropertyAccessors();
|
||||
BeanResolver getBeanResolver();
|
||||
|
||||
/**
|
||||
* Return a type locator that can be used to find types, either by short or
|
||||
@@ -76,11 +81,6 @@ public interface EvaluationContext {
|
||||
*/
|
||||
OperatorOverloader getOperatorOverloader();
|
||||
|
||||
/**
|
||||
* Return a bean resolver that can look up beans by name.
|
||||
*/
|
||||
BeanResolver getBeanResolver();
|
||||
|
||||
/**
|
||||
* Set a named variable within this evaluation context to a specified value.
|
||||
* @param name variable to set
|
||||
@@ -91,7 +91,7 @@ public interface EvaluationContext {
|
||||
/**
|
||||
* Look up a named variable within this evaluation context.
|
||||
* @param name variable to lookup
|
||||
* @return the value of the variable
|
||||
* @return the value of the variable, or {@code null} if not found
|
||||
*/
|
||||
Object lookupVariable(String name);
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2002-2018 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.support;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.expression.AccessException;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.MethodExecutor;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.expression.MethodResolver} variant for data binding
|
||||
* purposes, using reflection to access instance methods on a given target object.
|
||||
*
|
||||
* <p>This accessor does not resolve static methods and also no technical methods
|
||||
* on {@code java.lang.Object} or {@code java.lang.Class}.
|
||||
* For unrestricted resolution, choose {@link ReflectiveMethodResolver} instead.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 4.3.15
|
||||
* @see #forInstanceMethodInvocation()
|
||||
* @see DataBindingPropertyAccessor
|
||||
*/
|
||||
public class DataBindingMethodResolver extends ReflectiveMethodResolver {
|
||||
|
||||
private DataBindingMethodResolver() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodExecutor resolve(EvaluationContext context, Object targetObject, String name,
|
||||
List<TypeDescriptor> argumentTypes) throws AccessException {
|
||||
|
||||
if (targetObject instanceof Class) {
|
||||
throw new IllegalArgumentException("DataBindingMethodResolver does not support Class targets");
|
||||
}
|
||||
return super.resolve(context, targetObject, name, argumentTypes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isCandidateForInvocation(Method method, Class<?> targetClass) {
|
||||
if (Modifier.isStatic(method.getModifiers())) {
|
||||
return false;
|
||||
}
|
||||
Class<?> clazz = method.getDeclaringClass();
|
||||
return (clazz != Object.class && clazz != Class.class && !ClassLoader.class.isAssignableFrom(targetClass));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new data-binding method resolver for instance method resolution.
|
||||
*/
|
||||
public static DataBindingMethodResolver forInstanceMethodInvocation() {
|
||||
return new DataBindingMethodResolver();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2002-2018 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.support;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.expression.PropertyAccessor} variant for data binding
|
||||
* purposes, using reflection to access properties for reading and possibly writing.
|
||||
*
|
||||
* <p>A property can be referenced through a public getter method (when being read)
|
||||
* or a public setter method (when being written), and also as a public field.
|
||||
*
|
||||
* <p>This accessor is explicitly designed for user-declared properties and does not
|
||||
* resolve technical properties on {@code java.lang.Object} or {@code java.lang.Class}.
|
||||
* For unrestricted resolution, choose {@link ReflectivePropertyAccessor} instead.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 4.3.15
|
||||
* @see #forReadOnlyAccess()
|
||||
* @see #forReadWriteAccess()
|
||||
* @see SimpleEvaluationContext
|
||||
* @see StandardEvaluationContext
|
||||
* @see ReflectivePropertyAccessor
|
||||
*/
|
||||
public class DataBindingPropertyAccessor extends ReflectivePropertyAccessor {
|
||||
|
||||
/**
|
||||
* Create a new property accessor for reading and possibly also writing.
|
||||
* @param allowWrite whether to also allow for write operations
|
||||
* @see #canWrite
|
||||
*/
|
||||
private DataBindingPropertyAccessor(boolean allowWrite) {
|
||||
super(allowWrite);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isCandidateForProperty(Method method, Class<?> targetClass) {
|
||||
Class<?> clazz = method.getDeclaringClass();
|
||||
return (clazz != Object.class && clazz != Class.class && !ClassLoader.class.isAssignableFrom(targetClass));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new data-binding property accessor for read-only operations.
|
||||
*/
|
||||
public static DataBindingPropertyAccessor forReadOnlyAccess() {
|
||||
return new DataBindingPropertyAccessor(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new data-binding property accessor for read-write operations.
|
||||
*/
|
||||
public static DataBindingPropertyAccessor forReadWriteAccess() {
|
||||
return new DataBindingPropertyAccessor(true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 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,7 +21,6 @@ import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
@@ -81,6 +80,12 @@ public class ReflectiveMethodResolver implements MethodResolver {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Register a filter for methods on the given type.
|
||||
* @param type the type to filter on
|
||||
* @param filter the corresponding method filter,
|
||||
* or {@code null} to clear any filter for the given type
|
||||
*/
|
||||
public void registerMethodFilter(Class<?> type, MethodFilter filter) {
|
||||
if (this.filters == null) {
|
||||
this.filters = new HashMap<Class<?>, MethodFilter>();
|
||||
@@ -93,7 +98,6 @@ public class ReflectiveMethodResolver implements MethodResolver {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Locate a method on a type. There are three kinds of match that might occur:
|
||||
* <ol>
|
||||
@@ -219,7 +223,7 @@ public class ReflectiveMethodResolver implements MethodResolver {
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<Method> getMethods(Class<?> type, Object targetObject) {
|
||||
private Set<Method> getMethods(Class<?> type, Object targetObject) {
|
||||
if (targetObject instanceof Class) {
|
||||
Set<Method> result = new LinkedHashSet<Method>();
|
||||
// Add these so that static methods are invocable on the type: e.g. Float.valueOf(..)
|
||||
@@ -237,12 +241,24 @@ public class ReflectiveMethodResolver implements MethodResolver {
|
||||
Set<Method> result = new LinkedHashSet<Method>();
|
||||
// Expose interface methods (not proxy-declared overrides) for proper vararg introspection
|
||||
for (Class<?> ifc : type.getInterfaces()) {
|
||||
result.addAll(Arrays.asList(getMethods(ifc)));
|
||||
Method[] methods = getMethods(ifc);
|
||||
for (Method method : methods) {
|
||||
if (isCandidateForInvocation(method, type)) {
|
||||
result.add(method);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
return Arrays.asList(getMethods(type));
|
||||
Set<Method> result = new LinkedHashSet<Method>();
|
||||
Method[] methods = getMethods(type);
|
||||
for (Method method : methods) {
|
||||
if (isCandidateForInvocation(method, type)) {
|
||||
result.add(method);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,4 +274,17 @@ public class ReflectiveMethodResolver implements MethodResolver {
|
||||
return type.getMethods();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given {@code Method} is a candidate for method resolution
|
||||
* on an instance of the given target class.
|
||||
* <p>The default implementation considers any method as a candidate, even for
|
||||
* static methods sand non-user-declared methods on the {@link Object} base class.
|
||||
* @param method the Method to evaluate
|
||||
* @param targetClass the concrete target class that is being introspected
|
||||
* @since 4.3.15
|
||||
*/
|
||||
protected boolean isCandidateForInvocation(Method method, Class<?> targetClass) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2018 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.
|
||||
@@ -44,16 +44,19 @@ import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Simple {@link PropertyAccessor} that uses reflection to access properties
|
||||
* for reading and writing.
|
||||
* A powerful {@link PropertyAccessor} that uses reflection to access properties
|
||||
* for reading and possibly also for writing.
|
||||
*
|
||||
* <p>A property can be accessed through a public getter method (when being read)
|
||||
* <p>A property can be referenced through a public getter method (when being read)
|
||||
* or a public setter method (when being written), and also as a public field.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Juergen Hoeller
|
||||
* @author Phillip Webb
|
||||
* @since 3.0
|
||||
* @see StandardEvaluationContext
|
||||
* @see SimpleEvaluationContext
|
||||
* @see DataBindingPropertyAccessor
|
||||
*/
|
||||
public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||
|
||||
@@ -62,13 +65,15 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||
private static final Set<Class<?>> BOOLEAN_TYPES;
|
||||
|
||||
static {
|
||||
Set<Class<?>> booleanTypes = new HashSet<Class<?>>();
|
||||
Set<Class<?>> booleanTypes = new HashSet<Class<?>>(4);
|
||||
booleanTypes.add(Boolean.class);
|
||||
booleanTypes.add(Boolean.TYPE);
|
||||
BOOLEAN_TYPES = Collections.unmodifiableSet(booleanTypes);
|
||||
}
|
||||
|
||||
|
||||
private final boolean allowWrite;
|
||||
|
||||
private final Map<PropertyCacheKey, InvokerPair> readerCache =
|
||||
new ConcurrentHashMap<PropertyCacheKey, InvokerPair>(64);
|
||||
|
||||
@@ -81,6 +86,25 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||
private InvokerPair lastReadInvokerPair;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new property accessor for reading as well writing.
|
||||
* @see #ReflectivePropertyAccessor(boolean)
|
||||
*/
|
||||
public ReflectivePropertyAccessor() {
|
||||
this.allowWrite = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new property accessor for reading and possibly writing.
|
||||
* @param allowWrite whether to also allow for write operations
|
||||
* @since 4.3.15
|
||||
* @see #canWrite
|
||||
*/
|
||||
public ReflectivePropertyAccessor(boolean allowWrite) {
|
||||
this.allowWrite = allowWrite;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns {@code null} which means this is a general purpose accessor.
|
||||
*/
|
||||
@@ -94,14 +118,17 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||
if (target == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass());
|
||||
if (type.isArray() && name.equals("length")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
PropertyCacheKey cacheKey = new PropertyCacheKey(type, name, target instanceof Class);
|
||||
if (this.readerCache.containsKey(cacheKey)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Method method = findGetterForProperty(name, type, target);
|
||||
if (method != null) {
|
||||
// Treat it like a property...
|
||||
@@ -121,11 +148,8 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Member getLastReadInvokerPair() {
|
||||
return this.lastReadInvokerPair.member;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -144,20 +168,19 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||
|
||||
PropertyCacheKey cacheKey = new PropertyCacheKey(type, name, target instanceof Class);
|
||||
InvokerPair invoker = this.readerCache.get(cacheKey);
|
||||
lastReadInvokerPair = invoker;
|
||||
this.lastReadInvokerPair = invoker;
|
||||
|
||||
if (invoker == null || invoker.member instanceof Method) {
|
||||
Method method = (Method) (invoker != null ? invoker.member : null);
|
||||
if (method == null) {
|
||||
method = findGetterForProperty(name, type, target);
|
||||
if (method != null) {
|
||||
// TODO remove the duplication here between canRead and read
|
||||
// Treat it like a property...
|
||||
// The readerCache will only contain gettable properties (let's not worry about setters for now).
|
||||
Property property = new Property(type, method, null);
|
||||
TypeDescriptor typeDescriptor = new TypeDescriptor(property);
|
||||
invoker = new InvokerPair(method, typeDescriptor);
|
||||
lastReadInvokerPair = invoker;
|
||||
this.lastReadInvokerPair = invoker;
|
||||
this.readerCache.put(cacheKey, invoker);
|
||||
}
|
||||
}
|
||||
@@ -179,7 +202,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||
field = findField(name, type, target);
|
||||
if (field != null) {
|
||||
invoker = new InvokerPair(field, new TypeDescriptor(field));
|
||||
lastReadInvokerPair = invoker;
|
||||
this.lastReadInvokerPair = invoker;
|
||||
this.readerCache.put(cacheKey, invoker);
|
||||
}
|
||||
}
|
||||
@@ -200,14 +223,16 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||
|
||||
@Override
|
||||
public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
if (target == null) {
|
||||
if (!this.allowWrite || target == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass());
|
||||
PropertyCacheKey cacheKey = new PropertyCacheKey(type, name, target instanceof Class);
|
||||
if (this.writerCache.containsKey(cacheKey)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Method method = findSetterForProperty(name, type, target);
|
||||
if (method != null) {
|
||||
// Treat it like a property
|
||||
@@ -225,11 +250,17 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException {
|
||||
if (!this.allowWrite) {
|
||||
throw new AccessException("PropertyAccessor for property '" + name +
|
||||
"' on target [" + target + "] does not allow write operations");
|
||||
}
|
||||
|
||||
if (target == null) {
|
||||
throw new AccessException("Cannot write property on null target");
|
||||
}
|
||||
@@ -294,6 +325,16 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||
throw new AccessException("Neither setter method nor field found for property '" + name + "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated as of 4.3.15 since it is not used within the framework anymore
|
||||
*/
|
||||
@Deprecated
|
||||
public Member getLastReadInvokerPair() {
|
||||
InvokerPair lastReadInvoker = this.lastReadInvokerPair;
|
||||
return (lastReadInvoker != null ? lastReadInvoker.member : null);
|
||||
}
|
||||
|
||||
|
||||
private TypeDescriptor getTypeDescriptor(EvaluationContext context, Object target, String name) {
|
||||
if (target == null) {
|
||||
return null;
|
||||
@@ -306,7 +347,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||
PropertyCacheKey cacheKey = new PropertyCacheKey(type, name, target instanceof Class);
|
||||
TypeDescriptor typeDescriptor = this.typeDescriptorCache.get(cacheKey);
|
||||
if (typeDescriptor == null) {
|
||||
// attempt to populate the cache entry
|
||||
// Attempt to populate the cache entry
|
||||
try {
|
||||
if (canRead(context, target, name)) {
|
||||
typeDescriptor = this.typeDescriptorCache.get(cacheKey);
|
||||
@@ -316,7 +357,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||
}
|
||||
}
|
||||
catch (AccessException ex) {
|
||||
// continue with null type descriptor
|
||||
// Continue with null type descriptor
|
||||
}
|
||||
}
|
||||
return typeDescriptor;
|
||||
@@ -338,14 +379,6 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||
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.
|
||||
*/
|
||||
@@ -373,7 +406,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||
Method[] methods = getSortedClassMethods(clazz);
|
||||
for (String methodSuffix : methodSuffixes) {
|
||||
for (Method method : methods) {
|
||||
if (method.getName().equals(prefix + methodSuffix) &&
|
||||
if (isCandidateForProperty(method, clazz) && method.getName().equals(prefix + methodSuffix) &&
|
||||
method.getParameterTypes().length == numberOfParams &&
|
||||
(!mustBeStatic || Modifier.isStatic(method.getModifiers())) &&
|
||||
(requiredReturnTypes.isEmpty() || requiredReturnTypes.contains(method.getReturnType()))) {
|
||||
@@ -382,11 +415,23 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns class methods ordered with non bridge methods appearing higher.
|
||||
* Determine whether the given {@code Method} is a candidate for property access
|
||||
* on an instance of the given target class.
|
||||
* <p>The default implementation considers any method as a candidate, even for
|
||||
* non-user-declared properties on the {@link Object} base class.
|
||||
* @param method the Method to evaluate
|
||||
* @param targetClass the concrete target class that is being introspected
|
||||
* @since 4.3.15
|
||||
*/
|
||||
protected boolean isCandidateForProperty(Method method, Class<?> targetClass) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return class methods ordered with non bridge methods appearing higher.
|
||||
*/
|
||||
private Method[] getSortedClassMethods(Class<?> clazz) {
|
||||
Method[] methods = clazz.getMethods();
|
||||
@@ -408,9 +453,9 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||
protected String[] getPropertyMethodSuffixes(String propertyName) {
|
||||
String suffix = getPropertyMethodSuffix(propertyName);
|
||||
if (suffix.length() > 0 && Character.isUpperCase(suffix.charAt(0))) {
|
||||
return new String[] { suffix };
|
||||
return new String[] {suffix};
|
||||
}
|
||||
return new String[] { suffix, StringUtils.capitalize(suffix) };
|
||||
return new String[] {suffix, StringUtils.capitalize(suffix)};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -424,6 +469,14 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||
return StringUtils.capitalize(propertyName);
|
||||
}
|
||||
|
||||
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 field of a certain name on a specified class.
|
||||
*/
|
||||
@@ -452,29 +505,33 @@ 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
|
||||
* 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.
|
||||
* 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 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 a more
|
||||
* optimal accessor.
|
||||
* <p>Note: An optimal accessor is currently only usable for read attempts.
|
||||
* Do not call this method if you need a read-write accessor.
|
||||
* @see OptimalPropertyAccessor
|
||||
*/
|
||||
public PropertyAccessor createOptimalAccessor(EvaluationContext evalContext, Object target, String name) {
|
||||
// Don't be clever for arrays or null target
|
||||
public PropertyAccessor createOptimalAccessor(EvaluationContext context, Object target, String name) {
|
||||
// Don't be clever for arrays or a null target...
|
||||
if (target == null) {
|
||||
return this;
|
||||
}
|
||||
Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass());
|
||||
if (type.isArray()) {
|
||||
Class<?> clazz = (target instanceof Class ? (Class<?>) target : target.getClass());
|
||||
if (clazz.isArray()) {
|
||||
return this;
|
||||
}
|
||||
|
||||
PropertyCacheKey cacheKey = new PropertyCacheKey(type, name, target instanceof Class);
|
||||
PropertyCacheKey cacheKey = new PropertyCacheKey(clazz, name, target instanceof Class);
|
||||
InvokerPair invocationTarget = this.readerCache.get(cacheKey);
|
||||
|
||||
if (invocationTarget == null || invocationTarget.member instanceof Method) {
|
||||
Method method = (Method) (invocationTarget != null ? invocationTarget.member : null);
|
||||
if (method == null) {
|
||||
method = findGetterForProperty(name, type, target);
|
||||
method = findGetterForProperty(name, clazz, target);
|
||||
if (method != null) {
|
||||
invocationTarget = new InvokerPair(method, new TypeDescriptor(new MethodParameter(method, -1)));
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
@@ -489,7 +546,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||
if (invocationTarget == null || invocationTarget.member instanceof Field) {
|
||||
Field field = (invocationTarget != null ? (Field) invocationTarget.member : null);
|
||||
if (field == null) {
|
||||
field = findField(name, type, target instanceof Class);
|
||||
field = findField(name, clazz, target instanceof Class);
|
||||
if (field != null) {
|
||||
invocationTarget = new InvokerPair(field, new TypeDescriptor(field));
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
@@ -604,7 +661,6 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||
if (target == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass());
|
||||
if (type.isArray()) {
|
||||
return false;
|
||||
|
||||
@@ -16,12 +16,14 @@
|
||||
|
||||
package org.springframework.expression.spel.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.expression.BeanResolver;
|
||||
import org.springframework.expression.ConstructorResolver;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
@@ -38,29 +40,55 @@ import org.springframework.expression.spel.SpelMessage;
|
||||
|
||||
/**
|
||||
* A basic implementation of {@link EvaluationContext} that focuses on a subset
|
||||
* of essential SpEL features and configuration options, and relies on default
|
||||
* strategies otherwise.
|
||||
* of essential SpEL features and customization options, targeting simple
|
||||
* condition evaluation and in particular data binding scenarios.
|
||||
*
|
||||
* <p>In many cases, the full extent of the SpEL is not
|
||||
* required and should be meaningfully restricted. Examples include but are not
|
||||
* limited to data binding expressions, property-based filters, and others. To
|
||||
* that effect, {@code SimpleEvaluationContext} supports only a subset of the
|
||||
* SpEL language syntax that excludes references to Java types, constructors,
|
||||
* <p>In many cases, the full extent of the SpEL language is not required and
|
||||
* should be meaningfully restricted. Examples include but are not limited to
|
||||
* data binding expressions, property-based filters, and others. To that effect,
|
||||
* {@code SimpleEvaluationContext} is tailored to support only a subset of the
|
||||
* SpEL language syntax, e.g. excluding references to Java types, constructors,
|
||||
* and bean references.
|
||||
*
|
||||
* <p>Note that {@code SimpleEvaluationContext} cannot be configured with a
|
||||
* default root object. Instead it is meant to be created once and used
|
||||
* repeatedly through method variants on
|
||||
* {@link org.springframework.expression.Expression Expression} that accept
|
||||
* both an {@code EvaluationContext} and a root object.
|
||||
* <p>When creating a {@code SimpleEvaluationContext} you need to choose the
|
||||
* level of support that you need for property access in SpEL expressions:
|
||||
* <ul>
|
||||
* <li>A custom {@code PropertyAccessor} (typically not reflection-based),
|
||||
* potentially combined with a {@link DataBindingPropertyAccessor}</li>
|
||||
* <li>Data binding properties for read-only access</li>
|
||||
* <li>Data binding properties for read and write</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Conveniently, {@link SimpleEvaluationContext#forReadOnlyDataBinding()}
|
||||
* enables read access to properties via {@link DataBindingPropertyAccessor};
|
||||
* same for {@link SimpleEvaluationContext#forReadWriteDataBinding()} when
|
||||
* write access is needed as well. Alternatively, configure custom accessors
|
||||
* via {@link SimpleEvaluationContext#forPropertyAccessors}, and potentially
|
||||
* activate method resolution and/or a type converter through the builder.
|
||||
*
|
||||
* <p>Note that {@code SimpleEvaluationContext} is typically not configured
|
||||
* with a default root object. Instead it is meant to be created once and
|
||||
* used repeatedly through {@code getValue} calls on a pre-compiled
|
||||
* {@link org.springframework.expression.Expression} with both an
|
||||
* {@code EvaluationContext} and a root object as arguments:
|
||||
* {@link org.springframework.expression.Expression#getValue(EvaluationContext, Object)}.
|
||||
*
|
||||
* <p>For more power and flexibility, in particular for internal configuration
|
||||
* scenarios, consider using {@link StandardEvaluationContext} instead.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Juergen Hoeller
|
||||
* @since 4.3.15
|
||||
* @see #forPropertyAccessors
|
||||
* @see #forReadOnlyDataBinding()
|
||||
* @see #forReadWriteDataBinding()
|
||||
* @see StandardEvaluationContext
|
||||
* @see StandardTypeConverter
|
||||
* @see DataBindingPropertyAccessor
|
||||
*/
|
||||
public class SimpleEvaluationContext implements EvaluationContext {
|
||||
|
||||
private static final TypeLocator typeNotFoundTypeLocator = new TypeLocator() {
|
||||
|
||||
@Override
|
||||
public Class<?> findType(String typeName) throws EvaluationException {
|
||||
throw new SpelEvaluationException(SpelMessage.TYPE_NOT_FOUND, typeName);
|
||||
@@ -68,13 +96,11 @@ public class SimpleEvaluationContext implements EvaluationContext {
|
||||
};
|
||||
|
||||
|
||||
private final TypedValue rootObject;
|
||||
|
||||
private final List<PropertyAccessor> propertyAccessors;
|
||||
|
||||
private final List<ConstructorResolver> constructorResolvers =
|
||||
Collections.<ConstructorResolver>singletonList(new ReflectiveConstructorResolver());
|
||||
|
||||
private final List<MethodResolver> methodResolvers =
|
||||
Collections.<MethodResolver>singletonList(new ReflectiveMethodResolver());
|
||||
private final List<MethodResolver> methodResolvers;
|
||||
|
||||
private final TypeConverter typeConverter;
|
||||
|
||||
@@ -85,52 +111,45 @@ public class SimpleEvaluationContext implements EvaluationContext {
|
||||
private final Map<String, Object> variables = new HashMap<String, Object>();
|
||||
|
||||
|
||||
public SimpleEvaluationContext() {
|
||||
this(null, null);
|
||||
}
|
||||
private SimpleEvaluationContext(List<PropertyAccessor> accessors, List<MethodResolver> resolvers,
|
||||
TypeConverter converter, TypedValue rootObject) {
|
||||
|
||||
public SimpleEvaluationContext(List<PropertyAccessor> accessors, TypeConverter converter) {
|
||||
this.propertyAccessors = initPropertyAccessors(accessors);
|
||||
this.typeConverter = converter != null ? converter : new StandardTypeConverter();
|
||||
}
|
||||
|
||||
|
||||
private static List<PropertyAccessor> initPropertyAccessors(List<PropertyAccessor> accessors) {
|
||||
if (accessors == null) {
|
||||
accessors = new ArrayList<PropertyAccessor>(5);
|
||||
accessors.add(new ReflectivePropertyAccessor());
|
||||
}
|
||||
return accessors;
|
||||
this.propertyAccessors = accessors;
|
||||
this.methodResolvers = resolvers;
|
||||
this.typeConverter = (converter != null ? converter : new StandardTypeConverter());
|
||||
this.rootObject = (rootObject != null ? rootObject : TypedValue.NULL);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@code SimpleEvaluationContext} cannot be configured with a root object.
|
||||
* It is meant for repeated use with
|
||||
* {@link org.springframework.expression.Expression Expression} method
|
||||
* variants that accept both an {@code EvaluationContext} and a root object.
|
||||
* @return Always returns {@link TypedValue#NULL}.
|
||||
* Return the specified root object, if any.
|
||||
*/
|
||||
@Override
|
||||
public TypedValue getRootObject() {
|
||||
return TypedValue.NULL;
|
||||
return this.rootObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the specified {@link PropertyAccessor} delegates, if any.
|
||||
* @see #forPropertyAccessors
|
||||
*/
|
||||
@Override
|
||||
public List<PropertyAccessor> getPropertyAccessors() {
|
||||
return this.propertyAccessors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a single {@link ReflectiveConstructorResolver}.
|
||||
* Return an empty list, always, since this context does not support the
|
||||
* use of type references.
|
||||
*/
|
||||
@Override
|
||||
public List<ConstructorResolver> getConstructorResolvers() {
|
||||
return this.constructorResolvers;
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a single {@link ReflectiveMethodResolver}.
|
||||
* Return the specified {@link MethodResolver} delegates, if any.
|
||||
* @see Builder#withMethodResolvers
|
||||
*/
|
||||
@Override
|
||||
public List<MethodResolver> getMethodResolvers() {
|
||||
@@ -138,8 +157,8 @@ public class SimpleEvaluationContext implements EvaluationContext {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code SimpleEvaluationContext} does not support use of bean references.
|
||||
* @return Always returns {@code null}
|
||||
* {@code SimpleEvaluationContext} does not support the use of bean references.
|
||||
* @return always {@code null}
|
||||
*/
|
||||
@Override
|
||||
public BeanResolver getBeanResolver() {
|
||||
@@ -159,6 +178,8 @@ public class SimpleEvaluationContext implements EvaluationContext {
|
||||
/**
|
||||
* The configured {@link TypeConverter}.
|
||||
* <p>By default this is {@link StandardTypeConverter}.
|
||||
* @see Builder#withTypeConverter
|
||||
* @see Builder#withConversionService
|
||||
*/
|
||||
@Override
|
||||
public TypeConverter getTypeConverter() {
|
||||
@@ -173,7 +194,6 @@ public class SimpleEvaluationContext implements EvaluationContext {
|
||||
return this.typeComparator;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return an instance of {@link StandardOperatorOverloader}.
|
||||
*/
|
||||
@@ -192,4 +212,145 @@ public class SimpleEvaluationContext implements EvaluationContext {
|
||||
return this.variables.get(name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a {@code SimpleEvaluationContext} for the specified {@link PropertyAccessor}
|
||||
* delegates: typically a custom {@code PropertyAccessor} specific to a use case
|
||||
* (e.g. attribute resolution in a custom data structure), potentially combined with
|
||||
* a {@link DataBindingPropertyAccessor} if property dereferences are needed as well.
|
||||
* @param accessors the accessor delegates to use
|
||||
* @see DataBindingPropertyAccessor#forReadOnlyAccess()
|
||||
* @see DataBindingPropertyAccessor#forReadWriteAccess()
|
||||
*/
|
||||
public static Builder forPropertyAccessors(PropertyAccessor... accessors) {
|
||||
for (PropertyAccessor accessor : accessors) {
|
||||
if (accessor.getClass() == ReflectivePropertyAccessor.class) {
|
||||
throw new IllegalArgumentException("SimpleEvaluationContext is not designed for use with a plain " +
|
||||
"ReflectivePropertyAccessor. Consider using DataBindingPropertyAccessor or a custom subclass.");
|
||||
}
|
||||
}
|
||||
return new Builder(accessors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@code SimpleEvaluationContext} for read-only access to
|
||||
* public properties via {@link DataBindingPropertyAccessor}.
|
||||
* @see DataBindingPropertyAccessor#forReadOnlyAccess()
|
||||
* @see #forPropertyAccessors
|
||||
*/
|
||||
public static Builder forReadOnlyDataBinding() {
|
||||
return new Builder(DataBindingPropertyAccessor.forReadOnlyAccess());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@code SimpleEvaluationContext} for read-write access to
|
||||
* public properties via {@link DataBindingPropertyAccessor}.
|
||||
* @see DataBindingPropertyAccessor#forReadWriteAccess()
|
||||
* @see #forPropertyAccessors
|
||||
*/
|
||||
public static Builder forReadWriteDataBinding() {
|
||||
return new Builder(DataBindingPropertyAccessor.forReadWriteAccess());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builder for {@code SimpleEvaluationContext}.
|
||||
*/
|
||||
public static class Builder {
|
||||
|
||||
private final List<PropertyAccessor> accessors;
|
||||
|
||||
private List<MethodResolver> resolvers = Collections.emptyList();
|
||||
|
||||
private TypeConverter typeConverter;
|
||||
|
||||
private TypedValue rootObject;
|
||||
|
||||
public Builder(PropertyAccessor... accessors) {
|
||||
this.accessors = Arrays.asList(accessors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the specified {@link MethodResolver} delegates for
|
||||
* a combination of property access and method resolution.
|
||||
* @param resolvers the resolver delegates to use
|
||||
* @see #withInstanceMethods()
|
||||
* @see SimpleEvaluationContext#forPropertyAccessors
|
||||
*/
|
||||
public Builder withMethodResolvers(MethodResolver... resolvers) {
|
||||
for (MethodResolver resolver : resolvers) {
|
||||
if (resolver.getClass() == ReflectiveMethodResolver.class) {
|
||||
throw new IllegalArgumentException("SimpleEvaluationContext is not designed for use with a plain " +
|
||||
"ReflectiveMethodResolver. Consider using DataBindingMethodResolver or a custom subclass.");
|
||||
}
|
||||
}
|
||||
this.resolvers = Arrays.asList(resolvers);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@link DataBindingMethodResolver} for instance method invocation purposes
|
||||
* (i.e. not supporting static methods) in addition to the specified property accessors,
|
||||
* typically in combination with a {@link DataBindingPropertyAccessor}.
|
||||
* @see #withMethodResolvers
|
||||
* @see SimpleEvaluationContext#forReadOnlyDataBinding()
|
||||
* @see SimpleEvaluationContext#forReadWriteDataBinding()
|
||||
*/
|
||||
public Builder withInstanceMethods() {
|
||||
this.resolvers = Collections.singletonList(
|
||||
(MethodResolver) DataBindingMethodResolver.forInstanceMethodInvocation());
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Register a custom {@link ConversionService}.
|
||||
* <p>By default a {@link StandardTypeConverter} backed by a
|
||||
* {@link org.springframework.core.convert.support.DefaultConversionService} is used.
|
||||
* @see #withTypeConverter
|
||||
* @see StandardTypeConverter#StandardTypeConverter(ConversionService)
|
||||
*/
|
||||
public Builder withConversionService(ConversionService conversionService) {
|
||||
this.typeConverter = new StandardTypeConverter(conversionService);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Register a custom {@link TypeConverter}.
|
||||
* <p>By default a {@link StandardTypeConverter} backed by a
|
||||
* {@link org.springframework.core.convert.support.DefaultConversionService} is used.
|
||||
* @see #withConversionService
|
||||
* @see StandardTypeConverter#StandardTypeConverter()
|
||||
*/
|
||||
public Builder withTypeConverter(TypeConverter converter) {
|
||||
this.typeConverter = converter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a default root object to resolve against.
|
||||
* <p>Default is none, expecting an object argument at evaluation time.
|
||||
* @see org.springframework.expression.Expression#getValue(EvaluationContext)
|
||||
* @see org.springframework.expression.Expression#getValue(EvaluationContext, Object)
|
||||
*/
|
||||
public Builder withRootObject(Object rootObject) {
|
||||
this.rootObject = new TypedValue(rootObject);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a typed root object to resolve against.
|
||||
* <p>Default is none, expecting an object argument at evaluation time.
|
||||
* @see org.springframework.expression.Expression#getValue(EvaluationContext)
|
||||
* @see org.springframework.expression.Expression#getValue(EvaluationContext, Object)
|
||||
*/
|
||||
public Builder withTypedRootObject(Object rootObject, TypeDescriptor typeDescriptor) {
|
||||
this.rootObject = new TypedValue(rootObject, typeDescriptor);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SimpleEvaluationContext build() {
|
||||
return new SimpleEvaluationContext(this.accessors, this.resolvers, this.typeConverter, this.rootObject);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 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,9 +37,13 @@ import org.springframework.expression.TypedValue;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Provides a default EvaluationContext implementation.
|
||||
* A powerful and highly configurable {@link EvaluationContext} implementation.
|
||||
* This context uses standard implementations of all applicable strategies,
|
||||
* based on reflection to resolve properties, methods and fields.
|
||||
*
|
||||
* <p>To resolve properties/methods/fields this context uses a reflection mechanism.
|
||||
* <p>For a simpler builder-style context variant for data-binding purposes,
|
||||
* consider using {@link SimpleEvaluationContext} instead which allows for
|
||||
* opting into several SpEL features as needed by specific evaluation cases.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Juergen Hoeller
|
||||
@@ -71,10 +75,18 @@ public class StandardEvaluationContext implements EvaluationContext {
|
||||
private final Map<String, Object> variables = new HashMap<String, Object>();
|
||||
|
||||
|
||||
/**
|
||||
* Create a {@code StandardEvaluationContext} with a null root object.
|
||||
*/
|
||||
public StandardEvaluationContext() {
|
||||
setRootObject(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@code StandardEvaluationContext} with the given root object.
|
||||
* @param rootObject the root object to use
|
||||
* @see #setRootObject
|
||||
*/
|
||||
public StandardEvaluationContext(Object rootObject) {
|
||||
setRootObject(rootObject);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 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.
|
||||
@@ -32,7 +32,9 @@ import org.springframework.expression.PropertyAccessor;
|
||||
import org.springframework.expression.TypedValue;
|
||||
import org.springframework.expression.spel.standard.SpelExpression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.SimpleEvaluationContext;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.expression.spel.testresources.Person;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@@ -73,7 +75,7 @@ public class PropertyAccessTests extends AbstractExpressionTests {
|
||||
* supplied resolver might be able to - so null shouldn't crash the reflection resolver.
|
||||
*/
|
||||
@Test
|
||||
public void testAccessingOnNullObject() throws Exception {
|
||||
public void testAccessingOnNullObject() {
|
||||
SpelExpression expr = (SpelExpression)parser.parseExpression("madeup");
|
||||
EvaluationContext context = new StandardEvaluationContext(null);
|
||||
try {
|
||||
@@ -85,7 +87,7 @@ public class PropertyAccessTests extends AbstractExpressionTests {
|
||||
}
|
||||
assertFalse(expr.isWritable(context));
|
||||
try {
|
||||
expr.setValue(context,"abc");
|
||||
expr.setValue(context, "abc");
|
||||
fail("Should have failed - default property resolver cannot resolve on null");
|
||||
}
|
||||
catch (Exception ex) {
|
||||
@@ -93,19 +95,19 @@ public class PropertyAccessTests extends AbstractExpressionTests {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkException(Exception e, SpelMessage expectedMessage) {
|
||||
if (e instanceof SpelEvaluationException) {
|
||||
SpelMessage sm = ((SpelEvaluationException)e).getMessageCode();
|
||||
assertEquals("Expected exception type did not occur",expectedMessage,sm);
|
||||
private void checkException(Exception ex, SpelMessage expectedMessage) {
|
||||
if (ex instanceof SpelEvaluationException) {
|
||||
SpelMessage sm = ((SpelEvaluationException) ex).getMessageCode();
|
||||
assertEquals("Expected exception type did not occur", expectedMessage, sm);
|
||||
}
|
||||
else {
|
||||
fail("Should be a SpelException "+e);
|
||||
fail("Should be a SpelException " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
// Adding a new property accessor just for a particular type
|
||||
public void testAddingSpecificPropertyAccessor() throws Exception {
|
||||
public void testAddingSpecificPropertyAccessor() {
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
StandardEvaluationContext ctx = new StandardEvaluationContext();
|
||||
|
||||
@@ -115,7 +117,7 @@ public class PropertyAccessTests extends AbstractExpressionTests {
|
||||
ctx.addPropertyAccessor(new StringyPropertyAccessor());
|
||||
Expression expr = parser.parseRaw("new String('hello').flibbles");
|
||||
Integer i = expr.getValue(ctx, Integer.class);
|
||||
assertEquals((int) i, 7);
|
||||
assertEquals(7, (int) i);
|
||||
|
||||
// The reflection one will be used for other properties...
|
||||
expr = parser.parseRaw("new String('hello').CASE_INSENSITIVE_ORDER");
|
||||
@@ -125,7 +127,7 @@ public class PropertyAccessTests extends AbstractExpressionTests {
|
||||
expr = parser.parseRaw("new String('hello').flibbles");
|
||||
expr.setValue(ctx, 99);
|
||||
i = expr.getValue(ctx, Integer.class);
|
||||
assertEquals((int) i, 99);
|
||||
assertEquals(99, (int) i);
|
||||
|
||||
// Cannot set it to a string value
|
||||
try {
|
||||
@@ -162,10 +164,10 @@ public class PropertyAccessTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAccessingPropertyOfClass() throws Exception {
|
||||
public void testAccessingPropertyOfClass() {
|
||||
Expression expression = parser.parseExpression("name");
|
||||
Object value = expression.getValue(new StandardEvaluationContext(String.class));
|
||||
assertEquals(value, "java.lang.String");
|
||||
assertEquals("java.lang.String", value);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -182,6 +184,78 @@ public class PropertyAccessTests extends AbstractExpressionTests {
|
||||
assertEquals("Jens", expression.getValue(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void standardGetClassAccess() {
|
||||
assertEquals(String.class.getName(), parser.parseExpression("'a'.class.name").getValue());
|
||||
}
|
||||
|
||||
@Test(expected = SpelEvaluationException.class)
|
||||
public void noGetClassAccess() {
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
|
||||
|
||||
parser.parseExpression("'a'.class.name").getValue(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void propertyReadOnly() {
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
|
||||
|
||||
Expression expr = parser.parseExpression("name");
|
||||
Person target = new Person("p1");
|
||||
assertEquals("p1", expr.getValue(context, target));
|
||||
target.setName("p2");
|
||||
assertEquals("p2", expr.getValue(context, target));
|
||||
|
||||
try {
|
||||
parser.parseExpression("name='p3'").getValue(context, target);
|
||||
fail("Should have thrown SpelEvaluationException");
|
||||
}
|
||||
catch (SpelEvaluationException ex) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void propertyReadWrite() {
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
|
||||
|
||||
Expression expr = parser.parseExpression("name");
|
||||
Person target = new Person("p1");
|
||||
assertEquals("p1", expr.getValue(context, target));
|
||||
target.setName("p2");
|
||||
assertEquals("p2", expr.getValue(context, target));
|
||||
|
||||
parser.parseExpression("name='p3'").getValue(context, target);
|
||||
assertEquals("p3", target.getName());
|
||||
assertEquals("p3", expr.getValue(context, target));
|
||||
|
||||
expr.setValue(context, target, "p4");
|
||||
assertEquals("p4", target.getName());
|
||||
assertEquals("p4", expr.getValue(context, target));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void propertyAccessWithoutMethodResolver() {
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
|
||||
|
||||
Person target = new Person("p1");
|
||||
try {
|
||||
parser.parseExpression("name.substring(1)").getValue(context, target);
|
||||
fail("Should have thrown SpelEvaluationException");
|
||||
}
|
||||
catch (SpelEvaluationException ex) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void propertyAccessWithInstanceMethodResolver() {
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().withInstanceMethods().build();
|
||||
|
||||
Person target = new Person("p1");
|
||||
assertEquals("1", parser.parseExpression("name.substring(1)").getValue(context, target));
|
||||
}
|
||||
|
||||
|
||||
// This can resolve the property 'flibbles' on any String (very useful...)
|
||||
private static class StringyPropertyAccessor implements PropertyAccessor {
|
||||
@@ -223,7 +297,8 @@ public class PropertyAccessTests extends AbstractExpressionTests {
|
||||
throw new RuntimeException("Assertion Failed! name should be flibbles");
|
||||
}
|
||||
try {
|
||||
flibbles = (Integer) context.getTypeConverter().convertValue(newValue, TypeDescriptor.forObject(newValue), TypeDescriptor.valueOf(Integer.class));
|
||||
flibbles = (Integer) context.getTypeConverter().convertValue(newValue,
|
||||
TypeDescriptor.forObject(newValue), TypeDescriptor.valueOf(Integer.class));
|
||||
}
|
||||
catch (EvaluationException ex) {
|
||||
throw new AccessException("Cannot set flibbles to an object of type '" + newValue.getClass() + "'");
|
||||
|
||||
Reference in New Issue
Block a user