diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/Binding.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/Binding.java
index fcd8884be84..7cc389ef195 100644
--- a/org.springframework.context/src/main/java/org/springframework/ui/binding/Binding.java
+++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/Binding.java
@@ -34,10 +34,25 @@ public interface Binding {
Object getValue();
/**
- * If this Binding is read-only.
- * A read-only Binding cannot have source values applied and cannot be committed.
+ * If this Binding is editable.
+ * 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.
*/
- boolean isReadOnly();
+ boolean isEditable();
+
+ /**
+ * If this Binding is enabled.
+ * Used to determine if the user can interact with the field.
+ * A Binding that is not enabled cannot have source values applied and cannot be committed.
+ */
+ boolean isEnabled();
+
+ /**
+ * If this Binding is visible.
+ * Used to determine if the user can see the field.
+ * A Binding that is not visible cannot have source values applied and cannot be committed.
+ */
+ boolean isVisible();
/**
* Apply the source value to this binding.
@@ -45,7 +60,7 @@ public interface Binding {
* Sets to {@link BindingStatus#DIRTY} if succeeds.
* Sets to {@link BindingStatus#INVALID_SOURCE_VALUE} if fails.
* @param sourceValue
- * @throws IllegalStateException if {@link #isReadOnly()}
+ * @throws IllegalStateException if {@link #isEditable()}
*/
void applySourceValue(Object sourceValue);
@@ -60,11 +75,12 @@ public interface Binding {
BindingStatus getStatus();
/**
- * An alert that communicates the details of a BindingStatus change to the user.
- * Returns null if the BindingStatus has never changed.
- * Returns a {@link Severity#INFO} Alert with code bindSuccess after a successful commit.
- * Returns a {@link Severity#ERROR} Alert with code typeMismatch if the source value could not be converted to type required by the Model.
- * Returns a {@link Severity#FATAL} Alert with code internalError if the buffered value could not be committed due to a unexpected runtime exception.
+ * An alert that communicates current status to the user.
+ * Returns null if {@link BindingStatus#CLEAN} and {@link ValidationStatus#NOT_VALIDATED}.
+ * Returns a {@link Severity#INFO} Alert with code bindSuccess when {@link BindingStatus#COMMITTED}.
+ * Returns a {@link Severity#ERROR} Alert with code typeMismatch when {@link BindingStatus#INVALID_SOURCE_VALUE} or {@link BindingStatus#COMMIT_FAILURE} due to a value parse / type conversion error.
+ * Returns a {@link Severity#FATAL} Alert with code internalError 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}.
*/
Alert getStatusAlert();
@@ -72,10 +88,16 @@ public interface Binding {
* Commit the buffered value to the model.
* Sets to {@link BindingStatus#COMMITTED} if succeeds.
* Sets to {@link BindingStatus#COMMIT_FAILURE} if fails.
- * @throws IllegalStateException if not {@link BindingStatus#DIRTY} or {@link #isReadOnly()}
+ * @throws IllegalStateException if not {@link BindingStatus#DIRTY} or {@link #isEditable()}
*/
void commit();
+ /**
+ * Clear the buffered value without committing.
+ * @throws IllegalStateException if BindingStatus is CLEAN or COMMITTED.
+ */
+ void revert();
+
/**
* Access raw model values.
*/
@@ -91,14 +113,14 @@ public interface Binding {
/**
* If bound to an indexable Collection, either a {@link java.util.List} or an array.
*/
- boolean isIndexable();
+ boolean isList();
/**
* If a List, get a Binding to a element in the List.
* @param index the element index
* @return the indexed binding
*/
- Binding getIndexedBinding(int index);
+ Binding getListElementBinding(int index);
/**
* If bound to a {@link java.util.Map}.
@@ -110,11 +132,11 @@ public interface Binding {
* @param key the map key
* @return the keyed binding
*/
- Binding getKeyedBinding(Object key);
+ Binding getMapValueBinding(Object key);
/**
* Format a potential model value for display.
- * If an indexable binding, expects the model value to be a potential collection element & uses the configured element formatter.
+ * If a list binding, expects the model 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.
* @param potentialValue the potential value
* @return the formatted string
@@ -145,7 +167,7 @@ public interface Binding {
}
/**
- * The states of a Binding.
+ * Binding states.
* @author Keith Donald
*/
public enum BindingStatus {
@@ -175,4 +197,26 @@ public interface Binding {
*/
COMMIT_FAILURE
}
+
+ /**
+ * Validation states.
+ * @author Keith Donald
+ */
+ public enum ValidationStatus {
+
+ /**
+ * Initial state: No validation has run.
+ */
+ NOT_VALIDATED,
+
+ /**
+ * Validation has succeeded.
+ */
+ VALID,
+
+ /**
+ * Validation has failed.
+ */
+ INVALID
+ }
}
\ No newline at end of file
diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/config/BindingRuleConfiguration.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/config/BindingRuleConfiguration.java
index aa7994f4bf1..7360d2d332c 100644
--- a/org.springframework.context/src/main/java/org/springframework/ui/binding/config/BindingRuleConfiguration.java
+++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/config/BindingRuleConfiguration.java
@@ -18,7 +18,7 @@ package org.springframework.ui.binding.config;
import org.springframework.ui.format.Formatter;
/**
- * A fluent interface for configuring a newly added binding.
+ * A fluent interface for configuring a newly added binding rule.
* @author Keith Donald
*/
public interface BindingRuleConfiguration {
@@ -31,23 +31,21 @@ public interface BindingRuleConfiguration {
BindingRuleConfiguration formatWith(Formatter> formatter);
/**
- * If a collection property, set the Formatter to use to format indexed collection elements.
+ * If a indexable map property, set the Formatter to use to format map key indexes.
+ * Default is null.
+ */
+ BindingRuleConfiguration formatKeysWith(Formatter> formatter);
+
+ /**
+ * If an indexable list or map property, set the Formatter to use to format indexed elements.
* Default is null.
*/
BindingRuleConfiguration formatElementsWith(Formatter> formatter);
/**
- * Mark the binding as required.
- * A required binding will generate an exception if no sourceValue is provided to a bind invocation.
- * This attribute is used to detect client configuration errors.
- * It is not intended to be used as a user data validation constraint.
- * Examples:
- *
- * name=required - will generate an exception if 'name' is not contained in the source values map.
- * addresses=required - will generate an exception if 'addresses[n]' is not contained in the source value map for at least one n.
- * addresses.city=required - will generate an exception if 'addresses[n].city' is not present in the source values map, for every address[n].
- *
+ * Mark the binding as read only.
+ * A read-only binding cannot have source values applied and cannot be committed.
*/
- BindingRuleConfiguration required();
+ BindingRuleConfiguration readOnly();
}
\ No newline at end of file
diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericBinder.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericBinder.java
index d0265623b3f..a8e6c7da0c4 100644
--- a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericBinder.java
+++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericBinder.java
@@ -26,6 +26,7 @@ import org.springframework.ui.binding.Binding;
import org.springframework.ui.binding.BindingResult;
import org.springframework.ui.binding.BindingResults;
import org.springframework.ui.binding.Binding.BindingStatus;
+import org.springframework.ui.binding.config.BindingRuleConfiguration;
import org.springframework.util.Assert;
/**
@@ -77,6 +78,15 @@ public class GenericBinder implements Binder {
this.typeConverter = typeConverter;
}
+ /**
+ *
+ * @param propertyPath binding rule property path in format prop.nestedListProp[].nestedMapProp[].nestedProp
+ * @return
+ */
+ public BindingRuleConfiguration bindingRule(String propertyPath) {
+ return null;
+ }
+
// implementing Binder
public Object getModel() {
@@ -93,9 +103,9 @@ public class GenericBinder implements Binder {
for (PropertyPathElement element : path.getNestedElements()) {
if (element.isIndex()) {
if (binding.isMap()) {
- binding = binding.getKeyedBinding(element.getValue());
- } else if (binding.isIndexable()) {
- binding = binding.getIndexedBinding(element.getIntValue());
+ binding = binding.getMapValueBinding(element.getValue());
+ } else if (binding.isList()) {
+ binding = binding.getListElementBinding(element.getIntValue());
} else {
throw new IllegalArgumentException("Attempted to index a property that is not a Collection or Map");
}
@@ -135,7 +145,7 @@ public class GenericBinder implements Binder {
private BindingResult bind(Map.Entry sourceValue, Binding binding) {
String property = sourceValue.getKey();
Object value = sourceValue.getValue();
- if (binding.isReadOnly()) {
+ if (binding.isEditable()) {
return new PropertyNotWriteableResult(property, value, messageSource);
} else {
binding.applySourceValue(value);
@@ -145,5 +155,5 @@ public class GenericBinder implements Binder {
return new BindingStatusResult(property, value, binding.getStatusAlert());
}
}
-
+
}
diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/PropertyBinding.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/PropertyBinding.java
index 37300d16ec0..40bee219729 100644
--- a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/PropertyBinding.java
+++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/PropertyBinding.java
@@ -53,7 +53,7 @@ public class PropertyBinding implements Binding {
private ValueBuffer buffer;
- private BindingStatus bindingStatus;
+ private BindingStatus status;
public PropertyBinding(String property, Object model, TypeConverter typeConverter) {
this.propertyDescriptor = findPropertyDescriptor(property, model);
@@ -61,36 +61,43 @@ public class PropertyBinding implements Binding {
this.model = model;
this.typeConverter = typeConverter;
this.buffer = new ValueBuffer(getModel());
- bindingStatus = BindingStatus.CLEAN;
+ status = BindingStatus.CLEAN;
}
public Object getValue() {
- if (bindingStatus == BindingStatus.INVALID_SOURCE_VALUE) {
+ if (status == BindingStatus.INVALID_SOURCE_VALUE) {
return sourceValue;
- } else if (bindingStatus == BindingStatus.DIRTY || bindingStatus == BindingStatus.COMMIT_FAILURE) {
+ } else if (status == BindingStatus.DIRTY || status == BindingStatus.COMMIT_FAILURE) {
return formatValue(buffer.getValue());
} else {
return formatValue(getModel().getValue());
}
}
- public boolean isReadOnly() {
- return propertyDescriptor.getWriteMethod() == null || markedNotEditable();
+ public boolean isEditable() {
+ return propertyDescriptor.getWriteMethod() != null || !markedNotEditable();
+ }
+
+ public boolean isEnabled() {
+ return true;
+ }
+
+ public boolean isVisible() {
+ return true;
}
public void applySourceValue(Object sourceValue) {
- if (isReadOnly()) {
- throw new IllegalStateException("Property is read only");
- }
+ assertEditable();
+ assertEnabled();
if (sourceValue instanceof String) {
try {
buffer.setValue(valueFormatter.parse((String) sourceValue, getLocale()));
sourceValue = null;
- bindingStatus = BindingStatus.DIRTY;
+ status = BindingStatus.DIRTY;
} catch (ParseException e) {
this.sourceValue = sourceValue;
sourceValueParseException = e;
- bindingStatus = BindingStatus.INVALID_SOURCE_VALUE;
+ status = BindingStatus.INVALID_SOURCE_VALUE;
}
} else if (sourceValue instanceof String[]) {
String[] sourceValues = (String[]) sourceValue;
@@ -107,24 +114,24 @@ public class PropertyBinding implements Binding {
} catch (ParseException e) {
this.sourceValue = sourceValue;
sourceValueParseException = e;
- bindingStatus = BindingStatus.INVALID_SOURCE_VALUE;
+ status = BindingStatus.INVALID_SOURCE_VALUE;
break;
}
}
- if (bindingStatus != BindingStatus.INVALID_SOURCE_VALUE) {
+ if (status != BindingStatus.INVALID_SOURCE_VALUE) {
buffer.setValue(parsed);
sourceValue = null;
- bindingStatus = BindingStatus.DIRTY;
+ status = BindingStatus.DIRTY;
}
}
}
public BindingStatus getStatus() {
- return bindingStatus;
+ return status;
}
public Alert getStatusAlert() {
- if (bindingStatus == BindingStatus.INVALID_SOURCE_VALUE) {
+ if (status == BindingStatus.INVALID_SOURCE_VALUE) {
return new Alert() {
public String getCode() {
return "typeMismatch";
@@ -138,7 +145,7 @@ public class PropertyBinding implements Binding {
return Severity.ERROR;
}
};
- } else if (bindingStatus == BindingStatus.COMMIT_FAILURE) {
+ } else if (status == BindingStatus.COMMIT_FAILURE) {
if (buffer.getFlushException() instanceof ConversionFailedException) {
return new Alert() {
public String getCode() {
@@ -168,7 +175,7 @@ public class PropertyBinding implements Binding {
}
};
}
- } else if (bindingStatus == BindingStatus.COMMITTED) {
+ } else if (status == BindingStatus.COMMITTED) {
return new Alert() {
public String getCode() {
return "bindSucces";
@@ -188,14 +195,30 @@ public class PropertyBinding implements Binding {
}
public void commit() {
- if (bindingStatus != BindingStatus.DIRTY) {
- throw new IllegalStateException("Binding not dirty; nothing to commit");
- }
- buffer.flush();
- if (buffer.flushFailed()) {
- bindingStatus = BindingStatus.COMMIT_FAILURE;
+ assertEditable();
+ assertEnabled();
+ if (status == BindingStatus.DIRTY) {
+ buffer.flush();
+ if (buffer.flushFailed()) {
+ status = BindingStatus.COMMIT_FAILURE;
+ } else {
+ status = BindingStatus.COMMITTED;
+ }
} else {
- bindingStatus = BindingStatus.COMMITTED;
+ throw new IllegalStateException("Binding is not dirty; nothing to commit");
+ }
+ }
+
+ public void revert() {
+ if (status == BindingStatus.INVALID_SOURCE_VALUE) {
+ sourceValue = null;
+ sourceValueParseException = null;
+ status = BindingStatus.CLEAN;
+ } else if (status == BindingStatus.DIRTY || status == BindingStatus.COMMIT_FAILURE) {
+ buffer.clear();
+ status = BindingStatus.CLEAN;
+ } else {
+ throw new IllegalStateException("Nothing to revert");
}
}
@@ -210,9 +233,6 @@ public class PropertyBinding implements Binding {
}
public void setValue(Object value) {
- if (isReadOnly()) {
- throw new IllegalStateException("Property is read-only");
- }
TypeDescriptor targetType = new TypeDescriptor(new MethodParameter(propertyDescriptor.getWriteMethod(), 0));
if (value != null && typeConverter.canConvert(value.getClass(), targetType)) {
value = typeConverter.convert(value, targetType);
@@ -230,11 +250,11 @@ public class PropertyBinding implements Binding {
return new PropertyBinding(nestedProperty, getValue(), typeConverter);
}
- public boolean isIndexable() {
+ public boolean isList() {
return getModel().getValueType().isArray() || List.class.isAssignableFrom(getModel().getValueType());
}
- public Binding getIndexedBinding(int index) {
+ public Binding getListElementBinding(int index) {
assertListProperty();
//return new IndexedBinding(index, (List) getValue(), getCollectionTypeDescriptor(), typeConverter);
return null;
@@ -244,7 +264,7 @@ public class PropertyBinding implements Binding {
return Map.class.isAssignableFrom(getModel().getValueType());
}
- public Binding getKeyedBinding(Object key) {
+ public Binding getMapValueBinding(Object key) {
assertMapProperty();
if (key instanceof String) {
try {
@@ -259,7 +279,7 @@ public class PropertyBinding implements Binding {
public String formatValue(Object value) {
Formatter formatter;
- if (isIndexable() || isMap()) {
+ if (isList() || isMap()) {
formatter = indexedValueFormatter;
} else {
formatter = valueFormatter;
@@ -343,7 +363,7 @@ public class PropertyBinding implements Binding {
}
private void assertScalarProperty() {
- if (isIndexable()) {
+ if (isList()) {
throw new IllegalArgumentException("Is a Collection but should be a scalar");
}
if (isMap()) {
@@ -352,75 +372,30 @@ public class PropertyBinding implements Binding {
}
private void assertListProperty() {
- if (!isIndexable()) {
+ if (!isList()) {
throw new IllegalStateException("Not a List property binding");
}
}
private void assertMapProperty() {
- if (!isIndexable()) {
+ if (!isList()) {
throw new IllegalStateException("Not a Map property binding");
}
}
+
+ private void assertEditable() {
+ if (!isEditable()) {
+ throw new IllegalStateException("Binding is not editable");
+ }
+ }
+
+ private void assertEnabled() {
+ if (!isEditable()) {
+ throw new IllegalStateException("Binding is not enabled");
+ }
+ }
private boolean markedNotEditable() {
return false;
}
-
- static class ValueBuffer {
-
- private Object value;
-
- private boolean hasValue;
-
- private Model model;
-
- private boolean flushFailed;
-
- private Exception flushFailureCause;
-
- public ValueBuffer(Model model) {
- this.model = model;
- }
-
- public boolean hasValue() {
- return hasValue;
- }
-
- public Object getValue() {
- if (!hasValue()) {
- throw new IllegalStateException("No value in buffer");
- }
- return value;
- }
-
- public void setValue(Object value) {
- this.value = value;
- hasValue = true;
- }
-
- public void flush() {
- try {
- model.setValue(value);
- clear();
- } catch (Exception e) {
- flushFailed = true;
- flushFailureCause = e;
- }
- }
-
- public void clear() {
- value = null;
- hasValue = false;
- flushFailed = false;
- }
-
- public boolean flushFailed() {
- return flushFailed;
- }
-
- public Exception getFlushException() {
- return flushFailureCause;
- }
- }
}
\ No newline at end of file
diff --git a/org.springframework.context/src/test/java/org/springframework/ui/binding/support/GenericBinderTests.java b/org.springframework.context/src/test/java/org/springframework/ui/binding/support/GenericBinderTests.java
index b3e8023f46d..3f0a166c7e9 100644
--- a/org.springframework.context/src/test/java/org/springframework/ui/binding/support/GenericBinderTests.java
+++ b/org.springframework.context/src/test/java/org/springframework/ui/binding/support/GenericBinderTests.java
@@ -7,6 +7,7 @@ import static org.junit.Assert.assertTrue;
import java.math.BigDecimal;
import java.text.ParseException;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
@@ -21,6 +22,7 @@ import org.springframework.ui.binding.BindingResults;
import org.springframework.ui.format.AnnotationFormatterFactory;
import org.springframework.ui.format.Formatted;
import org.springframework.ui.format.Formatter;
+import org.springframework.ui.format.date.DateFormatter;
import org.springframework.ui.format.number.CurrencyFormat;
import org.springframework.ui.format.number.CurrencyFormatter;
@@ -55,7 +57,6 @@ public class GenericBinderTests {
BindingResults results = binder.bind(values);
assertEquals(3, results.size());
- System.out.println(results);
assertEquals("string", results.get(0).getProperty());
assertFalse(results.get(0).isFailure());
assertEquals("test", results.get(0).getSourceValue());
@@ -87,17 +88,15 @@ public class GenericBinderTests {
assertEquals("typeMismatch", results.get(1).getAlert().getCode());
}
- /*
@Test
public void bindSingleValuePropertyFormatter() throws ParseException {
- BindingRulesBuilder builder = new BindingRulesBuilder(TestBean.class);
- builder.bind("date").formatWith(new DateFormatter());;
- GenericBinder binder = new GenericBinder(bean, builder.getBindingRules());
-
+ GenericBinder binder = new GenericBinder(bean);
+ binder.bindingRule("date").formatWith(new DateFormatter());
binder.bind(Collections.singletonMap("date", "2009-06-01"));
assertEquals(new DateFormatter().parse("2009-06-01", Locale.US), bean.getDate());
}
+ /*
@Test
public void bindSingleValuePropertyFormatterParseException() {
BindingRulesBuilder builder = new BindingRulesBuilder(TestBean.class);