removed user values in favor of simple map after code review

git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@1421 50f2f4bb-b051-0410-bef5-90022cba6387
This commit is contained in:
Keith Donald 2009-06-25 03:17:04 +00:00
parent dd0f2a666e
commit 700aa802c0
11 changed files with 107 additions and 255 deletions

View File

@ -24,7 +24,6 @@ import org.springframework.ui.format.Formatter;
* Binds user-entered values to properties of a model object.
* @author Keith Donald
* @since 3.0
* @param <M> The kind of model object this binder binds to
* @see #configureBinding(BindingConfiguration)
* @see #bind(UserValues)
*/
@ -35,7 +34,7 @@ public interface Binder {
* @return the model object
*/
Object getModel();
/**
* Configures if this binder is <i>strict</i>; a strict binder requires all bindings to be registered explicitly using {@link #configureBinding(BindingConfiguration)}.
* An <i>optimistic</i> binder will implicitly create bindings as required to support {@link #bind(UserValues)} operations.
@ -64,6 +63,13 @@ public interface Binder {
*/
void registerFormatterFactory(AnnotationFormatterFactory<?, ?> factory);
/**
* Configures the registry of Formatters to query when no explicit Formatter has been registered for a Binding.
* Allows Formatters to be applied by property type and by property annotation.
* @param registry the formatter registry
*/
void setFormatterRegistry(FormatterRegistry registry);
/**
* Returns the binding for the property.
* @param property the property path
@ -72,20 +78,10 @@ public interface Binder {
Binding getBinding(String property);
/**
* Bind values in the map to the properties of the model object.
* @param values user-entered values to bind
* Bind source values in the map to the properties of the model object.
* @param values the source values to bind
* @return the results of the binding operation
*/
BindingResults bind(UserValues values);
/**
* Creates a {@link UserValue} list from a Map of user-submitted fields.
* The Binder may apply transformations as part of the creation process.
* For example, a Binder might insert empty or default values for fields that are not present.
* As another example, a Binder might collapse multiple fields into a single {@link UserValue} object.
* @param userMap the map of user-submitted fields
* @return the UserValue list that can be passed to {@link #bind(UserValues)}.
*/
UserValues createUserValues(Map<String, ? extends Object> userMap);
BindingResults bind(Map<String, ? extends Object> sourceValues);
}

View File

@ -56,7 +56,7 @@ public interface Binding {
/**
* The type of the underlying property associated with this binding.
*/
Class<?>getType();
Class<?> getType();
}

View File

@ -48,7 +48,7 @@ public class BindingConfiguration {
}
/**
* THe Formatter to use to format bound property values.
* The Formatter to use to format bound property values.
*/
public Formatter<?> getFormatter() {
return formatter;

View File

@ -32,11 +32,11 @@ public interface BindingResult {
String getProperty();
/**
* The raw user-entered value for which binding was attempted.
* The raw source value for which binding was attempted.
* If not a failure, this value was successfully bound to the model.
* @see #isFailure()
*/
Object getUserValue();
Object getSourceValue();
/**
* Indicates if the binding failed.

View File

@ -1,54 +0,0 @@
/*
* Copyright 2004-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.ui.binding;
/**
* Holds a user-entered value to bind to a model property.
* @author Keith Donald
* @since 3.0
* @see Binder#bind(List)
*/
public class UserValue {
private String property;
private Object value;
/**
* Create a new user value
* @param property the property associated with the value
* @param value the actual user-entered value
*/
public UserValue(String property, Object value) {
this.property = property;
this.value = value;
}
/**
* The property the user-entered value should bind to.
*/
public String getProperty() {
return property;
}
/**
* The actual user-entered value.
*/
public Object getValue() {
return value;
}
}

View File

@ -1,90 +0,0 @@
/*
* Copyright 2004-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.ui.binding;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* A holder for a list of UserValues.
* TODO just use a map here
* @author Keith Donald
* @since 3.0
* @see Binder#bind(UserValues)
*/
public class UserValues implements Iterable<UserValue> {
private List<UserValue> values;
/**
* Creates a new user values list of the default size.
*/
public UserValues() {
values = new ArrayList<UserValue>();
}
/**
* Creates a new user values list of the size provided.
*/
public UserValues(int size) {
values = new ArrayList<UserValue>(size);
}
// implementing Iterable
public Iterator<UserValue> iterator() {
return values.iterator();
}
/**
* The user values list.
* The returned list is not modifiable.
*/
public List<UserValue> asList() {
return Collections.unmodifiableList(values);
}
/**
* Add a new user value.
* @param property the property the value should be bound to
* @param value the actual user-entered value
*/
public void add(String property, Object value) {
values.add(new UserValue(property, value));
}
/**
* The number of user values in the list.
*/
public int size() {
return values.size();
}
/**
* Creates a new UserValues list with a single element.
* @param property the property
* @param value the actual user-entered value
* @return the singleton user value list
*/
public static UserValues single(String property, Object value) {
UserValues values = new UserValues(1);
values.add(property, value);
return values;
}
}

View File

@ -56,15 +56,13 @@ import org.springframework.ui.binding.BindingConfiguration;
import org.springframework.ui.binding.BindingResult;
import org.springframework.ui.binding.BindingResults;
import org.springframework.ui.binding.FormatterRegistry;
import org.springframework.ui.binding.UserValue;
import org.springframework.ui.binding.UserValues;
import org.springframework.ui.format.AnnotationFormatterFactory;
import org.springframework.ui.format.Formatter;
import org.springframework.util.Assert;
/**
* A generic {@link Binder binder} suitable for use in most environments.
* TODO - localization of alert messages using MessageResolver/MesageSource
* TODO - localization of alert messages using MessageResolver/MessageSource
* @author Keith Donald
* @since 3.0
* @see #configureBinding(BindingConfiguration)
@ -94,7 +92,7 @@ public class GenericBinder implements Binder {
* @param model the model object containing properties this binder will bind to
*/
public GenericBinder(Object model) {
Assert.notNull(model, "The model Object is reqyured");
Assert.notNull(model, "The model Object is required");
this.model = model;
bindings = new HashMap<String, Binding>();
int parserConfig = SpelExpressionParserConfiguration.CreateListsOnAttemptToIndexIntoNull
@ -148,25 +146,31 @@ public class GenericBinder implements Binder {
}
}
public BindingResults bind(UserValues values) {
ArrayListBindingResults results = new ArrayListBindingResults(values.size());
for (UserValue value : values) {
BindingImpl binding = (BindingImpl) getBinding(value.getProperty());
public BindingResults bind(Map<String, ? extends Object> sourceValues) {
sourceValues = filter(sourceValues);
ArrayListBindingResults results = new ArrayListBindingResults(sourceValues.size());
for (Map.Entry<String, ? extends Object> sourceValue : sourceValues.entrySet()) {
String property = sourceValue.getKey();
Object value = sourceValue.getValue();
BindingImpl binding = (BindingImpl) getBinding(property);
if (binding != null) {
results.add(binding.setValue(value.getValue()));
results.add(binding.setValue(value));
} else {
results.add(new NoSuchBindingResult(value));
results.add(new NoSuchBindingResult(property, value));
}
}
}
return results;
}
public UserValues createUserValues(Map<String, ? extends Object> userMap) {
UserValues values = new UserValues(userMap.size());
for (Map.Entry<String, ? extends Object> entry : userMap.entrySet()) {
values.add(entry.getKey(), entry.getValue());
}
return values;
// subclassing hooks
/**
* Hook subclasses may use to filter the source values to bind.
* @param sourceValues the original source values map provided by the caller
* @return the filtered source values map that will be used to bind
*/
protected Map<String, ? extends Object> filter(Map<String, ? extends Object> sourceValues) {
return sourceValues;
}
// internal helpers
@ -458,18 +462,21 @@ public class GenericBinder implements Binder {
}
static class NoSuchBindingResult implements BindingResult {
private UserValue userValue;
private String property;
public NoSuchBindingResult(UserValue userValue) {
this.userValue = userValue;
private Object sourceValue;
public NoSuchBindingResult(String property, Object sourceValue) {
this.property = property;
this.sourceValue = sourceValue;
}
public String getProperty() {
return userValue.getProperty();
return property;
}
public Object getUserValue() {
return userValue.getValue();
public Object getSourceValue() {
return sourceValue;
}
public boolean isFailure() {
@ -492,7 +499,7 @@ public class GenericBinder implements Binder {
}
public String getMessage() {
return "Failed to bind to property '" + userValue.getProperty() + "'; no binding has been added for the property";
return "Failed to bind to property '" + property + "'; no binding has been added for the property";
}
};
}
@ -513,7 +520,7 @@ public class GenericBinder implements Binder {
return property;
}
public Object getUserValue() {
public Object getSourceValue() {
return formatted;
}
@ -563,7 +570,7 @@ public class GenericBinder implements Binder {
return property;
}
public Object getUserValue() {
public Object getSourceValue() {
return formatted;
}
@ -651,7 +658,7 @@ public class GenericBinder implements Binder {
return property;
}
public Object getUserValue() {
public Object getSourceValue() {
return formatted;
}

View File

@ -15,11 +15,9 @@
*/
package org.springframework.ui.binding.support;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.ui.binding.UserValue;
import org.springframework.ui.binding.UserValues;
/**
* A binder designed for use in HTTP (web) environments.
* Suited for binding user-provided HTTP query parameters to model properties.
@ -61,27 +59,27 @@ public class WebBinder extends GenericBinder {
}
@Override
public UserValues createUserValues(Map<String, ? extends Object> userMap) {
UserValues values = new UserValues();
for (Map.Entry<String, ? extends Object> entry : userMap.entrySet()) {
protected Map<String, ? extends Object> filter(Map<String, ? extends Object> sourceValues) {
LinkedHashMap<String, Object> filteredValues = new LinkedHashMap<String, Object>();
for (Map.Entry<String, ? extends Object> entry : sourceValues.entrySet()) {
String field = entry.getKey();
Object value = entry.getValue();
if (field.startsWith(defaultPrefix)) {
field = field.substring(defaultPrefix.length());
if (!userMap.containsKey(field)) {
values.add(field, value);
if (!sourceValues.containsKey(field)) {
filteredValues.put(field, value);
}
} else if (field.startsWith(presentPrefix)) {
field = field.substring(presentPrefix.length());
if (!userMap.containsKey(field) && !userMap.containsKey(defaultPrefix + field)) {
if (!sourceValues.containsKey(field) && !sourceValues.containsKey(defaultPrefix + field)) {
value = getEmptyValue((BindingImpl) getBinding(field));
values.add(field, value);
filteredValues.put(field, value);
}
} else {
values.add(entry.getKey(), entry.getValue());
filteredValues.put(entry.getKey(), entry.getValue());
}
}
return values;
return filteredValues;
}
protected Object getEmptyValue(BindingImpl binding) {

View File

@ -30,7 +30,6 @@ import org.springframework.ui.binding.BindingResults;
import org.springframework.ui.binding.Bound;
import org.springframework.ui.binding.FormatterRegistry;
import org.springframework.ui.binding.Model;
import org.springframework.ui.binding.UserValues;
import org.springframework.ui.binding.support.WebBinder;
import org.springframework.ui.validation.Validator;
import org.springframework.util.StringUtils;
@ -62,8 +61,7 @@ public class WebBindAndValidateLifecycle {
}
public void execute(Map<String, ? extends Object> userMap) {
UserValues values = binder.createUserValues(userMap);
BindingResults bindingResults = binder.bind(values);
BindingResults bindingResults = binder.bind(userMap);
if (validator != null && validationDecider.shouldValidateAfter(bindingResults)) {
// TODO get validation results
validator.validate(binder.getModel(), bindingResults.successes().properties());

View File

@ -7,6 +7,7 @@ import static org.junit.Assert.assertTrue;
import java.math.BigDecimal;
import java.text.ParseException;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
@ -24,7 +25,6 @@ import org.springframework.ui.binding.Binding;
import org.springframework.ui.binding.BindingConfiguration;
import org.springframework.ui.binding.BindingResult;
import org.springframework.ui.binding.BindingResults;
import org.springframework.ui.binding.UserValues;
import org.springframework.ui.format.AnnotationFormatterFactory;
import org.springframework.ui.format.Formatter;
import org.springframework.ui.format.date.DateFormatter;
@ -52,24 +52,24 @@ public class GenericBinderTests {
@Test
public void bindSingleValuesWithDefaultTypeConverterConversion() {
UserValues values = new UserValues();
values.add("string", "test");
values.add("integer", "3");
values.add("foo", "BAR");
Map<String, String> values = new LinkedHashMap<String, String>();
values.put("string", "test");
values.put("integer", "3");
values.put("foo", "BAR");
BindingResults results = binder.bind(values);
assertEquals(3, results.size());
assertEquals("string", results.get(0).getProperty());
assertFalse(results.get(0).isFailure());
assertEquals("test", results.get(0).getUserValue());
assertEquals("test", results.get(0).getSourceValue());
assertEquals("integer", results.get(1).getProperty());
assertFalse(results.get(1).isFailure());
assertEquals("3", results.get(1).getUserValue());
assertEquals("3", results.get(1).getSourceValue());
assertEquals("foo", results.get(2).getProperty());
assertFalse(results.get(2).isFailure());
assertEquals("BAR", results.get(2).getUserValue());
assertEquals("BAR", results.get(2).getSourceValue());
assertEquals("test", bean.getString());
assertEquals(3, bean.getInteger());
@ -78,11 +78,11 @@ public class GenericBinderTests {
@Test
public void bindSingleValuesWithDefaultTypeCoversionFailure() {
UserValues values = new UserValues();
values.add("string", "test");
Map<String, String> values = new LinkedHashMap<String, String>();
values.put("string", "test");
// bad value
values.add("integer", "bogus");
values.add("foo", "BAR");
values.put("integer", "bogus");
values.put("foo", "BAR");
BindingResults results = binder.bind(values);
assertEquals(3, results.size());
assertTrue(results.get(1).isFailure());
@ -92,40 +92,40 @@ public class GenericBinderTests {
@Test
public void bindSingleValuePropertyFormatter() throws ParseException {
binder.configureBinding(new BindingConfiguration("date", new DateFormatter()));
binder.bind(UserValues.single("date", "2009-06-01"));
binder.bind(Collections.singletonMap("date", "2009-06-01"));
assertEquals(new DateFormatter().parse("2009-06-01", Locale.US), bean.getDate());
}
@Test
public void bindSingleValuePropertyFormatterParseException() {
binder.configureBinding(new BindingConfiguration("date", new DateFormatter()));
binder.bind(UserValues.single("date", "bogus"));
binder.bind(Collections.singletonMap("date", "bogus"));
}
@Test
public void bindSingleValueWithFormatterRegistedByType() throws ParseException {
binder.registerFormatter(Date.class, new DateFormatter());
binder.bind(UserValues.single("date", "2009-06-01"));
binder.bind(Collections.singletonMap("date", "2009-06-01"));
assertEquals(new DateFormatter().parse("2009-06-01", Locale.US), bean.getDate());
}
@Test
public void bindSingleValueWithFormatterRegisteredByAnnotation() throws ParseException {
binder.registerFormatter(CurrencyFormat.class, new CurrencyFormatter());
binder.bind(UserValues.single("currency", "$23.56"));
binder.bind(Collections.singletonMap("currency", "$23.56"));
assertEquals(new BigDecimal("23.56"), bean.getCurrency());
}
@Test
public void bindSingleValueWithnAnnotationFormatterFactoryRegistered() throws ParseException {
binder.registerFormatterFactory(new CurrencyAnnotationFormatterFactory());
binder.bind(UserValues.single("currency", "$23.56"));
binder.bind(Collections.singletonMap("currency", "$23.56"));
assertEquals(new BigDecimal("23.56"), bean.getCurrency());
}
@Test
public void bindSingleValuePropertyNotFound() throws ParseException {
BindingResults results = binder.bind(UserValues.single("bogus", "2009-06-01"));
BindingResults results = binder.bind(Collections.singletonMap("bogus", "2009-06-01"));
assertEquals(1, results.size());
assertTrue(results.get(0).isFailure());
assertEquals("propertyNotFound", results.get(0).getAlert().getCode());
@ -136,11 +136,10 @@ public class GenericBinderTests {
Map<String, String> userMap = new LinkedHashMap<String, String>();
userMap.put("string", "test");
userMap.put("integer", "3");
UserValues values = binder.createUserValues(userMap);
BindingResults results = binder.bind(values);
BindingResults results = binder.bind(userMap);
assertEquals(2, results.size());
assertEquals("test", results.get(0).getUserValue());
assertEquals("3", results.get(1).getUserValue());
assertEquals("test", results.get(0).getSourceValue());
assertEquals("3", results.get(1).getSourceValue());
assertEquals("test", bean.getString());
assertEquals(3, bean.getInteger());
}
@ -173,19 +172,19 @@ public class GenericBinderTests {
public void bindStrictNoMappingBindings() {
binder.setStrict(true);
binder.configureBinding(new BindingConfiguration("integer", null));
UserValues values = new UserValues();
values.add("integer", "3");
values.add("foo", "BAR");
Map<String, String> values = new LinkedHashMap<String, String>();
values.put("integer", "3");
values.put("foo", "BAR");
BindingResults results = binder.bind(values);
assertEquals(2, results.size());
assertEquals("integer", results.get(0).getProperty());
assertFalse(results.get(0).isFailure());
assertEquals("3", results.get(0).getUserValue());
assertEquals("3", results.get(0).getSourceValue());
assertEquals("foo", results.get(1).getProperty());
assertTrue(results.get(1).isFailure());
assertEquals("BAR", results.get(1).getUserValue());
assertEquals("BAR", results.get(1).getSourceValue());
}
@Test
@ -235,7 +234,7 @@ public class GenericBinderTests {
@Test
public void bindHandleNullValueInNestedPath() {
UserValues values = new UserValues();
Map<String, String> values = new LinkedHashMap<String, String>();
// EL configured with some options from SpelExpressionParserConfiguration:
// (see where Binder creates the parser)
@ -244,22 +243,22 @@ public class GenericBinderTests {
// are new instances of the type of the list entry, they are not null.
// not currently doing anything for maps or arrays
values.add("addresses[0].street", "4655 Macy Lane");
values.add("addresses[0].city", "Melbourne");
values.add("addresses[0].state", "FL");
values.add("addresses[0].zip", "35452");
values.put("addresses[0].street", "4655 Macy Lane");
values.put("addresses[0].city", "Melbourne");
values.put("addresses[0].state", "FL");
values.put("addresses[0].zip", "35452");
// Auto adds new Address at 1
values.add("addresses[1].street", "1234 Rostock Circle");
values.add("addresses[1].city", "Palm Bay");
values.add("addresses[1].state", "FL");
values.add("addresses[1].zip", "32901");
values.put("addresses[1].street", "1234 Rostock Circle");
values.put("addresses[1].city", "Palm Bay");
values.put("addresses[1].state", "FL");
values.put("addresses[1].zip", "32901");
// Auto adds new Address at 5 (plus intermediates 2,3,4)
values.add("addresses[5].street", "1234 Rostock Circle");
values.add("addresses[5].city", "Palm Bay");
values.add("addresses[5].state", "FL");
values.add("addresses[5].zip", "32901");
values.put("addresses[5].street", "1234 Rostock Circle");
values.put("addresses[5].city", "Palm Bay");
values.put("addresses[5].state", "FL");
values.put("addresses[5].zip", "32901");
BindingResults results = binder.bind(values);
Assert.assertEquals(6, bean.addresses.size());

View File

@ -17,7 +17,6 @@ import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.ui.binding.Binder;
import org.springframework.ui.binding.BindingConfiguration;
import org.springframework.ui.binding.BindingResults;
import org.springframework.ui.binding.UserValues;
import org.springframework.ui.format.date.DateFormatter;
import org.springframework.ui.format.number.CurrencyFormat;
import org.springframework.ui.format.number.CurrencyFormatter;
@ -50,15 +49,14 @@ public class WebBinderTests {
userMap.put("!currency", "$5.00");
userMap.put("_currency", "doesn't matter");
userMap.put("_addresses", "doesn't matter");
UserValues values = binder.createUserValues(userMap);
BindingResults results = binder.bind(values);
BindingResults results = binder.bind(userMap);
assertEquals(6, results.size());
assertEquals("test", results.get(0).getUserValue());
assertEquals(null, results.get(1).getUserValue());
assertEquals(Boolean.FALSE, results.get(2).getUserValue());
assertEquals("2009-06-10", results.get(3).getUserValue());
assertEquals("$5.00", results.get(4).getUserValue());
assertEquals(null, results.get(5).getUserValue());
assertEquals("test", results.get(0).getSourceValue());
assertEquals(null, results.get(1).getSourceValue());
assertEquals(Boolean.FALSE, results.get(2).getSourceValue());
assertEquals("2009-06-10", results.get(3).getSourceValue());
assertEquals("$5.00", results.get(4).getSourceValue());
assertEquals(null, results.get(5).getSourceValue());
assertEquals("test", bean.getString());
assertEquals(0, bean.getInteger());