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 b606069afc6..3256caf6ce9 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
@@ -16,6 +16,7 @@
package org.springframework.ui.binding;
import org.springframework.ui.alert.Alert;
+import org.springframework.ui.alert.Severity;
/**
* A binding between a source element and a model property.
@@ -25,9 +26,10 @@ import org.springframework.ui.alert.Alert;
public interface Binding {
/**
- * The bound value to display in the UI.
- * Is the formatted model value if not dirty.
- * Is the buffered value if dirty.
+ * The value to display in the UI.
+ * Is the formatted model value if {@link BindingStatus#CLEAN} or {@link BindingStatus#COMMITTED}.
+ * Is the formatted buffered value if {@link BindingStatus#DIRTY} or {@link BindingStatus#COMMIT_FAILURE}.
+ * Is the source value if {@link BindingStatus#INVALID_SOURCE_VALUE}.
*/
Object getValue();
@@ -39,38 +41,40 @@ public interface Binding {
/**
* Apply the source value to this binding.
- * The source value is parsed, validated, and stored in the binding's value buffer.
- * Sets 'dirty' status to true.
- * Sets 'valid' status to false if the source value is not valid.
+ * The source value is parsed and stored in the binding's value buffer.
+ * Sets to {@link BindingStatus#DIRTY} if succeeds.
+ * Sets to {@link BindingStatus#INVALID_SOURCE_VALUE} if fails.
* @param sourceValue
* @throws IllegalStateException if read only
*/
void applySourceValue(Object sourceValue);
/**
- * True if there is an uncommitted value in the binding buffer.
- * Set to true after applying a source value.
- * Set to false after a commit.
+ * The current binding status.
+ * Initially {@link BindingStatus#CLEAN clean}.
+ * Is {@link BindingStatus#DIRTY} after applying a source value to the value buffer.
+ * 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#COMMIT_FAILURE} if a buffered value could not be committed.
*/
- boolean isDirty();
+ BindingStatus getStatus();
/**
- * False if dirty and the buffered value is invalid.
- * False if dirty and the buffered value appears valid but could not be committed.
- * True otherwise.
+ * 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.
*/
- boolean isValid();
+ Alert getStatusAlert();
/**
* Commit the buffered value to the model.
- * @throws IllegalStateException if not dirty, not valid, or read-only
+ * Sets to {@link BindingStatus#CLEAN} if succeeds.
+ * Sets to {@link BindingStatus#COMMIT_FAILURE} if fails.
+ * @throws IllegalStateException if not {@link BindingStatus#DIRTY} or read-only
*/
void commit();
-
- /**
- * An Alert that communicates the current status of this Binding.
- */
- Alert getStatusAlert();
/**
* Access raw model values.
@@ -139,4 +143,36 @@ public interface Binding {
*/
void setValue(Object value);
}
+
+ /**
+ * The states of a Binding.
+ * @author Keith Donald
+ */
+ public enum BindingStatus {
+
+ /**
+ * Initial state: No value is buffered, and there is a direct channel to the model value.
+ */
+ CLEAN,
+
+ /**
+ * An invalid source value is applied.
+ */
+ INVALID_SOURCE_VALUE,
+
+ /**
+ * The binding buffer contains a valid value that has not been committed.
+ */
+ DIRTY,
+
+ /**
+ * The buffered value has been committed.
+ */
+ COMMITTED,
+
+ /**
+ * The buffered value failed to commit.
+ */
+ COMMIT_FAILURE
+ }
}
\ 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 989da08ba01..d0265623b3f 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
@@ -25,6 +25,7 @@ import org.springframework.ui.binding.Binder;
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.util.Assert;
/**
@@ -138,7 +139,7 @@ public class GenericBinder implements Binder {
return new PropertyNotWriteableResult(property, value, messageSource);
} else {
binding.applySourceValue(value);
- if (binding.isValid()) {
+ if (binding.getStatus() == BindingStatus.DIRTY) {
binding.commit();
}
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 f44015d19cd..37300d16ec0 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
@@ -20,9 +20,11 @@ import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
+import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.TypeConverter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.ui.alert.Alert;
+import org.springframework.ui.alert.Severity;
import org.springframework.ui.binding.Binding;
import org.springframework.ui.format.Formatter;
import org.springframework.util.ReflectionUtils;
@@ -46,39 +48,49 @@ public class PropertyBinding implements Binding {
private Object sourceValue;
- // TODO make a ValueBuffer
- private Object bufferedValue;
+ @SuppressWarnings("unused")
+ private ParseException sourceValueParseException;
+
+ private ValueBuffer buffer;
+
+ private BindingStatus bindingStatus;
public PropertyBinding(String property, Object model, TypeConverter typeConverter) {
this.propertyDescriptor = findPropertyDescriptor(property, model);
this.property = property;
this.model = model;
this.typeConverter = typeConverter;
+ this.buffer = new ValueBuffer(getModel());
+ bindingStatus = BindingStatus.CLEAN;
}
public Object getValue() {
- if (isDirty()) {
- // TODO null check isn't good enough
- if (bufferedValue != null) {
- return formatValue(bufferedValue);
- } else {
- return sourceValue;
- }
+ if (bindingStatus == BindingStatus.INVALID_SOURCE_VALUE) {
+ return sourceValue;
+ } else if (bindingStatus == BindingStatus.DIRTY || bindingStatus == BindingStatus.COMMIT_FAILURE) {
+ return formatValue(buffer.getValue());
} else {
return formatValue(getModel().getValue());
}
}
+ public boolean isReadOnly() {
+ return propertyDescriptor.getWriteMethod() == null || markedNotEditable();
+ }
+
public void applySourceValue(Object sourceValue) {
if (isReadOnly()) {
- throw new IllegalStateException("Property is read-only");
+ throw new IllegalStateException("Property is read only");
}
- this.sourceValue = sourceValue;
if (sourceValue instanceof String) {
try {
- this.bufferedValue = valueFormatter.parse((String) sourceValue, getLocale());
+ buffer.setValue(valueFormatter.parse((String) sourceValue, getLocale()));
+ sourceValue = null;
+ bindingStatus = BindingStatus.DIRTY;
} catch (ParseException e) {
-
+ this.sourceValue = sourceValue;
+ sourceValueParseException = e;
+ bindingStatus = BindingStatus.INVALID_SOURCE_VALUE;
}
} else if (sourceValue instanceof String[]) {
String[] sourceValues = (String[]) sourceValue;
@@ -87,50 +99,103 @@ public class PropertyBinding implements Binding {
parsedType = String.class;
}
Object parsed = Array.newInstance(parsedType, sourceValues.length);
- boolean parseError = false;
for (int i = 0; i < sourceValues.length; i++) {
Object parsedValue;
try {
parsedValue = indexedValueFormatter.parse(sourceValues[i], LocaleContextHolder.getLocale());
Array.set(parsed, i, parsedValue);
} catch (ParseException e) {
- parseError = true;
+ this.sourceValue = sourceValue;
+ sourceValueParseException = e;
+ bindingStatus = BindingStatus.INVALID_SOURCE_VALUE;
+ break;
}
}
- if (!parseError) {
- bufferedValue = parsed;
+ if (bindingStatus != BindingStatus.INVALID_SOURCE_VALUE) {
+ buffer.setValue(parsed);
+ sourceValue = null;
+ bindingStatus = BindingStatus.DIRTY;
}
}
}
-
- public boolean isDirty() {
- return sourceValue != null || bufferedValue != null;
+
+ public BindingStatus getStatus() {
+ return bindingStatus;
}
+
+ public Alert getStatusAlert() {
+ if (bindingStatus == BindingStatus.INVALID_SOURCE_VALUE) {
+ return new Alert() {
+ public String getCode() {
+ return "typeMismatch";
+ }
- public boolean isValid() {
- if (!isDirty()) {
- return true;
- } else {
- if (bufferedValue == null) {
- return false;
+ public String getMessage() {
+ return "Could not parse source value";
+ }
+
+ public Severity getSeverity() {
+ return Severity.ERROR;
+ }
+ };
+ } else if (bindingStatus == BindingStatus.COMMIT_FAILURE) {
+ if (buffer.getFlushException() instanceof ConversionFailedException) {
+ return new Alert() {
+ public String getCode() {
+ return "typeMismatch";
+ }
+
+ public String getMessage() {
+ return "Could not convert source value";
+ }
+
+ public Severity getSeverity() {
+ return Severity.ERROR;
+ }
+ };
} else {
- return true;
+ return new Alert() {
+ public String getCode() {
+ return "internalError";
+ }
+
+ public String getMessage() {
+ return "Internal error occurred";
+ }
+
+ public Severity getSeverity() {
+ return Severity.FATAL;
+ }
+ };
}
+ } else if (bindingStatus == BindingStatus.COMMITTED) {
+ return new Alert() {
+ public String getCode() {
+ return "bindSucces";
+ }
+
+ public String getMessage() {
+ return "Binding successful";
+ }
+
+ public Severity getSeverity() {
+ return Severity.INFO;
+ }
+ };
+ } else {
+ return null;
}
}
public void commit() {
- if (!isDirty()) {
+ if (bindingStatus != BindingStatus.DIRTY) {
throw new IllegalStateException("Binding not dirty; nothing to commit");
}
- if (!isValid()) {
- throw new IllegalStateException("Binding is invalid; only commit valid bindings");
- }
- try {
- getModel().setValue(bufferedValue);
- this.bufferedValue = null;
- } catch (Exception e) {
-
+ buffer.flush();
+ if (buffer.flushFailed()) {
+ bindingStatus = BindingStatus.COMMIT_FAILURE;
+ } else {
+ bindingStatus = BindingStatus.COMMITTED;
}
}
@@ -157,10 +222,6 @@ public class PropertyBinding implements Binding {
};
}
- public Alert getStatusAlert() {
- return null;
- }
-
public Binding getBinding(String nestedProperty) {
assertScalarProperty();
if (getValue() == null) {
@@ -196,14 +257,16 @@ public class PropertyBinding implements Binding {
return null;
}
- public boolean isReadOnly() {
- return propertyDescriptor.getWriteMethod() != null && !markedNotEditable();
- }
-
public String formatValue(Object value) {
- Class> formattedType = getFormattedObjectType(valueFormatter.getClass());
+ Formatter formatter;
+ if (isIndexable() || isMap()) {
+ formatter = indexedValueFormatter;
+ } else {
+ formatter = valueFormatter;
+ }
+ Class> formattedType = getFormattedObjectType(formatter.getClass());
value = typeConverter.convert(value, formattedType);
- return valueFormatter.format(value, getLocale());
+ return formatter.format(value, getLocale());
}
// internal helpers
@@ -304,4 +367,60 @@ public class PropertyBinding implements Binding {
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 e88a8a8b6ad..b3e8023f46d 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,37 +7,22 @@ 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.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
-import junit.framework.Assert;
-
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.springframework.context.i18n.LocaleContextHolder;
-import org.springframework.ui.binding.Binding;
-import org.springframework.ui.binding.BindingResult;
import org.springframework.ui.binding.BindingResults;
-import org.springframework.ui.binding.MissingSourceValuesException;
-import org.springframework.ui.binding.NoSuchBindingException;
-import org.springframework.ui.binding.config.BindingRulesBuilder;
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;
-import org.springframework.ui.format.number.IntegerFormatter;
-import org.springframework.ui.message.MockMessageSource;
-
-import edu.emory.mathcs.backport.java.util.Arrays;
public class GenericBinderTests {
@@ -59,7 +44,6 @@ public class GenericBinderTests {
}
- /*
@Test
public void bindSingleValuesWithDefaultTypeConverterConversion() {
GenericBinder binder = new GenericBinder(bean);
@@ -71,6 +55,7 @@ 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());
@@ -99,9 +84,10 @@ public class GenericBinderTests {
BindingResults results = binder.bind(values);
assertEquals(3, results.size());
assertTrue(results.get(1).isFailure());
- assertEquals("conversionFailed", results.get(1).getAlert().getCode());
+ assertEquals("typeMismatch", results.get(1).getAlert().getCode());
}
+ /*
@Test
public void bindSingleValuePropertyFormatter() throws ParseException {
BindingRulesBuilder builder = new BindingRulesBuilder(TestBean.class);