Refactored class naming to capture ui.binding system as a general PresentationModel framework; PresentationModel and FieldModel are the key API elements now

This commit is contained in:
Keith Donald 2009-07-24 15:50:37 +00:00
parent c7b019cd5c
commit 5cb5169237
33 changed files with 635 additions and 590 deletions

View File

@ -16,9 +16,10 @@
package org.springframework.ui.binding; package org.springframework.ui.binding;
/** /**
* Binding states. * FieldModel binding states.
* @author Keith Donald * @author Keith Donald
* @since 3.0 * @since 3.0
* @see FieldModel#getBindingStatus()
*/ */
public enum BindingStatus { public enum BindingStatus {
@ -28,9 +29,9 @@ public enum BindingStatus {
CLEAN, CLEAN,
/** /**
* An invalid source value is applied. * An invalid submitted value is applied.
*/ */
INVALID_SOURCE_VALUE, INVALID_SUBMITTED_VALUE,
/** /**
* The binding buffer contains a valid value that has not been committed. * The binding buffer contains a valid value that has not been committed.

View File

@ -19,11 +19,11 @@ import org.springframework.ui.alert.Alert;
import org.springframework.ui.alert.Severity; import org.springframework.ui.alert.Severity;
/** /**
* A binding between one or more UI components and a model property. * A model for a single data field containing dynamic information to display in the view.
* @author Keith Donald * @author Keith Donald
* @since 3.0 * @since 3.0
*/ */
public interface Binding { public interface FieldModel {
/** /**
* The model value formatted for display in a single field in the UI. * The model value formatted for display in a single field in the UI.
@ -33,47 +33,47 @@ public interface Binding {
String getRenderValue(); String getRenderValue();
/** /**
* The bound model value. * The field model value.
*/ */
Object getValue(); Object getValue();
/** /**
* The bound model value type. * The field model value type.
*/ */
Class<?> getValueType(); Class<?> getValueType();
/** /**
* If this Binding is editable. * If editable.
* Used to determine if the user can edit the field value. * Used to determine if the user can edit the field value.
* A Binding that is not editable cannot have source values applied and cannot be committed. * A Binding that is not editable cannot have submitted values applied and cannot be committed.
*/ */
boolean isEditable(); boolean isEditable();
/** /**
* If this Binding is enabled. * If enabled.
* Used to determine if the user can interact with the field at all. * Used to determine if the user can interact with the field at all.
* A Binding that is not enabled cannot have source values applied and cannot be committed. * A Binding that is not enabled cannot have submitted values applied and cannot be committed.
*/ */
boolean isEnabled(); boolean isEnabled();
/** /**
* If this Binding is visible. * If visible.
* Used to determine if the user can see the field. * Used to determine if the user can see the field.
*/ */
boolean isVisible(); boolean isVisible();
/** /**
* The current binding status. * The current field binding status.
* Initially {@link BindingStatus#CLEAN clean}. * Initially {@link BindingStatus#CLEAN clean}.
* Is {@link BindingStatus#DIRTY} after applying a source value to the value buffer. * Is {@link BindingStatus#DIRTY} after applying a submitted value to the value buffer.
* Is {@link BindingStatus#COMMITTED} after successfully committing the buffered value. * Is {@link BindingStatus#COMMITTED} after successfully committing the buffered value.
* Is {@link BindingStatus#INVALID_SOURCE_VALUE} if a source value could not be applied. * Is {@link BindingStatus#INVALID_SUBMITTED_VALUE} if a submitted value could not be applied.
* Is {@link BindingStatus#COMMIT_FAILURE} if a buffered value could not be committed. * Is {@link BindingStatus#COMMIT_FAILURE} if a buffered value could not be committed.
*/ */
BindingStatus getBindingStatus(); BindingStatus getBindingStatus();
/** /**
* The current validation status. * The current field validation status.
* Initially {@link ValidationStatus#NOT_VALIDATED}. * Initially {@link ValidationStatus#NOT_VALIDATED}.
* Is {@link ValidationStatus#VALID} after value is successfully validated. * Is {@link ValidationStatus#VALID} after value is successfully validated.
* Is {@link ValidationStatus#INVALID} after value fails validation. * Is {@link ValidationStatus#INVALID} after value fails validation.
@ -82,31 +82,31 @@ public interface Binding {
ValidationStatus getValidationStatus(); ValidationStatus getValidationStatus();
/** /**
* An alert that communicates current status to the user. * An alert that communicates current FieldModel status to the user.
* Returns <code>null</code> if {@link BindingStatus#CLEAN} and {@link ValidationStatus#NOT_VALIDATED}. * Returns <code>null</code> if {@link BindingStatus#CLEAN} and {@link ValidationStatus#NOT_VALIDATED}.
* Returns a {@link Severity#INFO} Alert with code <code>bindSuccess</code> when {@link BindingStatus#COMMITTED}. * Returns a {@link Severity#INFO} Alert with code <code>bindSuccess</code> when {@link BindingStatus#COMMITTED}.
* Returns a {@link Severity#ERROR} Alert with code <code>typeMismatch</code> when {@link BindingStatus#INVALID_SOURCE_VALUE} or {@link BindingStatus#COMMIT_FAILURE} due to a value parse / type conversion error. * Returns a {@link Severity#ERROR} Alert with code <code>typeMismatch</code> when {@link BindingStatus#INVALID_SUBMITTED_VALUE} or {@link BindingStatus#COMMIT_FAILURE} due to a value parse / type conversion error.
* Returns a {@link Severity#FATAL} Alert with code <code>internalError</code> when {@link BindingStatus#COMMIT_FAILURE} due to a unexpected runtime exception. * Returns a {@link Severity#FATAL} Alert with code <code>internalError</code> when {@link BindingStatus#COMMIT_FAILURE} due to a unexpected runtime exception.
* Returns a {@link Severity#INFO} Alert describing results of validation if {@link ValidationStatus#VALID} or {@link ValidationStatus#INVALID}. * Returns a {@link Severity#INFO} Alert describing results of validation if {@link ValidationStatus#VALID} or {@link ValidationStatus#INVALID}.
*/ */
Alert getStatusAlert(); Alert getStatusAlert();
/** /**
* Apply the source value to this binding. * Apply a submitted value to this FieldModel.
* The source value is parsed and stored in the binding's value buffer. * The submitted value is parsed and stored in the value buffer.
* Sets to {@link BindingStatus#DIRTY} if succeeds. * Sets to {@link BindingStatus#DIRTY} if succeeds.
* Sets to {@link BindingStatus#INVALID_SOURCE_VALUE} if fails. * Sets to {@link BindingStatus#INVALID_SUBMITTED_VALUE} if fails.
* @param sourceValue * @param submittedValue
* @throws IllegalStateException if not editable or not enabled * @throws IllegalStateException if not editable or not enabled
*/ */
void applySourceValue(Object sourceValue); void applySubmittedValue(Object submittedValue);
/** /**
* If {@link BindingStatus#INVALID_SOURCE_VALUE}, returns the invalid source value. * If {@link BindingStatus#INVALID_SUBMITTED_VALUE}, returns the invalid submitted value.
* Returns null otherwise. * Returns null otherwise.
* @return the invalid source value * @return the invalid submitted value
*/ */
Object getInvalidSourceValue(); Object getInvalidSubmittedValue();
/** /**
* Validate the model value. * Validate the model value.
@ -130,45 +130,46 @@ public interface Binding {
void revert(); void revert();
/** /**
* Get a Binding to a nested property value. * Get a model for a nested field.
* @param property the nested property name, such as "foo"; should not be a property path like "foo.bar" * @param fieldName the nested field name, such as "foo"; should not be a property path like "foo.bar"
* @return the binding to the nested property * @return the nested field model
* @throws IllegalStateException if not a bean * @throws IllegalStateException if {@link #isList()}
* @throws FieldNotFoundException if no such nested field exists
*/ */
Binding getNestedBinding(String property); FieldModel getNested(String fieldName);
/** /**
* If bound to an indexable collection, either a {@link java.util.List} or an array. * If an indexable {@link java.util.List} or array.
*/ */
boolean isList(); boolean isList();
/** /**
* If a list, get a Binding to a element in the list.. * If {@link #isList()}, get a FieldModel for a element in the list..
* @param index the element index * @param index the element index
* @return the indexed binding * @return the indexed binding
* @throws IllegalStateException if not a list * @throws IllegalStateException if not a list
*/ */
Binding getListElementBinding(int index); FieldModel getListElement(int index);
/** /**
* If bound to a Map. * If a Map.
*/ */
boolean isMap(); boolean isMap();
/** /**
* If a Map, get a Binding to a value in the Map. * If {@link #isMap()}, get FieldModel for a value in the Map.
* @param key the map key * @param key the map key
* @return the keyed binding * @return the keyed binding
* @throws IllegalStateException if not a map * @throws IllegalStateException if not a map
*/ */
Binding getMapValueBinding(Object key); FieldModel getMapValue(Object key);
/** /**
* Format a potential model value for display. * Format a potential model value for display.
* If a list binding, expects the model value to be a potential list element & uses the configured element formatter. * If {@link #isList()}, expects the value to be a potential list element & uses the configured element formatter.
* If a map binding, expects the model value to be a potential map value & uses the configured map value formatter. * If {@link #isMap()}, expects the value to be a potential map value & uses the configured map value formatter.
* @param potentialValue the potential value * @param potentialValue the potential value
* @return the formatted string * @return the formatted string
*/ */
String formatValue(Object potentialModelValue); String formatValue(Object potentialValue);
} }

View File

@ -16,22 +16,27 @@
package org.springframework.ui.binding; package org.springframework.ui.binding;
/** /**
* A factory for model property bindings. * Thrown when a PresentationModel field cannot be found.
* @author Keith Donald * @author Keith Donald
* @since 3.0 * @since 3.0
* @see PresentationModel#getFieldModel(String)
* @see FieldModel#getNested(String)
*/ */
public interface BindingFactory { @SuppressWarnings("serial")
public class FieldNotFoundException extends RuntimeException {
private String field;
/** /**
* The model object upon which bindings may be accessed. * Creates a new FieldNotFoundException.
* @param fieldName the field not found exception
*/ */
Object getModel(); public FieldNotFoundException(String fieldName) {
super("No field '" + fieldName + "' found");
}
/** public String getField() {
* Get a binding to a model property. return field;
* @param property the property path }
* @throws NoSuchBindingException if no binding to the property exists
*/
Binding getBinding(String property);
} }

View File

@ -0,0 +1,34 @@
/*
* 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;
/**
* Represents the state and behavior of a presentation independently of the GUI controls used in the interface.
* Pulls the state and behavior of a view out into a model class that is part of the presentation.
* Coordinates with the domain layer and provides an interface to the view that minimizes decision making in the view.
* @author Keith Donald
* @since 3.0
*/
public interface PresentationModel {
/**
* Get the model for the field.
* @param fieldName the field name.
* @throws FieldNotFoundException if no such field exists
*/
FieldModel getFieldModel(String fieldName);
}

View File

@ -16,19 +16,19 @@
package org.springframework.ui.binding; package org.springframework.ui.binding;
/** /**
* A locator for BindingFactories indexed by their models. * A factory for domain object PresentationModels.
* Makes it easy for clients to lookup BindingFactories for models the need to bind to. * Makes it easy for clients to lookup PresentationModels for domain objects they need to bind to.
* @author Keith Donald * @author Keith Donald
* @since 3.0 * @since 3.0
*/ */
public interface BindingFactoryLocator { public interface PresentationModelFactory {
/** /**
* Get the BindingFactory for the model object. * Get the PresentationModel for the domain object.
* If no such BindingFactory exists, one is created and cached. * If none exists, one is created and cached.
* Never returns <code>null</code>. * Never returns <code>null</code>.
* @param model the model object * @param domainObject the model object
* @return the binding Factory * @return the presentation model
*/ */
public BindingFactory getBindingFactory(Object model); public PresentationModel getPresentationModel(Object domainObject);
} }

View File

@ -16,9 +16,10 @@
package org.springframework.ui.binding; package org.springframework.ui.binding;
/** /**
* Validation states. * FieldModel Validation states.
* @author Keith Donald * @author Keith Donald
* @since 3.0 * @since 3.0
* @see FieldModel#getValidationStatus()
*/ */
public enum ValidationStatus { public enum ValidationStatus {

View File

@ -70,7 +70,7 @@ class ArrayListBindingResults implements BindingResults {
public List<String> properties() { public List<String> properties() {
List<String> properties = new ArrayList<String>(results.size()); List<String> properties = new ArrayList<String>(results.size());
for (BindingResult result : this) { for (BindingResult result : this) {
properties.add(result.getProperty()); properties.add(result.getFieldName());
} }
return properties; return properties;
} }

View File

@ -17,7 +17,7 @@ package org.springframework.ui.binding.binder;
import java.util.Map; import java.util.Map;
import org.springframework.ui.binding.Binding; import org.springframework.ui.binding.FieldModel;
/** /**
* Binds user-entered values to properties of a model object. * Binds user-entered values to properties of a model object.
@ -28,18 +28,13 @@ import org.springframework.ui.binding.Binding;
*/ */
public interface Binder { public interface Binder {
/**
* The model this binder binds to.
*/
public Object getModel();
/** /**
* Bind the source values to the properties of the model. * Bind the source values to the properties of the model.
* A result is returned for each registered {@link Binding}. * A result is returned for each registered {@link FieldModel}.
* @param sourceValues the source values to bind * @param fieldValues the field values to bind
* @return the results of the binding operation * @return the results of the binding operation
* @throws MissingSourceValuesException when the sourceValues Map is missing entries for required bindings * @throws MissingFieldException when the fieldValues Map is missing entries for required bindings
*/ */
BindingResults bind(Map<String, ? extends Object> sourceValues); BindingResults bind(Map<String, ? extends Object> fieldValues);
} }

View File

@ -15,7 +15,7 @@
*/ */
package org.springframework.ui.binding.binder; package org.springframework.ui.binding.binder;
import org.springframework.ui.binding.config.BindingRuleConfiguration; import org.springframework.ui.binding.config.FieldModelConfiguration;
/** /**
* A SPI interface that lets you configure a model binder, then execute it. * A SPI interface that lets you configure a model binder, then execute it.
@ -37,7 +37,7 @@ public interface BinderExecutor<M> {
* @param property the model property * @param property the model property
* @return a builder API for configuring the rule * @return a builder API for configuring the rule
*/ */
BindingRuleConfiguration bindingRule(String property); FieldModelConfiguration bindingRule(String property);
// TODO allow injection of pre-created BindingRules // TODO allow injection of pre-created BindingRules

View File

@ -26,17 +26,17 @@ import org.springframework.ui.alert.Alert;
public interface BindingResult { public interface BindingResult {
/** /**
* The model property this binding result is for. * The name of the field this binding result is for.
* @see Binder#getNestedBinding(String) * @see Binder#getNested(String)
*/ */
String getProperty(); String getFieldName();
/** /**
* The raw source value for which binding was attempted. * The raw submitted value for which binding was attempted.
* If not a failure, this value was successfully bound to the model. * If not a failure, this value was successfully bound to the model.
* @see #isFailure() * @see #isFailure()
*/ */
Object getSourceValue(); Object getSubmittedValue();
/** /**
* Indicates if the binding failed. * Indicates if the binding failed.

View File

@ -20,23 +20,23 @@ import org.springframework.ui.alert.Severity;
class BindingStatusResult implements BindingResult { class BindingStatusResult implements BindingResult {
private String property; private String fieldName;
private Object sourceValue; private Object sourceValue;
private Alert bindingStatusAlert; private Alert bindingStatusAlert;
public BindingStatusResult(String property, Object sourceValue, Alert alert) { public BindingStatusResult(String fieldName, Object sourceValue, Alert alert) {
this.property = property; this.fieldName = fieldName;
this.sourceValue = sourceValue; this.sourceValue = sourceValue;
this.bindingStatusAlert = alert; this.bindingStatusAlert = alert;
} }
public String getProperty() { public String getFieldName() {
return property; return fieldName;
} }
public Object getSourceValue() { public Object getSubmittedValue() {
return sourceValue; return sourceValue;
} }

View File

@ -22,7 +22,7 @@ import org.springframework.ui.alert.Severity;
import org.springframework.ui.message.MessageBuilder; import org.springframework.ui.message.MessageBuilder;
import org.springframework.ui.message.ResolvableArgument; import org.springframework.ui.message.ResolvableArgument;
class PropertyNotEditableResult implements BindingResult { class FieldNotEditableResult implements BindingResult {
private String property; private String property;
@ -30,17 +30,17 @@ class PropertyNotEditableResult implements BindingResult {
private MessageSource messageSource; private MessageSource messageSource;
public PropertyNotEditableResult(String property, Object sourceValue, MessageSource messageSource) { public FieldNotEditableResult(String property, Object sourceValue, MessageSource messageSource) {
this.property = property; this.property = property;
this.sourceValue = sourceValue; this.sourceValue = sourceValue;
this.messageSource = messageSource; this.messageSource = messageSource;
} }
public String getProperty() { public String getFieldName() {
return property; return property;
} }
public Object getSourceValue() { public Object getSubmittedValue() {
return sourceValue; return sourceValue;
} }

View File

@ -22,7 +22,7 @@ import org.springframework.ui.alert.Severity;
import org.springframework.ui.message.MessageBuilder; import org.springframework.ui.message.MessageBuilder;
import org.springframework.ui.message.ResolvableArgument; import org.springframework.ui.message.ResolvableArgument;
class PropertyNotFoundResult implements BindingResult { class FieldNotFoundResult implements BindingResult {
private String property; private String property;
@ -30,17 +30,17 @@ class PropertyNotFoundResult implements BindingResult {
private MessageSource messageSource; private MessageSource messageSource;
public PropertyNotFoundResult(String property, Object sourceValue, MessageSource messageSource) { public FieldNotFoundResult(String property, Object sourceValue, MessageSource messageSource) {
this.property = property; this.property = property;
this.sourceValue = sourceValue; this.sourceValue = sourceValue;
this.messageSource = messageSource; this.messageSource = messageSource;
} }
public String getProperty() { public String getFieldName() {
return property; return property;
} }
public Object getSourceValue() { public Object getSubmittedValue() {
return sourceValue; return sourceValue;
} }

View File

@ -21,10 +21,10 @@ import java.util.Map;
import org.springframework.context.MessageSource; import org.springframework.context.MessageSource;
import org.springframework.core.convert.TypeConverter; import org.springframework.core.convert.TypeConverter;
import org.springframework.ui.binding.Binding; import org.springframework.ui.binding.FieldModel;
import org.springframework.ui.binding.BindingFactory; import org.springframework.ui.binding.FieldNotFoundException;
import org.springframework.ui.binding.PresentationModel;
import org.springframework.ui.binding.BindingStatus; import org.springframework.ui.binding.BindingStatus;
import org.springframework.ui.binding.support.PropertyNotFoundException;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
@ -37,15 +37,15 @@ import org.springframework.util.Assert;
*/ */
public class GenericBinder implements Binder { public class GenericBinder implements Binder {
private BindingFactory bindingFactory; private PresentationModel presentationModel;
private String[] requiredProperties; private String[] requiredFields;
private MessageSource messageSource; private MessageSource messageSource;
public GenericBinder(BindingFactory bindingFactory) { public GenericBinder(PresentationModel presentationModel) {
Assert.notNull(bindingFactory, "The BindingFactory is required"); Assert.notNull(presentationModel, "The PresentationModel is required");
this.bindingFactory = bindingFactory; this.presentationModel = presentationModel;
} }
/** /**
@ -58,34 +58,38 @@ public class GenericBinder implements Binder {
} }
/** /**
* Configure the properties for which source values must be present in each bind attempt. * Configure the fields for which values must be present in each bind attempt.
* @param propertyPaths the property path expressions * @param fieldNames the field names
* @see MissingSourceValuesException * @see MissingFieldException
*/ */
public void setRequired(String[] propertyPaths) { public void setRequiredFields(String[] fieldNames) {
this.requiredProperties = propertyPaths; this.requiredFields = fieldNames;
} }
public Object getModel() { // subclassing hooks
return bindingFactory.getModel();
}
protected Binding getBinding(String property) { /**
return bindingFactory.getBinding(property); * Get the model for the field.
* @param fieldName
* @return the field model
* @throws NoSuchFieldException if no such field exists
*/
protected FieldModel getFieldModel(String fieldName) {
return presentationModel.getFieldModel(fieldName);
} }
// implementing Binder // implementing Binder
public BindingResults bind(Map<String, ? extends Object> sourceValues) { public BindingResults bind(Map<String, ? extends Object> fieldValues) {
sourceValues = filter(sourceValues); fieldValues = filter(fieldValues);
checkRequired(sourceValues); checkRequired(fieldValues);
ArrayListBindingResults results = new ArrayListBindingResults(sourceValues.size()); ArrayListBindingResults results = new ArrayListBindingResults(fieldValues.size());
for (Map.Entry<String, ? extends Object> sourceValue : sourceValues.entrySet()) { for (Map.Entry<String, ? extends Object> fieldValue : fieldValues.entrySet()) {
try { try {
Binding binding = getBinding(sourceValue.getKey()); FieldModel field = getFieldModel(fieldValue.getKey());
results.add(bind(sourceValue, binding)); results.add(bind(fieldValue, field));
} catch (PropertyNotFoundException e) { } catch (FieldNotFoundException e) {
results.add(new PropertyNotFoundResult(sourceValue.getKey(), sourceValue.getValue(), messageSource)); results.add(new FieldNotFoundResult(fieldValue.getKey(), fieldValue.getValue(), messageSource));
} }
} }
return results; return results;
@ -95,26 +99,26 @@ public class GenericBinder implements Binder {
/** /**
* Hook subclasses may use to filter the source values to bind. * Hook subclasses may use to filter the source values to bind.
* This hook allows the binder to pre-process the source values before binding occurs. * This hook allows the binder to pre-process the field values before binding occurs.
* For example, a Binder might insert empty or default values for fields that are not present. * For example, a Binder might insert empty or default values for fields that are not present.
* As another example, a Binder might collapse multiple source values into a single source value. * As another example, a Binder might collapse multiple source values into a single source value.
* @param sourceValues the original source values map provided by the caller * @param fieldValues the original fieldValues map provided by the caller
* @return the filtered source values map that will be used to bind * @return the filtered fieldValues map that will be used to bind
*/ */
protected Map<String, ? extends Object> filter(Map<String, ? extends Object> sourceValues) { protected Map<String, ? extends Object> filter(Map<String, ? extends Object> fieldValues) {
return sourceValues; return fieldValues;
} }
// internal helpers // internal helpers
private void checkRequired(Map<String, ? extends Object> sourceValues) { private void checkRequired(Map<String, ? extends Object> fieldValues) {
if (requiredProperties == null) { if (requiredFields == null) {
return; return;
} }
List<String> missingRequired = new ArrayList<String>(); List<String> missingRequired = new ArrayList<String>();
for (String required : requiredProperties) { for (String required : requiredFields) {
boolean found = false; boolean found = false;
for (String property : sourceValues.keySet()) { for (String property : fieldValues.keySet()) {
if (property.equals(required)) { if (property.equals(required)) {
found = true; found = true;
} }
@ -124,21 +128,21 @@ public class GenericBinder implements Binder {
} }
} }
if (!missingRequired.isEmpty()) { if (!missingRequired.isEmpty()) {
throw new MissingSourceValuesException(missingRequired, sourceValues); throw new MissingFieldException(missingRequired, fieldValues);
} }
} }
private BindingResult bind(Map.Entry<String, ? extends Object> sourceValue, Binding binding) { private BindingResult bind(Map.Entry<String, ? extends Object> fieldValue, FieldModel field) {
String property = sourceValue.getKey(); String fieldName = fieldValue.getKey();
Object value = sourceValue.getValue(); Object value = fieldValue.getValue();
if (!binding.isEditable()) { if (!field.isEditable()) {
return new PropertyNotEditableResult(property, value, messageSource); return new FieldNotEditableResult(fieldName, value, messageSource);
} else { } else {
binding.applySourceValue(value); field.applySubmittedValue(value);
if (binding.getBindingStatus() == BindingStatus.DIRTY) { if (field.getBindingStatus() == BindingStatus.DIRTY) {
binding.commit(); field.commit();
} }
return new BindingStatusResult(property, value, binding.getStatusAlert()); return new BindingStatusResult(fieldName, value, field.getStatusAlert());
} }
} }

View File

@ -26,7 +26,7 @@ import java.util.Map;
* @see Binder#bind(java.util.Map) * @see Binder#bind(java.util.Map)
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class MissingSourceValuesException extends RuntimeException { public class MissingFieldException extends RuntimeException {
private List<String> missing; private List<String> missing;
@ -35,7 +35,7 @@ public class MissingSourceValuesException extends RuntimeException {
* @param missing * @param missing
* @param sourceValues * @param sourceValues
*/ */
public MissingSourceValuesException(List<String> missing, Map<String, ? extends Object> sourceValues) { public MissingFieldException(List<String> missing, Map<String, ? extends Object> sourceValues) {
super(getMessage(missing, sourceValues)); super(getMessage(missing, sourceValues));
this.missing = missing; this.missing = missing;
} }
@ -49,9 +49,9 @@ public class MissingSourceValuesException extends RuntimeException {
private static String getMessage(List<String> missingRequired, Map<String, ? extends Object> sourceValues) { private static String getMessage(List<String> missingRequired, Map<String, ? extends Object> sourceValues) {
if (missingRequired.size() == 1) { if (missingRequired.size() == 1) {
return "Missing a source value for required propertyPath [" + missingRequired.get(0) + "]; sourceValues map contained " + sourceValues.keySet(); return "Missing a field [" + missingRequired.get(0) + "]; fieldValues map contained " + sourceValues.keySet();
} else { } else {
return "Missing source values to required propertyPaths " + missingRequired + "; sourceValues map contained " + sourceValues.keySet(); return "Missing fields " + missingRequired + "; fieldValues map contained " + sourceValues.keySet();
} }
} }

View File

@ -18,8 +18,8 @@ package org.springframework.ui.binding.binder;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import org.springframework.ui.binding.Binding; import org.springframework.ui.binding.FieldModel;
import org.springframework.ui.binding.BindingFactory; import org.springframework.ui.binding.PresentationModel;
/** /**
* A binder designed for use in HTTP (web) environments. * A binder designed for use in HTTP (web) environments.
@ -39,13 +39,12 @@ public class WebBinder extends GenericBinder {
* Creates a new web binder for the model object. * Creates a new web binder for the model object.
* @param model the model object containing properties this binder will bind to * @param model the model object containing properties this binder will bind to
*/ */
public WebBinder(BindingFactory bindingFactory) { public WebBinder(PresentationModel bindingFactory) {
super(bindingFactory); super(bindingFactory);
} }
/** /**
* Configure the prefix to detect the default user value for a property when no value was submitted. * Configure the prefix used to detect the default value for a field when no value is submitted.
* This is used to configure a <i>default</i> {@link UserValue} for binding when no value is submitted by the client.
* Default is '!'. * Default is '!'.
*/ */
public void setDefaultPrefix(String defaultPrefix) { public void setDefaultPrefix(String defaultPrefix) {
@ -53,8 +52,8 @@ public class WebBinder extends GenericBinder {
} }
/** /**
* Configure the prefix to detect the presence of a property on the web UI when no user value for the property was actually submitted. * Configure the prefix used to detect the presence of a field on the web UI when no value was actually submitted.
* This is used to configure a <i>empty</i> {@link UserValue} for binding when no other {@link #setDefaultPrefix(String) default value} is specified by the client. * This is used to configure a <i>empty</i> field when no other {@link #setDefaultPrefix(String) default value} is specified by the client.
* Default is '_'. * Default is '_'.
*/ */
public void setPresentPrefix(String presentPrefix) { public void setPresentPrefix(String presentPrefix) {
@ -62,20 +61,20 @@ public class WebBinder extends GenericBinder {
} }
@Override @Override
protected Map<String, ? extends Object> filter(Map<String, ? extends Object> sourceValues) { protected Map<String, ? extends Object> filter(Map<String, ? extends Object> fieldValues) {
LinkedHashMap<String, Object> filteredValues = new LinkedHashMap<String, Object>(); LinkedHashMap<String, Object> filteredValues = new LinkedHashMap<String, Object>();
for (Map.Entry<String, ? extends Object> entry : sourceValues.entrySet()) { for (Map.Entry<String, ? extends Object> entry : fieldValues.entrySet()) {
String field = entry.getKey(); String field = entry.getKey();
Object value = entry.getValue(); Object value = entry.getValue();
if (field.startsWith(defaultPrefix)) { if (field.startsWith(defaultPrefix)) {
field = field.substring(defaultPrefix.length()); field = field.substring(defaultPrefix.length());
if (!sourceValues.containsKey(field)) { if (!fieldValues.containsKey(field)) {
filteredValues.put(field, value); filteredValues.put(field, value);
} }
} else if (field.startsWith(presentPrefix)) { } else if (field.startsWith(presentPrefix)) {
field = field.substring(presentPrefix.length()); field = field.substring(presentPrefix.length());
if (!sourceValues.containsKey(field) && !sourceValues.containsKey(defaultPrefix + field)) { if (!fieldValues.containsKey(field) && !fieldValues.containsKey(defaultPrefix + field)) {
value = getEmptyValue(getBinding(field)); value = getEmptyValue(getFieldModel(field));
filteredValues.put(field, value); filteredValues.put(field, value);
} }
} else { } else {
@ -85,7 +84,7 @@ public class WebBinder extends GenericBinder {
return filteredValues; return filteredValues;
} }
protected Object getEmptyValue(Binding binding) { protected Object getEmptyValue(FieldModel binding) {
Class<?> type = binding.getValueType(); Class<?> type = binding.getValueType();
if (boolean.class.equals(type) || Boolean.class.equals(type)) { if (boolean.class.equals(type) || Boolean.class.equals(type)) {
return Boolean.FALSE; return Boolean.FALSE;

View File

@ -21,35 +21,35 @@ import org.springframework.ui.format.Formatter;
* A fluent interface for configuring a newly added binding rule. * A fluent interface for configuring a newly added binding rule.
* @author Keith Donald * @author Keith Donald
*/ */
public interface BindingRuleConfiguration { public interface FieldModelConfiguration {
/** /**
* Set the Formatter to use to format bound property values. * Set the Formatter to use to format bound property values.
*/ */
BindingRuleConfiguration formatWith(Formatter<?> formatter); FieldModelConfiguration formatWith(Formatter<?> formatter);
/** /**
* If a map property, set the Formatter to use to format map keys. * If a map property, set the Formatter to use to format map keys.
*/ */
BindingRuleConfiguration formatKeysWith(Formatter<?> formatter); FieldModelConfiguration formatKeysWith(Formatter<?> formatter);
/** /**
* If an list or map property, set the Formatter to use to format indexed elements. * If an list or map property, set the Formatter to use to format indexed elements.
*/ */
BindingRuleConfiguration formatElementsWith(Formatter<?> formatter); FieldModelConfiguration formatElementsWith(Formatter<?> formatter);
/** /**
* Set when the binding is editable. * Set when the binding is editable.
*/ */
BindingRuleConfiguration editableWhen(Condition condition); FieldModelConfiguration editableWhen(Condition condition);
/** /**
* Set when the binding is enabled. * Set when the binding is enabled.
*/ */
BindingRuleConfiguration enabledWhen(Condition condition); FieldModelConfiguration enabledWhen(Condition condition);
/** /**
* Set when the binding is visible. * Set when the binding is visible.
*/ */
BindingRuleConfiguration visibleWhen(Condition condition); FieldModelConfiguration visibleWhen(Condition condition);
} }

View File

@ -15,29 +15,26 @@
*/ */
package org.springframework.ui.binding.support; package org.springframework.ui.binding.support;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
public class CollectionTypeDescriptor { public class CollectionTypeDescriptor {
private Class<?> collectionType; private Class<?> type;
private Class<?> elementType; private Class<?> elementType;
/** public CollectionTypeDescriptor(Class<?> type, Class<?> elementType) {
* Creates a new generic collection property type Assert.notNull(type, "The collection type is required");
* @param collectionType the collection type this.type = type;
* @param elementType the element type
*/
public CollectionTypeDescriptor(Class<?> collectionType, Class<?> elementType) {
this.collectionType = collectionType;
this.elementType = elementType; this.elementType = elementType;
} }
/** /**
* The collection type. * The collection type.
*/ */
public Class<?> getCollectionType() { public Class<?> getType() {
return collectionType; return type;
} }
/** /**
@ -52,11 +49,11 @@ public class CollectionTypeDescriptor {
return false; return false;
} }
CollectionTypeDescriptor type = (CollectionTypeDescriptor) o; CollectionTypeDescriptor type = (CollectionTypeDescriptor) o;
return collectionType.equals(type.collectionType) return type.equals(type.type)
&& ObjectUtils.nullSafeEquals(elementType, type.elementType); && ObjectUtils.nullSafeEquals(elementType, type.elementType);
} }
public int hashCode() { public int hashCode() {
return collectionType.hashCode() + elementType.hashCode(); return type.hashCode() + elementType.hashCode();
} }
} }

View File

@ -34,38 +34,38 @@ import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.style.StylerUtils; import org.springframework.core.style.StylerUtils;
import org.springframework.ui.alert.Alert; import org.springframework.ui.alert.Alert;
import org.springframework.ui.alert.Severity; import org.springframework.ui.alert.Severity;
import org.springframework.ui.binding.Binding;
import org.springframework.ui.binding.BindingStatus; import org.springframework.ui.binding.BindingStatus;
import org.springframework.ui.binding.FieldModel;
import org.springframework.ui.binding.ValidationStatus; import org.springframework.ui.binding.ValidationStatus;
import org.springframework.ui.format.Formatter; import org.springframework.ui.format.Formatter;
import org.springframework.ui.message.MessageBuilder; import org.springframework.ui.message.MessageBuilder;
import org.springframework.ui.message.ResolvableArgument; import org.springframework.ui.message.ResolvableArgument;
public class GenericBinding implements Binding { public class DefaultFieldModel implements FieldModel {
private ValueModel valueModel; private ValueModel valueModel;
private BindingContext bindingContext; private FieldModelContext context;
private ValueBuffer buffer; private ValueBuffer buffer;
private BindingStatus bindingStatus; private BindingStatus bindingStatus;
private Object sourceValue; private Object submittedValue;
private Exception invalidSourceValueCause; private Exception invalidSubmittedValueCause;
public GenericBinding(ValueModel valueModel, BindingContext bindingContext) { public DefaultFieldModel(ValueModel valueModel, FieldModelContext context) {
this.valueModel = valueModel; this.valueModel = valueModel;
this.bindingContext = bindingContext; this.context = context;
buffer = new ValueBuffer(valueModel); buffer = new ValueBuffer(valueModel);
bindingStatus = BindingStatus.CLEAN; bindingStatus = BindingStatus.CLEAN;
} }
// implementing Binding // implementing FieldModel
public String getRenderValue() { public String getRenderValue() {
return format(getValue(), bindingContext.getFormatter()); return format(getValue(), context.getFormatter());
} }
public Object getValue() { public Object getValue() {
@ -81,42 +81,42 @@ public class GenericBinding implements Binding {
} }
public boolean isEditable() { public boolean isEditable() {
return valueModel.isWriteable() && bindingContext.getEditableCondition().isTrue(); return valueModel.isWriteable() && context.getEditableCondition().isTrue();
} }
public boolean isEnabled() { public boolean isEnabled() {
return bindingContext.getEnabledCondition().isTrue(); return context.getEnabledCondition().isTrue();
} }
public boolean isVisible() { public boolean isVisible() {
return bindingContext.getVisibleCondition().isTrue(); return context.getVisibleCondition().isTrue();
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void applySourceValue(Object sourceValue) { public void applySubmittedValue(Object submittedValue) {
assertEditable(); assertEditable();
assertEnabled(); assertEnabled();
if (sourceValue instanceof String) { if (submittedValue instanceof String) {
try { try {
Object parsed = bindingContext.getFormatter().parse((String) sourceValue, getLocale()); Object parsed = context.getFormatter().parse((String) submittedValue, getLocale());
buffer.setValue(coerseToValueType(parsed)); buffer.setValue(coerseToValueType(parsed));
sourceValue = null; submittedValue = null;
bindingStatus = BindingStatus.DIRTY; bindingStatus = BindingStatus.DIRTY;
} catch (ParseException e) { } catch (ParseException e) {
this.sourceValue = sourceValue; this.submittedValue = submittedValue;
invalidSourceValueCause = e; invalidSubmittedValueCause = e;
bindingStatus = BindingStatus.INVALID_SOURCE_VALUE; bindingStatus = BindingStatus.INVALID_SUBMITTED_VALUE;
} catch (ConversionFailedException e) { } catch (ConversionFailedException e) {
this.sourceValue = sourceValue; this.submittedValue = submittedValue;
invalidSourceValueCause = e; invalidSubmittedValueCause = e;
bindingStatus = BindingStatus.INVALID_SOURCE_VALUE; bindingStatus = BindingStatus.INVALID_SUBMITTED_VALUE;
} }
} else if (sourceValue instanceof String[]) { } else if (submittedValue instanceof String[]) {
Object parsed; Object parsed;
if (isMap()) { if (isMap()) {
String[] sourceValues = (String[]) sourceValue; String[] sourceValues = (String[]) submittedValue;
Formatter keyFormatter = bindingContext.getKeyFormatter(); Formatter keyFormatter = context.getKeyFormatter();
Formatter valueFormatter = bindingContext.getElementFormatter(); Formatter valueFormatter = context.getElementFormatter();
Map map = new LinkedHashMap(sourceValues.length); Map map = new LinkedHashMap(sourceValues.length);
for (int i = 0; i < sourceValues.length; i++) { for (int i = 0; i < sourceValues.length; i++) {
String entryString = sourceValues[i]; String entryString = sourceValues[i];
@ -126,59 +126,59 @@ public class GenericBinding implements Binding {
Object parsedMapValue = valueFormatter.parse(keyValue[1], getLocale()); Object parsedMapValue = valueFormatter.parse(keyValue[1], getLocale());
map.put(parsedMapKey, parsedMapValue); map.put(parsedMapKey, parsedMapValue);
} catch (ParseException e) { } catch (ParseException e) {
this.sourceValue = sourceValue; this.submittedValue = submittedValue;
invalidSourceValueCause = e; invalidSubmittedValueCause = e;
bindingStatus = BindingStatus.INVALID_SOURCE_VALUE; bindingStatus = BindingStatus.INVALID_SUBMITTED_VALUE;
break; break;
} }
} }
parsed = map; parsed = map;
} else { } else {
String[] sourceValues = (String[]) sourceValue; String[] sourceValues = (String[]) submittedValue;
List list = new ArrayList(sourceValues.length); List list = new ArrayList(sourceValues.length);
for (int i = 0; i < sourceValues.length; i++) { for (int i = 0; i < sourceValues.length; i++) {
Object parsedValue; Object parsedValue;
try { try {
parsedValue = bindingContext.getElementFormatter().parse(sourceValues[i], getLocale()); parsedValue = context.getElementFormatter().parse(sourceValues[i], getLocale());
list.add(parsedValue); list.add(parsedValue);
} catch (ParseException e) { } catch (ParseException e) {
this.sourceValue = sourceValue; this.submittedValue = submittedValue;
invalidSourceValueCause = e; invalidSubmittedValueCause = e;
bindingStatus = BindingStatus.INVALID_SOURCE_VALUE; bindingStatus = BindingStatus.INVALID_SUBMITTED_VALUE;
break; break;
} }
} }
parsed = list; parsed = list;
} }
if (bindingStatus != BindingStatus.INVALID_SOURCE_VALUE) { if (bindingStatus != BindingStatus.INVALID_SUBMITTED_VALUE) {
try { try {
buffer.setValue(coerseToValueType(parsed)); buffer.setValue(coerseToValueType(parsed));
sourceValue = null; submittedValue = null;
bindingStatus = BindingStatus.DIRTY; bindingStatus = BindingStatus.DIRTY;
} catch (ConversionFailedException e) { } catch (ConversionFailedException e) {
this.sourceValue = sourceValue; this.submittedValue = submittedValue;
invalidSourceValueCause = e; invalidSubmittedValueCause = e;
bindingStatus = BindingStatus.INVALID_SOURCE_VALUE; bindingStatus = BindingStatus.INVALID_SUBMITTED_VALUE;
} }
} }
} else { } else {
try { try {
buffer.setValue(coerseToValueType(sourceValue)); buffer.setValue(coerseToValueType(submittedValue));
sourceValue = null; submittedValue = null;
bindingStatus = BindingStatus.DIRTY; bindingStatus = BindingStatus.DIRTY;
} catch (ConversionFailedException e) { } catch (ConversionFailedException e) {
this.sourceValue = sourceValue; this.submittedValue = submittedValue;
invalidSourceValueCause = e; invalidSubmittedValueCause = e;
bindingStatus = BindingStatus.INVALID_SOURCE_VALUE; bindingStatus = BindingStatus.INVALID_SUBMITTED_VALUE;
} }
} }
} }
public Object getInvalidSourceValue() { public Object getInvalidSubmittedValue() {
if (bindingStatus != BindingStatus.INVALID_SOURCE_VALUE) { if (bindingStatus != BindingStatus.INVALID_SUBMITTED_VALUE) {
throw new IllegalStateException("No invalid source value"); throw new IllegalStateException("No invalid submitted value applied to this field");
} }
return sourceValue; return submittedValue;
} }
public BindingStatus getBindingStatus() { public BindingStatus getBindingStatus() {
@ -186,32 +186,33 @@ public class GenericBinding implements Binding {
} }
public ValidationStatus getValidationStatus() { public ValidationStatus getValidationStatus() {
// TODO implementation
return ValidationStatus.NOT_VALIDATED; return ValidationStatus.NOT_VALIDATED;
} }
public Alert getStatusAlert() { public Alert getStatusAlert() {
if (bindingStatus == BindingStatus.INVALID_SOURCE_VALUE) { if (bindingStatus == BindingStatus.INVALID_SUBMITTED_VALUE) {
return new AbstractAlert() { return new AbstractAlert() {
public String getCode() { public String getCode() {
return "typeMismatch"; return "typeMismatch";
} }
public String getMessage() { public String getMessage() {
MessageBuilder builder = new MessageBuilder(bindingContext.getMessageSource()); MessageBuilder builder = new MessageBuilder(context.getMessageSource());
builder.code(getCode()); builder.code(getCode());
if (invalidSourceValueCause instanceof ParseException) { if (invalidSubmittedValueCause instanceof ParseException) {
ParseException e = (ParseException) invalidSourceValueCause; ParseException e = (ParseException) invalidSubmittedValueCause;
builder.arg("label", bindingContext.getLabel()); builder.arg("label", context.getLabel());
builder.arg("value", sourceValue); builder.arg("value", submittedValue);
builder.arg("errorOffset", e.getErrorOffset()); builder.arg("errorOffset", e.getErrorOffset());
builder.defaultMessage("Failed to bind '" + bindingContext.getLabel() + "'; the source value " builder.defaultMessage("Failed to bind '" + context.getLabel() + "'; the submitted value "
+ StylerUtils.style(sourceValue) + " has an invalid format and could no be parsed"); + StylerUtils.style(submittedValue) + " has an invalid format and could no be parsed");
} else { } else {
ConversionFailedException e = (ConversionFailedException) invalidSourceValueCause; ConversionFailedException e = (ConversionFailedException) invalidSubmittedValueCause;
builder.arg("label", new ResolvableArgument(bindingContext.getLabel())); builder.arg("label", new ResolvableArgument(context.getLabel()));
builder.arg("value", sourceValue); builder.arg("value", submittedValue);
builder.defaultMessage("Failed to bind '" + bindingContext.getLabel() + "'; the source value " builder.defaultMessage("Failed to bind '" + context.getLabel() + "'; the submitted value "
+ StylerUtils.style(sourceValue) + " has could not be converted to " + StylerUtils.style(submittedValue) + " has could not be converted to "
+ e.getTargetType().getName()); + e.getTargetType().getName());
} }
@ -257,6 +258,7 @@ public class GenericBinding implements Binding {
} }
public void validate() { public void validate() {
// TODO implementation
} }
public void commit() { public void commit() {
@ -270,57 +272,57 @@ public class GenericBinding implements Binding {
bindingStatus = BindingStatus.COMMITTED; bindingStatus = BindingStatus.COMMITTED;
} }
} else { } else {
throw new IllegalStateException("Binding is not dirty; nothing to commit"); throw new IllegalStateException("Field is not dirty; nothing to commit");
} }
} }
public void revert() { public void revert() {
if (bindingStatus == BindingStatus.INVALID_SOURCE_VALUE) { if (bindingStatus == BindingStatus.INVALID_SUBMITTED_VALUE) {
sourceValue = null; submittedValue = null;
invalidSourceValueCause = null; invalidSubmittedValueCause = null;
bindingStatus = BindingStatus.CLEAN; bindingStatus = BindingStatus.CLEAN;
} else if (bindingStatus == BindingStatus.DIRTY || bindingStatus == BindingStatus.COMMIT_FAILURE) { } else if (bindingStatus == BindingStatus.DIRTY || bindingStatus == BindingStatus.COMMIT_FAILURE) {
buffer.clear(); buffer.clear();
bindingStatus = BindingStatus.CLEAN; bindingStatus = BindingStatus.CLEAN;
} else { } else {
throw new IllegalStateException("Nothing to revert"); throw new IllegalStateException("Field is clean or committed; nothing to revert");
} }
} }
public Binding getNestedBinding(String property) { public FieldModel getNested(String fieldName) {
return bindingContext.getNestedBinding(property); return context.getNested(fieldName);
} }
public boolean isList() { public boolean isList() {
return List.class.isAssignableFrom(getValueType()); return getValueType().isArray() || List.class.isAssignableFrom(getValueType());
} }
public Binding getListElementBinding(int index) { public FieldModel getListElement(int index) {
return bindingContext.getListElementBinding(index); return context.getListElement(index);
} }
public boolean isMap() { public boolean isMap() {
return Map.class.isAssignableFrom(getValueType()); return Map.class.isAssignableFrom(getValueType());
} }
public Binding getMapValueBinding(Object key) { public FieldModel getMapValue(Object key) {
if (key instanceof String) { if (key instanceof String) {
try { try {
key = bindingContext.getKeyFormatter().parse((String) key, getLocale()); key = context.getKeyFormatter().parse((String) key, getLocale());
} catch (ParseException e) { } catch (ParseException e) {
throw new IllegalArgumentException("Unable to parse map key '" + key + "'", e); throw new IllegalArgumentException("Unable to parse map key '" + key + "'", e);
} }
} }
return bindingContext.getMapValueBinding(key); return context.getMapValue(key);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public String formatValue(Object value) { public String formatValue(Object value) {
Formatter formatter; Formatter formatter;
if (Collection.class.isAssignableFrom(getValueType()) || getValueType().isArray() || isMap()) { if (Collection.class.isAssignableFrom(getValueType()) || getValueType().isArray() || isMap()) {
formatter = bindingContext.getElementFormatter(); formatter = context.getElementFormatter();
} else { } else {
formatter = bindingContext.getFormatter(); formatter = context.getFormatter();
} }
return format(value, formatter); return format(value, formatter);
} }
@ -330,7 +332,7 @@ public class GenericBinding implements Binding {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private String format(Object value, Formatter formatter) { private String format(Object value, Formatter formatter) {
Class<?> formattedType = getFormattedObjectType(formatter.getClass()); Class<?> formattedType = getFormattedObjectType(formatter.getClass());
value = bindingContext.getTypeConverter().convert(value, formattedType); value = context.getTypeConverter().convert(value, formattedType);
return formatter.format(value, getLocale()); return formatter.format(value, getLocale());
} }
@ -370,7 +372,7 @@ public class GenericBinding implements Binding {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Object coerseToValueType(Object parsed) { private Object coerseToValueType(Object parsed) {
TypeDescriptor targetType = valueModel.getValueTypeDescriptor(); TypeDescriptor targetType = valueModel.getValueTypeDescriptor();
TypeConverter converter = bindingContext.getTypeConverter(); TypeConverter converter = context.getTypeConverter();
if (parsed != null && converter.canConvert(parsed.getClass(), targetType)) { if (parsed != null && converter.canConvert(parsed.getClass(), targetType)) {
return converter.convert(parsed, targetType); return converter.convert(parsed, targetType);
} else { } else {
@ -380,13 +382,13 @@ public class GenericBinding implements Binding {
private void assertEditable() { private void assertEditable() {
if (!isEditable()) { if (!isEditable()) {
throw new IllegalStateException("Binding is not editable"); throw new IllegalStateException("Field is not editable");
} }
} }
private void assertEnabled() { private void assertEnabled() {
if (!isEditable()) { if (!isEditable()) {
throw new IllegalStateException("Binding is not enabled"); throw new IllegalStateException("Field is not enabled");
} }
} }

View File

@ -29,11 +29,11 @@ import org.springframework.context.MessageSource;
import org.springframework.core.GenericCollectionTypeResolver; import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.convert.TypeConverter; import org.springframework.core.convert.TypeConverter;
import org.springframework.core.convert.support.DefaultTypeConverter; import org.springframework.core.convert.support.DefaultTypeConverter;
import org.springframework.ui.binding.Binding; import org.springframework.ui.binding.FieldModel;
import org.springframework.ui.binding.BindingFactory; import org.springframework.ui.binding.FieldNotFoundException;
import org.springframework.ui.binding.PresentationModel;
import org.springframework.ui.binding.binder.Binder; import org.springframework.ui.binding.binder.Binder;
import org.springframework.ui.binding.binder.BindingResult; import org.springframework.ui.binding.config.FieldModelConfiguration;
import org.springframework.ui.binding.config.BindingRuleConfiguration;
import org.springframework.ui.binding.config.Condition; import org.springframework.ui.binding.config.Condition;
import org.springframework.ui.format.Formatter; import org.springframework.ui.format.Formatter;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -42,15 +42,16 @@ import org.springframework.util.Assert;
* A generic {@link Binder binder} suitable for use in most environments. * A generic {@link Binder binder} suitable for use in most environments.
* @author Keith Donald * @author Keith Donald
* @since 3.0 * @since 3.0
* @see #setFormatterRegistry(FormatterRegistry)
* @see #setMessageSource(MessageSource) * @see #setMessageSource(MessageSource)
* @see #setTypeConverter(TypeConverter) * @see #setTypeConverter(TypeConverter)
* @see #bind(Map) * @see #bind(Map)
*/ */
public class GenericBindingFactory implements BindingFactory { public class DefaultPresentationModel implements PresentationModel {
private Object model; private Object domainModel;
private Map<String, GenericBindingRule> bindingRules; private Map<String, PropertyFieldModelRule> fieldModelRules;
private FormatterRegistry formatterRegistry; private FormatterRegistry formatterRegistry;
@ -59,19 +60,19 @@ public class GenericBindingFactory implements BindingFactory {
private MessageSource messageSource; private MessageSource messageSource;
/** /**
* Creates a new binder for the model object. * Creates a new presentation model for the domain model.
* @param model the model object containing properties this binder will bind to * @param domainModel the domain model object
*/ */
public GenericBindingFactory(Object model) { public DefaultPresentationModel(Object domainModel) {
Assert.notNull(model, "The model to bind to is required"); Assert.notNull(domainModel, "The domain model to bind to is required");
this.model = model; this.domainModel = domainModel;
bindingRules = new HashMap<String, GenericBindingRule>(); fieldModelRules = new HashMap<String, PropertyFieldModelRule>();
formatterRegistry = new GenericFormatterRegistry(); formatterRegistry = new GenericFormatterRegistry();
typeConverter = new DefaultTypeConverter(); typeConverter = new DefaultTypeConverter();
} }
/** /**
* Configures the registry of Formatters to query when no explicit Formatter has been registered for a Binding. * Configures the registry of Formatters to query when no explicit Formatter has been registered for a field.
* Allows Formatters to be applied by property type and by property annotation. * Allows Formatters to be applied by property type and by property annotation.
* @param registry the formatter registry * @param registry the formatter registry
*/ */
@ -81,7 +82,7 @@ public class GenericBindingFactory implements BindingFactory {
} }
/** /**
* Configure the MessageSource that resolves localized {@link BindingResult} alert messages. * Configure the MessageSource that resolves localized UI alert messages.
* @param messageSource the message source * @param messageSource the message source
*/ */
public void setMessageSource(MessageSource messageSource) { public void setMessageSource(MessageSource messageSource) {
@ -90,10 +91,11 @@ public class GenericBindingFactory implements BindingFactory {
} }
/** /**
* Configure the TypeConverter that converts values as required by Binding setValue and getValue attempts. * Configure the TypeConverter that converts values as required by the binding system.
* For a setValue attempt, the TypeConverter will be asked to perform a conversion if the value parsed by the Binding's Formatter is not assignable to the target property type. * For a {@link FieldModel#applySubmittedValue(Object) applySubmittedValue call}, this TypeConverter will be asked to perform a conversion if the value parsed by the field's Formatter is not assignable to the target value type.
* For a getValue attempt, the TypeConverter will be asked to perform a conversion if the property type does not match the type T required by the Binding's Formatter. * For a {@link FieldModel#getRenderValue() getRenderValue call}, this TypeConverter will be asked to perform a conversion if the value type does not match the type T required by the field's Formatter.
* @param typeConverter the type converter used by the binding system, which is based on Spring EL * For a {@link FieldModel#getMapValue(Object) getMapValue call} this TypeConverter will be asked to convert the Map key to the type required if there is no keyFormatter registered for the field.
* @param typeConverter the type converter used by the binding system
*/ */
public void setTypeConverter(TypeConverter typeConverter) { public void setTypeConverter(TypeConverter typeConverter) {
Assert.notNull(typeConverter, "The TypeConverter is required"); Assert.notNull(typeConverter, "The TypeConverter is required");
@ -101,57 +103,57 @@ public class GenericBindingFactory implements BindingFactory {
} }
/** /**
* Add a new binding rule for the property at the path specified. * Add a a new FieldModelRule for the property at the path specified.
* @param propertyPath binding rule property path in format prop.nestedProp * @param propertyPath binding rule property path in format prop.nestedProp
* @return a builder for the binding rule * @return a builder for the binding rule
*/ */
public BindingRuleConfiguration bindingRule(String propertyPath) { public FieldModelConfiguration field(String propertyPath) {
PropertyPath path = new PropertyPath(propertyPath); FieldPath path = new FieldPath(propertyPath);
GenericBindingRule rule = getBindingRule(path.getFirstElement().getValue()); PropertyFieldModelRule rule = getRule(path.getFirstElement().getValue());
for (PropertyPathElement element : path.getNestedElements()) { for (FieldPathElement element : path.getNestedElements()) {
rule = rule.getBindingRule(element.getValue()); rule = rule.getNestedRule(element.getValue());
} }
return rule; return rule;
} }
// implementing Binder public Object getDomainModel() {
return domainModel;
public Object getModel() {
return model;
} }
public Binding getBinding(String property) { // implementing PresentationModel
PropertyPath path = new PropertyPath(property);
Binding binding = getBindingRule(path.getFirstElement().getValue()).getBinding(model); public FieldModel getFieldModel(String fieldName) {
for (PropertyPathElement element : path.getNestedElements()) { FieldPath path = new FieldPath(fieldName);
FieldModel field = getRule(path.getFirstElement().getValue()).getFieldModel(domainModel);
for (FieldPathElement element : path.getNestedElements()) {
if (element.isIndex()) { if (element.isIndex()) {
if (binding.isMap()) { if (field.isMap()) {
binding = binding.getMapValueBinding(element.getValue()); field = field.getMapValue(element.getValue());
} else if (binding.isList()) { } else if (field.isList()) {
binding = binding.getListElementBinding(element.getIntValue()); field = field.getListElement(element.getIntValue());
} else { } else {
throw new IllegalArgumentException("Attempted to index a property that is not a List or Map"); throw new IllegalArgumentException("Attempted to index a field that is not a List, Array, or a Map");
} }
} else { } else {
binding = binding.getNestedBinding(element.getValue()); field = field.getNested(element.getValue());
} }
} }
return binding; return field;
} }
private GenericBindingRule getBindingRule(String property) { private PropertyFieldModelRule getRule(String fieldName) {
GenericBindingRule rule = bindingRules.get(property); PropertyFieldModelRule rule = fieldModelRules.get(fieldName);
if (rule == null) { if (rule == null) {
rule = new GenericBindingRule(property, model.getClass()); rule = new PropertyFieldModelRule(fieldName, domainModel.getClass());
bindingRules.put(property, rule); fieldModelRules.put(fieldName, rule);
} }
return rule; return rule;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
class GenericBindingRule implements BindingRuleConfiguration, BindingContext { class PropertyFieldModelRule implements FieldModelConfiguration, FieldModelContext {
private Class<?> modelClass; private Class<?> domainModelClass;
private PropertyDescriptor property; private PropertyDescriptor property;
@ -167,20 +169,20 @@ public class GenericBindingFactory implements BindingFactory {
private Condition visibleCondition = Condition.ALWAYS_TRUE; private Condition visibleCondition = Condition.ALWAYS_TRUE;
private Map<String, GenericBindingRule> nestedBindingRules; private Map<String, PropertyFieldModelRule> nestedFieldModelRules;
private Binding binding; private FieldModel fieldModel;
private Map<Integer, Binding> listElementBindings; private Map<Integer, FieldModel> listElements;
private Map<Object, Binding> mapValueBindings; private Map<Object, FieldModel> mapValues;
public GenericBindingRule(String property, Class modelClass) { public PropertyFieldModelRule(String property, Class domainModelClass) {
this.modelClass = modelClass; this.domainModelClass = domainModelClass;
this.property = findPropertyDescriptor(property); this.property = findPropertyDescriptor(property);
} }
// implementing BindingContext // implementing FieldModelContext
public MessageSource getMessageSource() { public MessageSource getMessageSource() {
return messageSource; return messageSource;
@ -226,84 +228,103 @@ public class GenericBindingFactory implements BindingFactory {
return visibleCondition; return visibleCondition;
} }
public Binding getNestedBinding(String property) {
createValueIfNecessary();
return getBindingRule(property, binding.getValueType()).getBinding(binding.getValue());
}
public Binding getListElementBinding(int index) {
if (listElementBindings == null) {
listElementBindings = new HashMap<Integer, Binding>();
}
growListIfNecessary(index);
Binding binding = listElementBindings.get(index);
if (binding == null) {
BindingContext bindingContext = new ListElementBindingContext(index, this);
ValueModel valueModel = new ListElementValueModel(index, getElementType(), (List) this.binding.getValue());
binding = new GenericBinding(valueModel, bindingContext);
listElementBindings.put(index, binding);
}
return binding;
}
public Binding getMapValueBinding(Object key) {
if (mapValueBindings == null) {
mapValueBindings = new HashMap<Object, Binding>();
}
createMapValueIfNecessary();
Binding binding = mapValueBindings.get(key);
if (binding == null) {
BindingContext bindingContext = new MapValueBindingContext(key, this);
ValueModel valueModel = new MapValueValueModel(key, getElementType(), (Map) this.binding.getValue(), bindingContext);
binding = new GenericBinding(valueModel, bindingContext);
mapValueBindings.put(key, binding);
}
return binding;
}
public String getLabel() { public String getLabel() {
return property.getName(); return property.getName();
} }
// implementing BindingRuleConfiguration public FieldModel getNested(String fieldName) {
createValueIfNecessary();
return getNestedRule(fieldName, fieldModel.getValueType()).getFieldModel(fieldModel.getValue());
}
public BindingRuleConfiguration formatWith(Formatter<?> formatter) { public FieldModel getListElement(int index) {
// TODO array support
if (listElements == null) {
listElements = new HashMap<Integer, FieldModel>();
}
growListIfNecessary(index);
FieldModel field = listElements.get(index);
if (field == null) {
FieldModelContext context = new ListElementContext(index, this);
ValueModel valueModel = new ListElementValueModel(index, getElementType(), (List) fieldModel.getValue());
field = new DefaultFieldModel(valueModel, context);
listElements.put(index, field);
}
return field;
}
public FieldModel getMapValue(Object key) {
if (mapValues == null) {
mapValues = new HashMap<Object, FieldModel>();
}
createMapValueIfNecessary();
FieldModel field = mapValues.get(key);
if (field == null) {
FieldModelContext context = new MapValueContext(key, this);
ValueModel valueModel = new MapValueValueModel(key, getElementType(), (Map) fieldModel.getValue(), context);
field = new DefaultFieldModel(valueModel, context);
mapValues.put(key, field);
}
return field;
}
// implementing FieldModelConfiguration
public FieldModelConfiguration formatWith(Formatter<?> formatter) {
this.formatter = formatter; this.formatter = formatter;
return this; return this;
} }
public BindingRuleConfiguration formatElementsWith(Formatter<?> formatter) { public FieldModelConfiguration formatElementsWith(Formatter<?> formatter) {
if (!List.class.isAssignableFrom(modelClass) || modelClass.isArray()) { if (!List.class.isAssignableFrom(domainModelClass) || domainModelClass.isArray()) {
throw new IllegalStateException( throw new IllegalStateException(
"Bound property is not a List or an array; cannot set a element formatter"); "Field is not a List or an Array; cannot set a element formatter");
} }
elementFormatter = formatter; elementFormatter = formatter;
return this; return this;
} }
public BindingRuleConfiguration formatKeysWith(Formatter<?> formatter) { public FieldModelConfiguration formatKeysWith(Formatter<?> formatter) {
if (!Map.class.isAssignableFrom(modelClass)) { if (!Map.class.isAssignableFrom(domainModelClass)) {
throw new IllegalStateException("Bound property is not a Map; cannot set a key formatter"); throw new IllegalStateException("Field is not a Map; cannot set a key formatter");
} }
keyFormatter = formatter; keyFormatter = formatter;
return this; return this;
} }
public BindingRuleConfiguration editableWhen(Condition condition) { public FieldModelConfiguration editableWhen(Condition condition) {
editableCondition = condition; editableCondition = condition;
return this; return this;
} }
public BindingRuleConfiguration enabledWhen(Condition condition) { public FieldModelConfiguration enabledWhen(Condition condition) {
enabledCondition = condition; enabledCondition = condition;
return this; return this;
} }
public BindingRuleConfiguration visibleWhen(Condition condition) { public FieldModelConfiguration visibleWhen(Condition condition) {
visibleCondition = condition; visibleCondition = condition;
return this; return this;
} }
// package private helpers
PropertyFieldModelRule getNestedRule(String propertyName) {
return getNestedRule(propertyName, this.property.getPropertyType());
}
PropertyFieldModelRule getNestedRule(String propertyName, Class<?> domainModelClass) {
if (nestedFieldModelRules == null) {
nestedFieldModelRules = new HashMap<String, PropertyFieldModelRule>();
}
PropertyFieldModelRule rule = nestedFieldModelRules.get(propertyName);
if (rule == null) {
rule = new PropertyFieldModelRule(propertyName, domainModelClass);
nestedFieldModelRules.put(propertyName, rule);
}
return rule;
}
// internal helpers // internal helpers
private Class<?> getElementType() { private Class<?> getElementType() {
@ -318,40 +339,22 @@ public class GenericBindingFactory implements BindingFactory {
return GenericCollectionTypeResolver.getMapKeyReturnType(property.getReadMethod()); return GenericCollectionTypeResolver.getMapKeyReturnType(property.getReadMethod());
} }
GenericBindingRule getBindingRule(String property) { FieldModel getFieldModel(Object domainObject) {
return getBindingRule(property, this.property.getPropertyType()); if (fieldModel == null) {
} PropertyValueModel valueModel = new PropertyValueModel(property, domainObject);
fieldModel = new DefaultFieldModel(valueModel, this);
GenericBindingRule getBindingRule(String property, Class<?> modelClass) {
if (nestedBindingRules == null) {
nestedBindingRules = new HashMap<String, GenericBindingRule>();
} }
GenericBindingRule rule = nestedBindingRules.get(property); return fieldModel;
if (rule == null) {
rule = new GenericBindingRule(property, modelClass);
nestedBindingRules.put(property, rule);
}
return rule;
}
// internal helpers
Binding getBinding(Object model) {
if (binding == null) {
PropertyValueModel valueModel = new PropertyValueModel(property, model);
binding = new GenericBinding(valueModel, this);
}
return binding;
} }
private PropertyDescriptor findPropertyDescriptor(String property) { private PropertyDescriptor findPropertyDescriptor(String property) {
PropertyDescriptor[] propDescs = getBeanInfo(modelClass).getPropertyDescriptors(); PropertyDescriptor[] propDescs = getBeanInfo(domainModelClass).getPropertyDescriptors();
for (PropertyDescriptor propDesc : propDescs) { for (PropertyDescriptor propDesc : propDescs) {
if (propDesc.getName().equals(property)) { if (propDesc.getName().equals(property)) {
return propDesc; return propDesc;
} }
} }
throw new PropertyNotFoundException(property, modelClass); throw new FieldNotFoundException(property);
} }
private BeanInfo getBeanInfo(Class<?> clazz) { private BeanInfo getBeanInfo(Class<?> clazz) {
@ -363,30 +366,30 @@ public class GenericBindingFactory implements BindingFactory {
} }
private void createValueIfNecessary() { private void createValueIfNecessary() {
Object value = binding.getValue(); Object value = fieldModel.getValue();
if (value == null) { if (value == null) {
value = newValue(binding.getValueType()); value = newValue(fieldModel.getValueType());
binding.applySourceValue(value); fieldModel.applySubmittedValue(value);
binding.commit(); fieldModel.commit();
} }
} }
private void createMapValueIfNecessary() { private void createMapValueIfNecessary() {
Object value = binding.getValue(); Object value = fieldModel.getValue();
if (value == null) { if (value == null) {
value = newMapValue(binding.getValueType()); value = newMapValue(fieldModel.getValueType());
binding.applySourceValue(value); fieldModel.applySubmittedValue(value);
binding.commit(); fieldModel.commit();
} }
} }
private void growListIfNecessary(int index) { private void growListIfNecessary(int index) {
List list = (List) binding.getValue(); List list = (List) fieldModel.getValue();
if (list == null) { if (list == null) {
list = newListValue(binding.getValueType()); list = newListValue(fieldModel.getValueType());
binding.applySourceValue(list); fieldModel.applySubmittedValue(list);
binding.commit(); fieldModel.commit();
list = (List) binding.getValue(); list = (List) fieldModel.getValue();
} }
if (index >= list.size()) { if (index >= list.size()) {
for (int i = list.size(); i <= index; i++) { for (int i = list.size(); i <= index; i++) {
@ -423,15 +426,15 @@ public class GenericBindingFactory implements BindingFactory {
} }
private static class ListElementBindingContext implements BindingContext { private static class ListElementContext implements FieldModelContext {
private int index; private int index;
private GenericBindingRule listBindingContext; private PropertyFieldModelRule listBindingContext;
final Map<String, Binding> nestedBindings = new HashMap<String, Binding>(); final Map<String, FieldModel> nestedBindings = new HashMap<String, FieldModel>();
public ListElementBindingContext(int index, GenericBindingRule listBindingContext) { public ListElementContext(int index, PropertyFieldModelRule listBindingContext) {
this.index = index; this.index = index;
this.listBindingContext = listBindingContext; this.listBindingContext = listBindingContext;
} }
@ -444,22 +447,6 @@ public class GenericBindingFactory implements BindingFactory {
return listBindingContext.getTypeConverter(); return listBindingContext.getTypeConverter();
} }
public Binding getNestedBinding(String property) {
Object model = ((List<?>) listBindingContext.binding.getValue()).get(index);
Class<?> elementType = listBindingContext.getElementType();
if (elementType == null) {
elementType = model.getClass();
}
GenericBindingRule rule = listBindingContext.getBindingRule(property, elementType);
Binding binding = nestedBindings.get(property);
if (binding == null) {
PropertyValueModel valueModel = new PropertyValueModel(rule.property, model);
binding = new GenericBinding(valueModel, rule);
nestedBindings.put(property, binding);
}
return binding;
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Formatter getFormatter() { public Formatter getFormatter() {
return listBindingContext.getElementFormatter(); return listBindingContext.getElementFormatter();
@ -467,11 +454,13 @@ public class GenericBindingFactory implements BindingFactory {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Formatter getElementFormatter() { public Formatter getElementFormatter() {
// TODO multi-dimensional support
return null; return null;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Formatter getKeyFormatter() { public Formatter getKeyFormatter() {
// TODO multi-dimensional support
return null; return null;
} }
@ -487,95 +476,116 @@ public class GenericBindingFactory implements BindingFactory {
return listBindingContext.getVisibleCondition(); return listBindingContext.getVisibleCondition();
} }
public Binding getListElementBinding(int index) {
throw new IllegalArgumentException("Not yet supported");
}
public Binding getMapValueBinding(Object key) {
throw new IllegalArgumentException("Not yet supported");
}
public String getLabel() { public String getLabel() {
return listBindingContext.getLabel() + "[" + index + "]"; return listBindingContext.getLabel() + "[" + index + "]";
} }
}; public FieldModel getNested(String property) {
Object model = ((List<?>) listBindingContext.fieldModel.getValue()).get(index);
private static class MapValueBindingContext implements BindingContext { Class<?> elementType = listBindingContext.getElementType();
private Object key;
private GenericBindingRule mapBindingContext;
final Map<String, Binding> nestedBindings = new HashMap<String, Binding>();
public MapValueBindingContext(Object key, GenericBindingRule mapBindingContext) {
this.key = key;
this.mapBindingContext = mapBindingContext;
}
public MessageSource getMessageSource() {
return mapBindingContext.getMessageSource();
}
public TypeConverter getTypeConverter() {
return mapBindingContext.getTypeConverter();
}
@SuppressWarnings("unchecked")
public Binding getNestedBinding(String property) {
Object model = ((Map) mapBindingContext.binding.getValue()).get(key);
Class<?> elementType = mapBindingContext.getElementType();
if (elementType == null) { if (elementType == null) {
elementType = model.getClass(); elementType = model.getClass();
} }
GenericBindingRule rule = mapBindingContext.getBindingRule(property, elementType); PropertyFieldModelRule rule = listBindingContext.getNestedRule(property, elementType);
Binding binding = nestedBindings.get(property); FieldModel binding = nestedBindings.get(property);
if (binding == null) { if (binding == null) {
PropertyValueModel valueModel = new PropertyValueModel(rule.property, model); PropertyValueModel valueModel = new PropertyValueModel(rule.property, model);
binding = new GenericBinding(valueModel, rule); binding = new DefaultFieldModel(valueModel, rule);
nestedBindings.put(property, binding); nestedBindings.put(property, binding);
} }
return binding; return binding;
} }
public FieldModel getListElement(int index) {
// TODO multi-dimensional support
throw new IllegalArgumentException("Not yet supported");
}
public FieldModel getMapValue(Object key) {
// TODO multi-dimensional support
throw new IllegalArgumentException("Not yet supported");
}
};
private static class MapValueContext implements FieldModelContext {
private Object key;
private PropertyFieldModelRule mapContext;
final Map<String, FieldModel> nestedBindings = new HashMap<String, FieldModel>();
public MapValueContext(Object key, PropertyFieldModelRule mapContext) {
this.key = key;
this.mapContext = mapContext;
}
public MessageSource getMessageSource() {
return mapContext.getMessageSource();
}
public TypeConverter getTypeConverter() {
return mapContext.getTypeConverter();
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Formatter getFormatter() { public Formatter getFormatter() {
return mapBindingContext.getElementFormatter(); return mapContext.getElementFormatter();
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Formatter getElementFormatter() { public Formatter getElementFormatter() {
// TODO multi-dimensional support
return null; return null;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Formatter getKeyFormatter() { public Formatter getKeyFormatter() {
// TODO multi-dimensional support
return null; return null;
} }
public Condition getEditableCondition() { public Condition getEditableCondition() {
return mapBindingContext.getEditableCondition(); return mapContext.getEditableCondition();
} }
public Condition getEnabledCondition() { public Condition getEnabledCondition() {
return mapBindingContext.getEnabledCondition(); return mapContext.getEnabledCondition();
} }
public Condition getVisibleCondition() { public Condition getVisibleCondition() {
return mapBindingContext.getVisibleCondition(); return mapContext.getVisibleCondition();
} }
public Binding getListElementBinding(int index) { @SuppressWarnings("unchecked")
public FieldModel getNested(String property) {
Object model = ((Map) mapContext.fieldModel.getValue()).get(key);
Class<?> elementType = mapContext.getElementType();
if (elementType == null) {
elementType = model.getClass();
}
PropertyFieldModelRule rule = mapContext.getNestedRule(property, elementType);
FieldModel binding = nestedBindings.get(property);
if (binding == null) {
PropertyValueModel valueModel = new PropertyValueModel(rule.property, model);
binding = new DefaultFieldModel(valueModel, rule);
nestedBindings.put(property, binding);
}
return binding;
}
public FieldModel getListElement(int index) {
// TODO multi-dimensional support
throw new IllegalArgumentException("Not yet supported"); throw new IllegalArgumentException("Not yet supported");
} }
public Binding getMapValueBinding(Object key) { public FieldModel getMapValue(Object key) {
// TODO multi-dimensional support
throw new IllegalArgumentException("Not yet supported"); throw new IllegalArgumentException("Not yet supported");
} }
public String getLabel() { public String getLabel() {
return mapBindingContext.getLabel() + "[" + key + "]"; return mapContext.getLabel() + "[" + key + "]";
} }
}; };

View File

@ -18,26 +18,26 @@ package org.springframework.ui.binding.support;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
import java.util.Map; import java.util.Map;
import org.springframework.ui.binding.BindingFactory; import org.springframework.ui.binding.PresentationModel;
import org.springframework.ui.binding.BindingFactoryLocator; import org.springframework.ui.binding.PresentationModelFactory;
/** /**
* BindingFactoryLocator implementation that uses a {@link IdentityHashMap} to map models to BindingFactories. * BindingFactoryLocator implementation that uses a {@link IdentityHashMap} to map models to BindingFactories.
* @author Keith Donald * @author Keith Donald
*/ */
public class GenericBindingFactoryLocator implements BindingFactoryLocator { public class DefaultPresentationModelFactory implements PresentationModelFactory {
private Map<Object, BindingFactory> bindingFactories = new IdentityHashMap<Object, BindingFactory>(); private Map<Object, PresentationModel> presentationModels = new IdentityHashMap<Object, PresentationModel>();
public void put(BindingFactory bindingFactory) { public void put(Object domainObject, PresentationModel presentationModel) {
bindingFactories.put(bindingFactory.getModel(), bindingFactory); presentationModels.put(domainObject, presentationModel);
} }
public BindingFactory getBindingFactory(Object model) { public PresentationModel getPresentationModel(Object domainObject) {
BindingFactory factory = bindingFactories.get(model); PresentationModel factory = presentationModels.get(domainObject);
if (factory == null) { if (factory == null) {
factory = new GenericBindingFactory(model); factory = new DefaultPresentationModel(domainObject);
bindingFactories.put(model, factory); presentationModels.put(domainObject, factory);
} }
return factory; return factory;
} }

View File

@ -17,16 +17,16 @@ package org.springframework.ui.binding.support;
import org.springframework.context.MessageSource; import org.springframework.context.MessageSource;
import org.springframework.core.convert.TypeConverter; import org.springframework.core.convert.TypeConverter;
import org.springframework.ui.binding.Binding; import org.springframework.ui.binding.FieldModel;
import org.springframework.ui.binding.config.Condition; import org.springframework.ui.binding.config.Condition;
import org.springframework.ui.format.Formatter; import org.springframework.ui.format.Formatter;
/** /**
* A context that allows a Binding to query its BindingRule. * A context that allows a FieldModel to access its external configuration.
* @author Keith Donald * @author Keith Donald
* @since 3.0 * @since 3.0
*/ */
public interface BindingContext { public interface FieldModelContext {
MessageSource getMessageSource(); MessageSource getMessageSource();
@ -38,8 +38,6 @@ public interface BindingContext {
Condition getVisibleCondition(); Condition getVisibleCondition();
Binding getNestedBinding(String property);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Formatter getFormatter(); Formatter getFormatter();
@ -49,10 +47,12 @@ public interface BindingContext {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Formatter getKeyFormatter(); Formatter getKeyFormatter();
Binding getListElementBinding(int index);
Binding getMapValueBinding(Object key);
String getLabel(); String getLabel();
FieldModel getNested(String fieldName);
FieldModel getListElement(int index);
FieldModel getMapValue(Object key);
} }

View File

@ -20,11 +20,11 @@ import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
public class PropertyPath implements Iterable<PropertyPathElement> { class FieldPath implements Iterable<FieldPathElement> {
private List<PropertyPathElement> elements = new ArrayList<PropertyPathElement>(); private List<FieldPathElement> elements = new ArrayList<FieldPathElement>();
public PropertyPath(String propertyPath) { public FieldPath(String propertyPath) {
// a.b.c[i].d[key].e // a.b.c[i].d[key].e
String[] props = propertyPath.split("\\."); String[] props = propertyPath.split("\\.");
if (props.length == 0) { if (props.length == 0) {
@ -35,19 +35,19 @@ public class PropertyPath implements Iterable<PropertyPathElement> {
int start = prop.indexOf('['); int start = prop.indexOf('[');
int end = prop.indexOf(']', start); int end = prop.indexOf(']', start);
String index = prop.substring(start + 1, end); String index = prop.substring(start + 1, end);
elements.add(new PropertyPathElement(prop.substring(0, start), false)); elements.add(new FieldPathElement(prop.substring(0, start), false));
elements.add(new PropertyPathElement(index, true)); elements.add(new FieldPathElement(index, true));
} else { } else {
elements.add(new PropertyPathElement(prop, false)); elements.add(new FieldPathElement(prop, false));
} }
} }
} }
public PropertyPathElement getFirstElement() { public FieldPathElement getFirstElement() {
return elements.get(0); return elements.get(0);
} }
public List<PropertyPathElement> getNestedElements() { public List<FieldPathElement> getNestedElements() {
if (elements.size() > 1) { if (elements.size() > 1) {
return elements.subList(1, elements.size()); return elements.subList(1, elements.size());
} else { } else {
@ -55,12 +55,11 @@ public class PropertyPath implements Iterable<PropertyPathElement> {
} }
} }
public Iterator<PropertyPathElement> iterator() { public Iterator<FieldPathElement> iterator() {
return elements.iterator(); return elements.iterator();
} }
public String toString() { public String toString() {
return elements.toString(); return elements.toString();
} }
} }

View File

@ -15,13 +15,13 @@
*/ */
package org.springframework.ui.binding.support; package org.springframework.ui.binding.support;
public class PropertyPathElement { class FieldPathElement {
private String value; private String value;
private boolean index; private boolean index;
public PropertyPathElement(String value, boolean index) { public FieldPathElement(String value, boolean index) {
this.value = value; this.value = value;
this.index = index; this.index = index;
} }

View File

@ -28,6 +28,10 @@ import org.springframework.ui.format.Formatter;
*/ */
public interface FormatterRegistry { public interface FormatterRegistry {
/**
* Get the Formatter for the property.
* @return the Formatter, or <code>null</code> if none is registered
*/
Formatter<?> getFormatter(PropertyDescriptor property); Formatter<?> getFormatter(PropertyDescriptor property);
/** /**

View File

@ -263,7 +263,7 @@ public class GenericFormatterRegistry implements FormatterRegistry {
public Object parse(String formatted, Locale locale) throws ParseException { public Object parse(String formatted, Locale locale) throws ParseException {
String[] fields = StringUtils.commaDelimitedListToStringArray(formatted); String[] fields = StringUtils.commaDelimitedListToStringArray(formatted);
if (collectionType.getCollectionType().isArray()) { if (collectionType.getType().isArray()) {
Object array = Array.newInstance(getElementType(), fields.length); Object array = Array.newInstance(getElementType(), fields.length);
for (int i = 0; i < fields.length; i++) { for (int i = 0; i < fields.length; i++) {
Array.set(array, i, elementFormatter.parse(fields[i], locale)); Array.set(array, i, elementFormatter.parse(fields[i], locale));
@ -289,7 +289,7 @@ public class GenericFormatterRegistry implements FormatterRegistry {
private Collection newCollection() { private Collection newCollection() {
try { try {
Class<? extends Collection> implType = ConversionUtils Class<? extends Collection> implType = ConversionUtils
.getCollectionImpl((Class<? extends Collection>) collectionType.getCollectionType()); .getCollectionImpl((Class<? extends Collection>) collectionType.getType());
return (Collection) implType.newInstance(); return (Collection) implType.newInstance();
} catch (InstantiationException e) { } catch (InstantiationException e) {
throw new IllegalStateException("Should not happen", e); throw new IllegalStateException("Should not happen", e);

View File

@ -19,7 +19,12 @@ import java.util.List;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
class ListElementValueModel implements ValueModel { /**
* A ValueModel for a element in a List.
* @author Keith Donald
* @since 3.0
*/
public class ListElementValueModel implements ValueModel {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private List list; private List list;

View File

@ -19,6 +19,11 @@ import java.util.Map;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
/**
* A ValueModel for a element in a Map.
* @author Keith Donald
* @since 3.0
*/
public class MapValueValueModel implements ValueModel { public class MapValueValueModel implements ValueModel {
private Object key; private Object key;
@ -29,7 +34,7 @@ public class MapValueValueModel implements ValueModel {
private Map map; private Map map;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public MapValueValueModel(Object key, Class<?> elementType, Map map, BindingContext bindingContext) { public MapValueValueModel(Object key, Class<?> elementType, Map map, FieldModelContext bindingContext) {
this.key = key; this.key = key;
this.elementType = elementType; this.elementType = elementType;
this.map = map; this.map = map;

View File

@ -1,39 +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.support;
@SuppressWarnings("serial")
public class PropertyNotFoundException extends RuntimeException {
private String property;
private Class<?> modelClass;
public PropertyNotFoundException(String property, Class<?> modelClass) {
super("No property '" + property + "' found on model [" + modelClass.getName() + "]");
this.property = property;
this.modelClass = modelClass;
}
public String getProperty() {
return property;
}
public Class<?> getModelClass() {
return modelClass;
}
}

View File

@ -21,6 +21,11 @@ import org.springframework.core.MethodParameter;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
/**
* A ValueModel for a bean property.
* @author Keith Donald
* @since 3.0
*/
public class PropertyValueModel implements ValueModel { public class PropertyValueModel implements ValueModel {
private PropertyDescriptor property; private PropertyDescriptor property;

View File

@ -18,8 +18,9 @@ package org.springframework.ui.binding.support;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
/** /**
* For accessing the raw bound model object. * A interface for reading and writing a value.
* @author Keith Donald * @author Keith Donald
* @since 3.0
*/ */
public interface ValueModel { public interface ValueModel {

View File

@ -1,4 +1,4 @@
package org.springframework.ui.binding.support; package org.springframework.ui.binding.binder;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
@ -22,11 +22,14 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.style.ToStringCreator; import org.springframework.core.style.ToStringCreator;
import org.springframework.ui.binding.Binding; import org.springframework.ui.binding.FieldModel;
import org.springframework.ui.binding.BindingStatus; import org.springframework.ui.binding.BindingStatus;
import org.springframework.ui.binding.binder.BindingResults; import org.springframework.ui.binding.binder.BindingResults;
import org.springframework.ui.binding.binder.GenericBinder; import org.springframework.ui.binding.binder.GenericBinder;
import org.springframework.ui.binding.binder.MissingSourceValuesException; import org.springframework.ui.binding.binder.MissingFieldException;
import org.springframework.ui.binding.support.CollectionTypeDescriptor;
import org.springframework.ui.binding.support.DefaultPresentationModel;
import org.springframework.ui.binding.support.GenericFormatterRegistry;
import org.springframework.ui.format.AnnotationFormatterFactory; import org.springframework.ui.format.AnnotationFormatterFactory;
import org.springframework.ui.format.Formatted; import org.springframework.ui.format.Formatted;
import org.springframework.ui.format.Formatter; import org.springframework.ui.format.Formatter;
@ -40,15 +43,15 @@ public class GenericBinderTests {
private GenericBinder binder; private GenericBinder binder;
private GenericBindingFactory bindingFactory; private DefaultPresentationModel presentationModel;
private TestBean bean; private TestBean bean;
@Before @Before
public void setUp() { public void setUp() {
bean = new TestBean(); bean = new TestBean();
bindingFactory = new GenericBindingFactory(bean); presentationModel = new DefaultPresentationModel(bean);
binder = new GenericBinder(bindingFactory); binder = new GenericBinder(presentationModel);
LocaleContextHolder.setLocale(Locale.US); LocaleContextHolder.setLocale(Locale.US);
} }
@ -66,17 +69,17 @@ public class GenericBinderTests {
BindingResults results = binder.bind(values); BindingResults results = binder.bind(values);
assertEquals(3, results.size()); assertEquals(3, results.size());
assertEquals("string", results.get(0).getProperty()); assertEquals("string", results.get(0).getFieldName());
assertFalse(results.get(0).isFailure()); assertFalse(results.get(0).isFailure());
assertEquals("test", results.get(0).getSourceValue()); assertEquals("test", results.get(0).getSubmittedValue());
assertEquals("integer", results.get(1).getProperty()); assertEquals("integer", results.get(1).getFieldName());
assertFalse(results.get(1).isFailure()); assertFalse(results.get(1).isFailure());
assertEquals("3", results.get(1).getSourceValue()); assertEquals("3", results.get(1).getSubmittedValue());
assertEquals("foo", results.get(2).getProperty()); assertEquals("foo", results.get(2).getFieldName());
assertFalse(results.get(2).isFailure()); assertFalse(results.get(2).isFailure());
assertEquals("BAR", results.get(2).getSourceValue()); assertEquals("BAR", results.get(2).getSubmittedValue());
assertEquals("test", bean.getString()); assertEquals("test", bean.getString());
assertEquals(3, bean.getInteger()); assertEquals(3, bean.getInteger());
@ -98,14 +101,14 @@ public class GenericBinderTests {
@Test @Test
public void bindSingleValuePropertyFormatter() throws ParseException { public void bindSingleValuePropertyFormatter() throws ParseException {
bindingFactory.bindingRule("date").formatWith(new DateFormatter()); presentationModel.field("date").formatWith(new DateFormatter());
binder.bind(Collections.singletonMap("date", "2009-06-01")); binder.bind(Collections.singletonMap("date", "2009-06-01"));
assertEquals(new DateFormatter().parse("2009-06-01", Locale.US), bean.getDate()); assertEquals(new DateFormatter().parse("2009-06-01", Locale.US), bean.getDate());
} }
@Test @Test
public void bindSingleValuePropertyFormatterParseException() { public void bindSingleValuePropertyFormatterParseException() {
bindingFactory.bindingRule("date").formatWith(new DateFormatter()); presentationModel.field("date").formatWith(new DateFormatter());
BindingResults results = binder.bind(Collections.singletonMap("date", "bogus")); BindingResults results = binder.bind(Collections.singletonMap("date", "bogus"));
assertEquals(1, results.size()); assertEquals(1, results.size());
assertTrue(results.get(0).isFailure()); assertTrue(results.get(0).isFailure());
@ -116,7 +119,7 @@ public class GenericBinderTests {
public void bindSingleValueWithFormatterRegistedByType() throws ParseException { public void bindSingleValueWithFormatterRegistedByType() throws ParseException {
GenericFormatterRegistry formatterRegistry = new GenericFormatterRegistry(); GenericFormatterRegistry formatterRegistry = new GenericFormatterRegistry();
formatterRegistry.add(Date.class, new DateFormatter()); formatterRegistry.add(Date.class, new DateFormatter());
bindingFactory.setFormatterRegistry(formatterRegistry); presentationModel.setFormatterRegistry(formatterRegistry);
binder.bind(Collections.singletonMap("date", "2009-06-01")); binder.bind(Collections.singletonMap("date", "2009-06-01"));
assertEquals(new DateFormatter().parse("2009-06-01", Locale.US), bean.getDate()); assertEquals(new DateFormatter().parse("2009-06-01", Locale.US), bean.getDate());
@ -126,7 +129,7 @@ public class GenericBinderTests {
public void bindSingleValueWithAnnotationFormatterFactoryRegistered() throws ParseException { public void bindSingleValueWithAnnotationFormatterFactoryRegistered() throws ParseException {
GenericFormatterRegistry formatterRegistry = new GenericFormatterRegistry(); GenericFormatterRegistry formatterRegistry = new GenericFormatterRegistry();
formatterRegistry.add(new CurrencyAnnotationFormatterFactory()); formatterRegistry.add(new CurrencyAnnotationFormatterFactory());
bindingFactory.setFormatterRegistry(formatterRegistry); presentationModel.setFormatterRegistry(formatterRegistry);
binder.bind(Collections.singletonMap("currency", "$23.56")); binder.bind(Collections.singletonMap("currency", "$23.56"));
assertEquals(new BigDecimal("23.56"), bean.getCurrency()); assertEquals(new BigDecimal("23.56"), bean.getCurrency());
@ -135,27 +138,27 @@ public class GenericBinderTests {
@Test @Test
public void bindSingleValuePropertyNotFound() throws ParseException { public void bindSingleValuePropertyNotFound() throws ParseException {
BindingResults results = binder.bind(Collections.singletonMap("bogus", "2009-06-01")); BindingResults results = binder.bind(Collections.singletonMap("bogus", "2009-06-01"));
assertEquals("bogus", results.get(0).getProperty()); assertEquals("bogus", results.get(0).getFieldName());
assertTrue(results.get(0).isFailure()); assertTrue(results.get(0).isFailure());
assertEquals("propertyNotFound", results.get(0).getAlert().getCode()); assertEquals("propertyNotFound", results.get(0).getAlert().getCode());
} }
@Test(expected = MissingSourceValuesException.class) @Test(expected = MissingFieldException.class)
public void bindMissingRequiredSourceValue() { public void bindMissingRequiredSourceValue() {
binder.setRequired(new String[] { "integer" }); binder.setRequiredFields(new String[] { "integer" });
// missing "integer" - violated bind contract // missing "integer" - violated bind contract
binder.bind(Collections.singletonMap("string", "test")); binder.bind(Collections.singletonMap("string", "test"));
} }
@Test @Test
public void getBindingCustomFormatter() { public void getBindingCustomFormatter() {
bindingFactory.bindingRule("currency").formatWith(new CurrencyFormatter()); presentationModel.field("currency").formatWith(new CurrencyFormatter());
Binding b = bindingFactory.getBinding("currency"); FieldModel b = presentationModel.getFieldModel("currency");
assertFalse(b.isList()); assertFalse(b.isList());
assertFalse(b.isMap()); assertFalse(b.isMap());
assertEquals(null, b.getValue()); assertEquals(null, b.getValue());
assertEquals("", b.getRenderValue()); assertEquals("", b.getRenderValue());
b.applySourceValue("$23.56"); b.applySubmittedValue("$23.56");
assertEquals(BindingStatus.DIRTY, b.getBindingStatus()); assertEquals(BindingStatus.DIRTY, b.getBindingStatus());
assertEquals(new BigDecimal("23.56"), b.getValue()); assertEquals(new BigDecimal("23.56"), b.getValue());
assertEquals("$23.56", b.getRenderValue()); assertEquals("$23.56", b.getRenderValue());
@ -168,9 +171,9 @@ public class GenericBinderTests {
@Test @Test
public void getBindingCustomFormatterRequiringTypeCoersion() { public void getBindingCustomFormatterRequiringTypeCoersion() {
// IntegerFormatter formats Longs, so conversion from Integer -> Long is performed // IntegerFormatter formats Longs, so conversion from Integer -> Long is performed
bindingFactory.bindingRule("integer").formatWith(new IntegerFormatter()); presentationModel.field("integer").formatWith(new IntegerFormatter());
Binding b = bindingFactory.getBinding("integer"); FieldModel b = presentationModel.getFieldModel("integer");
b.applySourceValue("2,300"); b.applySubmittedValue("2,300");
assertEquals("2,300", b.getRenderValue()); assertEquals("2,300", b.getRenderValue());
b.commit(); b.commit();
assertEquals(BindingStatus.COMMITTED, b.getBindingStatus()); assertEquals(BindingStatus.COMMITTED, b.getBindingStatus());
@ -182,10 +185,10 @@ public class GenericBinderTests {
MockMessageSource messages = new MockMessageSource(); MockMessageSource messages = new MockMessageSource();
messages.addMessage("typeMismatch", Locale.US, messages.addMessage("typeMismatch", Locale.US,
"Please enter an integer in format ### for the #{label} field; you entered #{value}"); "Please enter an integer in format ### for the #{label} field; you entered #{value}");
bindingFactory.setMessageSource(messages); presentationModel.setMessageSource(messages);
bindingFactory.bindingRule("integer").formatWith(new IntegerFormatter()); presentationModel.field("integer").formatWith(new IntegerFormatter());
Binding b = bindingFactory.getBinding("integer"); FieldModel b = presentationModel.getFieldModel("integer");
b.applySourceValue("bogus"); b.applySubmittedValue("bogus");
assertEquals("Please enter an integer in format ### for the integer field; you entered bogus", b assertEquals("Please enter an integer in format ### for the integer field; you entered bogus", b
.getStatusAlert().getMessage()); .getStatusAlert().getMessage());
} }
@ -193,11 +196,11 @@ public class GenericBinderTests {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Test @Test
public void getBindingMultiValued() { public void getBindingMultiValued() {
Binding b = bindingFactory.getBinding("foos"); FieldModel b = presentationModel.getFieldModel("foos");
assertTrue(b.isList()); assertTrue(b.isList());
assertEquals(null, b.getValue()); assertEquals(null, b.getValue());
assertEquals("", b.getRenderValue()); assertEquals("", b.getRenderValue());
b.applySourceValue(new String[] { "BAR", "BAZ", "BOOP" }); b.applySubmittedValue(new String[] { "BAR", "BAZ", "BOOP" });
b.commit(); b.commit();
assertEquals(FooEnum.BAR, bean.getFoos().get(0)); assertEquals(FooEnum.BAR, bean.getFoos().get(0));
assertEquals(FooEnum.BAZ, bean.getFoos().get(1)); assertEquals(FooEnum.BAZ, bean.getFoos().get(1));
@ -213,22 +216,22 @@ public class GenericBinderTests {
@Test @Test
public void getBindingMultiValuedIndexAccess() { public void getBindingMultiValuedIndexAccess() {
bean.setFoos(Arrays.asList(new FooEnum[] { FooEnum.BAR })); bean.setFoos(Arrays.asList(new FooEnum[] { FooEnum.BAR }));
Binding b = bindingFactory.getBinding("foos[0]"); FieldModel b = presentationModel.getFieldModel("foos[0]");
assertFalse(b.isList()); assertFalse(b.isList());
assertEquals(FooEnum.BAR, b.getValue()); assertEquals(FooEnum.BAR, b.getValue());
assertEquals("BAR", b.getRenderValue()); assertEquals("BAR", b.getRenderValue());
b.applySourceValue("BAZ"); b.applySubmittedValue("BAZ");
assertEquals("BAZ", b.getRenderValue()); assertEquals("BAZ", b.getRenderValue());
assertEquals(FooEnum.BAZ, b.getValue()); assertEquals(FooEnum.BAZ, b.getValue());
} }
@Test @Test
public void getBindingMultiValuedTypeConversionFailure() { public void getBindingMultiValuedTypeConversionFailure() {
Binding b = bindingFactory.getBinding("foos"); FieldModel b = presentationModel.getFieldModel("foos");
assertTrue(b.isList()); assertTrue(b.isList());
assertEquals(null, b.getValue()); assertEquals(null, b.getValue());
b.applySourceValue(new String[] { "BAR", "BOGUS", "BOOP" }); b.applySubmittedValue(new String[] { "BAR", "BOGUS", "BOOP" });
assertEquals(BindingStatus.INVALID_SOURCE_VALUE, b.getBindingStatus()); assertEquals(BindingStatus.INVALID_SUBMITTED_VALUE, b.getBindingStatus());
assertEquals("typeMismatch", b.getStatusAlert().getCode()); assertEquals("typeMismatch", b.getStatusAlert().getCode());
} }
@ -263,7 +266,7 @@ public class GenericBinderTests {
public void bindToListSingleString() { public void bindToListSingleString() {
GenericFormatterRegistry formatterRegistry = new GenericFormatterRegistry(); GenericFormatterRegistry formatterRegistry = new GenericFormatterRegistry();
formatterRegistry.add(new CollectionTypeDescriptor(List.class, Address.class), new AddressListFormatter()); formatterRegistry.add(new CollectionTypeDescriptor(List.class, Address.class), new AddressListFormatter());
bindingFactory.setFormatterRegistry(formatterRegistry); presentationModel.setFormatterRegistry(formatterRegistry);
Map<String, String> values = new LinkedHashMap<String, String>(); Map<String, String> values = new LinkedHashMap<String, String>();
values values
.put("addresses", .put("addresses",
@ -310,7 +313,7 @@ public class GenericBinderTests {
public void getListAsSingleString() { public void getListAsSingleString() {
GenericFormatterRegistry formatterRegistry = new GenericFormatterRegistry(); GenericFormatterRegistry formatterRegistry = new GenericFormatterRegistry();
formatterRegistry.add(new CollectionTypeDescriptor(List.class, Address.class), new AddressListFormatter()); formatterRegistry.add(new CollectionTypeDescriptor(List.class, Address.class), new AddressListFormatter());
bindingFactory.setFormatterRegistry(formatterRegistry); presentationModel.setFormatterRegistry(formatterRegistry);
Address address1 = new Address(); Address address1 = new Address();
address1.setStreet("s1"); address1.setStreet("s1");
address1.setCity("c1"); address1.setCity("c1");
@ -325,8 +328,8 @@ public class GenericBinderTests {
addresses.add(address1); addresses.add(address1);
addresses.add(address2); addresses.add(address2);
bean.addresses = addresses; bean.addresses = addresses;
String value = bindingFactory.getBinding("addresses").getRenderValue(); String value = presentationModel.getFieldModel("addresses").getRenderValue();
assertEquals("s1:c1:st1:z1,s2:c2:st2:z2,", value); assertEquals("s1:c1:st1:z1,s2:c2:st2:z2", value);
} }
@Test @Test
@ -345,7 +348,7 @@ public class GenericBinderTests {
addresses.add(address1); addresses.add(address1);
addresses.add(address2); addresses.add(address2);
bean.addresses = addresses; bean.addresses = addresses;
String value = bindingFactory.getBinding("addresses").getRenderValue(); String value = presentationModel.getFieldModel("addresses").getRenderValue();
assertEquals("s1:c1:st1:z1,s2:c2:st2:z2", value); assertEquals("s1:c1:st1:z1,s2:c2:st2:z2", value);
} }
@ -424,7 +427,7 @@ public class GenericBinderTests {
foods.put(FoodGroup.FRUIT, "Peaches"); foods.put(FoodGroup.FRUIT, "Peaches");
foods.put(FoodGroup.MEAT, "Ham"); foods.put(FoodGroup.MEAT, "Ham");
bean.favoriteFoodsByGroup = foods; bean.favoriteFoodsByGroup = foods;
String value = bindingFactory.getBinding("favoriteFoodsByGroup").getRenderValue(); String value = presentationModel.getFieldModel("favoriteFoodsByGroup").getRenderValue();
// TODO this is inconsistent with previous test case // TODO this is inconsistent with previous test case
assertEquals("{DAIRY=Milk, FRUIT=Peaches, MEAT=Ham}", value); assertEquals("{DAIRY=Milk, FRUIT=Peaches, MEAT=Ham}", value);
} }
@ -439,15 +442,15 @@ public class GenericBinderTests {
@Test @Test
public void formatPossibleValue() { public void formatPossibleValue() {
bindingFactory.bindingRule("currency").formatWith(new CurrencyFormatter()); presentationModel.field("currency").formatWith(new CurrencyFormatter());
Binding b = bindingFactory.getBinding("currency"); FieldModel b = presentationModel.getFieldModel("currency");
assertEquals("$5.00", b.formatValue(new BigDecimal("5"))); assertEquals("$5.00", b.formatValue(new BigDecimal("5")));
} }
@Test @Test
public void formatPossibleValueDefault() { public void formatPossibleValueDefault() {
bindingFactory.bindingRule("currency"); presentationModel.field("currency");
Binding b = bindingFactory.getBinding("currency"); FieldModel b = presentationModel.getFieldModel("currency");
assertEquals("5", b.formatValue(new BigDecimal("5"))); assertEquals("5", b.formatValue(new BigDecimal("5")));
} }

View File

@ -1,4 +1,4 @@
package org.springframework.ui.binding.support; package org.springframework.ui.binding.binder;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@ -12,11 +12,12 @@ import java.util.Map;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.ui.binding.binder.BindingResults; import org.springframework.ui.binding.binder.BindingResults;
import org.springframework.ui.binding.binder.WebBinder; import org.springframework.ui.binding.binder.WebBinder;
import org.springframework.ui.binding.support.DefaultPresentationModel;
import org.springframework.ui.binding.support.GenericFormatterRegistry;
import org.springframework.ui.format.date.DateFormatter; import org.springframework.ui.format.date.DateFormatter;
import org.springframework.ui.format.number.CurrencyFormat; import org.springframework.ui.format.number.CurrencyFormat;
import org.springframework.ui.format.number.CurrencyFormatter; import org.springframework.ui.format.number.CurrencyFormatter;
@ -25,11 +26,15 @@ public class WebBinderTests {
TestBean bean = new TestBean(); TestBean bean = new TestBean();
WebBinder binder = new WebBinder(new GenericBindingFactory(bean)); DefaultPresentationModel presentationModel;
WebBinder binder;
@Before @Before
public void setUp() { public void setUp() {
LocaleContextHolder.setLocale(Locale.US); LocaleContextHolder.setLocale(Locale.US);
presentationModel = new DefaultPresentationModel(bean);
binder = new WebBinder(presentationModel);
} }
@After @After
@ -38,11 +43,11 @@ public class WebBinderTests {
} }
@Test @Test
@Ignore
public void bindUserValuesCreatedFromUserMap() throws ParseException { public void bindUserValuesCreatedFromUserMap() throws ParseException {
GenericFormatterRegistry registry = new GenericFormatterRegistry(); GenericFormatterRegistry registry = new GenericFormatterRegistry();
registry.add(Date.class, new DateFormatter());
registry.add(CurrencyFormat.class, new CurrencyFormatter()); registry.add(CurrencyFormat.class, new CurrencyFormatter());
//binder.setFormatterRegistry(registry); presentationModel.setFormatterRegistry(registry);
Map<String, String> userMap = new LinkedHashMap<String, String>(); Map<String, String> userMap = new LinkedHashMap<String, String>();
userMap.put("string", "test"); userMap.put("string", "test");
userMap.put("_integer", "doesn't matter"); userMap.put("_integer", "doesn't matter");
@ -53,12 +58,12 @@ public class WebBinderTests {
userMap.put("_addresses", "doesn't matter"); userMap.put("_addresses", "doesn't matter");
BindingResults results = binder.bind(userMap); BindingResults results = binder.bind(userMap);
assertEquals(6, results.size()); assertEquals(6, results.size());
assertEquals("test", results.get(0).getSourceValue()); assertEquals("test", results.get(0).getSubmittedValue());
assertEquals(null, results.get(1).getSourceValue()); assertEquals(null, results.get(1).getSubmittedValue());
assertEquals(Boolean.FALSE, results.get(2).getSourceValue()); assertEquals(Boolean.FALSE, results.get(2).getSubmittedValue());
assertEquals("2009-06-10", results.get(3).getSourceValue()); assertEquals("2009-06-10", results.get(3).getSubmittedValue());
assertEquals("$5.00", results.get(4).getSourceValue()); assertEquals("$5.00", results.get(4).getSubmittedValue());
assertEquals(null, results.get(5).getSourceValue()); assertEquals(null, results.get(5).getSubmittedValue());
assertEquals("test", bean.getString()); assertEquals("test", bean.getString());
assertEquals(0, bean.getInteger()); assertEquals(0, bean.getInteger());
@ -73,13 +78,21 @@ public class WebBinderTests {
} }
public static class TestBean { public static class TestBean {
private String string; private String string;
private int integer; private int integer;
private boolean bool; private boolean bool;
private Date date; private Date date;
private FooEnum foo; private FooEnum foo;
private BigDecimal currency; private BigDecimal currency;
private List<FooEnum> foos; private List<FooEnum> foos;
private List<Address> addresses; private List<Address> addresses;
public String getString() { public String getString() {