SimpleEvaluationContext.Builder withRootObject/withTypedRootObject

Issue: SPR-16588
This commit is contained in:
Juergen Hoeller 2018-03-27 00:22:54 +02:00
parent 19875d8e3f
commit c60cefa331
2 changed files with 72 additions and 17 deletions

View File

@ -23,9 +23,11 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.BeanResolver; import org.springframework.expression.BeanResolver;
import org.springframework.expression.ConstructorResolver; import org.springframework.expression.ConstructorResolver;
import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.MethodResolver; import org.springframework.expression.MethodResolver;
import org.springframework.expression.OperatorOverloader; import org.springframework.expression.OperatorOverloader;
import org.springframework.expression.PropertyAccessor; import org.springframework.expression.PropertyAccessor;
@ -90,6 +92,8 @@ public class SimpleEvaluationContext implements EvaluationContext {
}; };
private final TypedValue rootObject;
private final List<PropertyAccessor> propertyAccessors; private final List<PropertyAccessor> propertyAccessors;
private final List<MethodResolver> methodResolvers; private final List<MethodResolver> methodResolvers;
@ -104,24 +108,21 @@ public class SimpleEvaluationContext implements EvaluationContext {
private SimpleEvaluationContext(List<PropertyAccessor> accessors, List<MethodResolver> resolvers, private SimpleEvaluationContext(List<PropertyAccessor> accessors, List<MethodResolver> resolvers,
@Nullable TypeConverter converter) { @Nullable TypeConverter converter, @Nullable TypedValue rootObject) {
this.propertyAccessors = accessors; this.propertyAccessors = accessors;
this.methodResolvers = resolvers; this.methodResolvers = resolvers;
this.typeConverter = (converter != null ? converter : new StandardTypeConverter()); this.typeConverter = (converter != null ? converter : new StandardTypeConverter());
this.rootObject = (rootObject != null ? rootObject : TypedValue.NULL);
} }
/** /**
* {@code SimpleEvaluationContext} cannot be configured with a root object. * Return the specified root object, if any.
* 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}.
*/ */
@Override @Override
public TypedValue getRootObject() { public TypedValue getRootObject() {
return TypedValue.NULL; return this.rootObject;
} }
/** /**
@ -262,6 +263,9 @@ public class SimpleEvaluationContext implements EvaluationContext {
@Nullable @Nullable
private TypeConverter typeConverter; private TypeConverter typeConverter;
@Nullable
private TypedValue rootObject;
public Builder(PropertyAccessor... accessors) { public Builder(PropertyAccessor... accessors) {
this.accessors = Arrays.asList(accessors); this.accessors = Arrays.asList(accessors);
} }
@ -297,6 +301,18 @@ public class SimpleEvaluationContext implements EvaluationContext {
return this; 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}. * Register a custom {@link TypeConverter}.
* <p>By default a {@link StandardTypeConverter} backed by a * <p>By default a {@link StandardTypeConverter} backed by a
@ -310,19 +326,29 @@ public class SimpleEvaluationContext implements EvaluationContext {
} }
/** /**
* Register a custom {@link ConversionService}. * Specify a default root object to resolve against.
* <p>By default a {@link StandardTypeConverter} backed by a * <p>Default is none, expecting an object argument at evaluation time.
* {@link org.springframework.core.convert.support.DefaultConversionService} is used. * @see Expression#getValue(EvaluationContext)
* @see #withTypeConverter * @see Expression#getValue(EvaluationContext, Object)
* @see StandardTypeConverter#StandardTypeConverter(ConversionService)
*/ */
public Builder withConversionService(ConversionService conversionService) { public Builder withRootObject(Object rootObject) {
this.typeConverter = new StandardTypeConverter(conversionService); 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 Expression#getValue(EvaluationContext)
* @see Expression#getValue(EvaluationContext, Object)
*/
public Builder withTypedRootObject(Object rootObject, TypeDescriptor typeDescriptor) {
this.rootObject = new TypedValue(rootObject, typeDescriptor);
return this; return this;
} }
public SimpleEvaluationContext build() { public SimpleEvaluationContext build() {
return new SimpleEvaluationContext(this.accessors, this.resolvers, this.typeConverter); return new SimpleEvaluationContext(this.accessors, this.resolvers, this.typeConverter, this.rootObject);
} }
} }

View File

@ -234,10 +234,29 @@ public class PropertyAccessTests extends AbstractExpressionTests {
assertEquals("p4", expr.getValue(context, target)); assertEquals("p4", expr.getValue(context, target));
} }
@Test
public void propertyReadWriteWithRootObject() {
Person target = new Person("p1");
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().withRootObject(target).build();
assertSame(target, context.getRootObject().getValue());
Expression expr = parser.parseExpression("name");
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 @Test
public void propertyAccessWithoutMethodResolver() { public void propertyAccessWithoutMethodResolver() {
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Person target = new Person("p1"); Person target = new Person("p1");
try { try {
parser.parseExpression("name.substring(1)").getValue(context, target); parser.parseExpression("name.substring(1)").getValue(context, target);
@ -251,11 +270,21 @@ public class PropertyAccessTests extends AbstractExpressionTests {
@Test @Test
public void propertyAccessWithInstanceMethodResolver() { public void propertyAccessWithInstanceMethodResolver() {
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().withInstanceMethods().build(); EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().withInstanceMethods().build();
Person target = new Person("p1"); Person target = new Person("p1");
assertEquals("1", parser.parseExpression("name.substring(1)").getValue(context, target)); assertEquals("1", parser.parseExpression("name.substring(1)").getValue(context, target));
} }
@Test
public void propertyAccessWithInstanceMethodResolverAndTypedRootObject() {
Person target = new Person("p1");
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().
withInstanceMethods().withTypedRootObject(target, TypeDescriptor.valueOf(Object.class)).build();
assertEquals("1", parser.parseExpression("name.substring(1)").getValue(context, target));
assertSame(target, context.getRootObject().getValue());
assertSame(Object.class, context.getRootObject().getTypeDescriptor().getType());
}
// This can resolve the property 'flibbles' on any String (very useful...) // This can resolve the property 'flibbles' on any String (very useful...)
private static class StringyPropertyAccessor implements PropertyAccessor { private static class StringyPropertyAccessor implements PropertyAccessor {