From 2da1bb860732aea7c597961f821d6e70c8537d46 Mon Sep 17 00:00:00 2001 From: Keith Donald Date: Mon, 20 Jul 2009 19:07:32 +0000 Subject: [PATCH] more passing tests --- .../springframework/ui/binding/Binding.java | 67 +++++----- .../ui/binding/support/DefaultFormatter.java | 41 ++++++- .../ui/binding/support/GenericBinder.java | 6 + .../support/GenericFormatterRegistry.java | 4 +- .../ui/binding/support/PropertyBinding.java | 114 ++++++++++++------ .../binding/support/GenericBinderTests.java | 57 +++++---- 6 files changed, 193 insertions(+), 96 deletions(-) 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 63f76d2e2e1..0e3b697f9a6 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 @@ -29,10 +29,19 @@ public interface Binding { * 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}. + */ + String getRenderValue(); + + /** + * The bound model value. */ Object getValue(); - + + /** + * The bound model value type. + */ + Class getValueType(); + /** * If this Binding is editable. * Used to determine if the user can edit the field value. @@ -63,6 +72,13 @@ public interface Binding { */ void applySourceValue(Object sourceValue); + /** + * If {@link BindingStatus#INVALID_SOURCE_VALUE}, returns the invalid source value. + * Returns null otherwise. + * @return the invalid source value + */ + Object getInvalidSourceValue(); + /** * The current binding status. * Initially {@link BindingStatus#CLEAN clean}. @@ -96,12 +112,29 @@ public interface Binding { * @throws IllegalStateException if BindingStatus is CLEAN or COMMITTED. */ void revert(); - + /** - * Access raw model values. + * For accessing the raw bound model object. + * @author Keith Donald */ - Model getModel(); - + public interface Model { + + /** + * The model value. + */ + Object getValue(); + + /** + * The model value type. + */ + Class getValueType(); + + /** + * Set the model value. + */ + void setValue(Object value); + } + /** * Get a Binding to a nested property value. * @param property the nested property name, such as "foo"; should not be a property path like "foo.bar" @@ -145,28 +178,6 @@ public interface Binding { */ String formatValue(Object potentialModelValue); - /** - * For accessing the raw bound model object. - * @author Keith Donald - */ - public interface Model { - - /** - * The model value. - */ - Object getValue(); - - /** - * The model value type. - */ - Class getValueType(); - - /** - * Set the model value. - */ - void setValue(Object value); - } - /** * Binding states. * @author Keith Donald diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/DefaultFormatter.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/DefaultFormatter.java index e190cce2347..3ace9aba2a2 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/DefaultFormatter.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/DefaultFormatter.java @@ -3,15 +3,52 @@ */ package org.springframework.ui.binding.support; +import java.lang.reflect.Array; import java.text.ParseException; +import java.util.Collection; +import java.util.Iterator; import java.util.Locale; import org.springframework.ui.format.Formatter; -class DefaultFormatter implements Formatter { +@SuppressWarnings("unchecked") +class DefaultFormatter implements Formatter { - public static final Formatter INSTANCE = new DefaultFormatter(); + public static final Formatter INSTANCE = new DefaultFormatter(); + public static final Formatter COLLECTION_FORMATTER = new Formatter() { + public String format(Object object, Locale locale) { + if (object == null) { + return ""; + } else { + StringBuffer buffer = new StringBuffer(); + if (object.getClass().isArray()) { + int length = Array.getLength(object); + for (int i = 0; i < length; i++) { + buffer.append(INSTANCE.format(Array.get(object, i), locale)); + if (i < length - 1) { + buffer.append(","); + } + } + } else if (Collection.class.isAssignableFrom(object.getClass())) { + Collection c = (Collection) object; + for (Iterator it = c.iterator(); it.hasNext();) { + buffer.append(INSTANCE.format(it.next(), locale)); + if (it.hasNext()) { + buffer.append(","); + } + } + } + return buffer.toString(); + } + } + + public Object parse(String formatted, Locale locale) throws ParseException { + throw new UnsupportedOperationException("Not yet implemented"); + } + + }; + public String format(Object object, Locale locale) { if (object == null) { return ""; 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 f38ba70adee..3374ae2a501 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 @@ -300,11 +300,17 @@ public class GenericBinder implements Binder { } public BindingRuleConfiguration formatElementsWith(Formatter formatter) { + if (!List.class.isAssignableFrom(modelClass) || modelClass.isArray()) { + throw new IllegalStateException("Bound property is not a List or an array; cannot set a element formatter"); + } elementFormatter = formatter; return this; } public BindingRuleConfiguration formatKeysWith(Formatter formatter) { + if (!Map.class.isAssignableFrom(modelClass)) { + throw new IllegalStateException("Bound property is not a Map; cannot set a key formatter"); + } keyFormatter = formatter; return this; } diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericFormatterRegistry.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericFormatterRegistry.java index 0074c6c49dc..8c76fb1b357 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericFormatterRegistry.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericFormatterRegistry.java @@ -59,12 +59,12 @@ public class GenericFormatterRegistry implements FormatterRegistry { } Formatter formatter = null; Class type; - if (propertyType.isCollection()) { + if (propertyType.isCollection() || propertyType.isArray()) { formatter = collectionTypeFormatters.get(new GenericCollectionPropertyType(propertyType.getType(), propertyType.getElementType())); if (formatter != null) { return formatter; } else { - type = propertyType.getElementType(); + return DefaultFormatter.COLLECTION_FORMATTER; } } else { type = propertyType.getType(); 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 54070bec213..2350aed32ea 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,11 +20,14 @@ 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.core.style.StylerUtils; import org.springframework.ui.alert.Alert; import org.springframework.ui.alert.Severity; import org.springframework.ui.binding.Binding; import org.springframework.ui.binding.support.GenericBinder.BindingContext; import org.springframework.ui.format.Formatter; +import org.springframework.ui.message.MessageBuilder; +import org.springframework.ui.message.ResolvableArgument; import org.springframework.util.ReflectionUtils; @SuppressWarnings("unchecked") @@ -38,8 +41,7 @@ public class PropertyBinding implements Binding { private Object sourceValue; - @SuppressWarnings("unused") - private ParseException sourceValueParseException; + private Exception invalidSourceValueCause; private ValueBuffer buffer; @@ -49,20 +51,26 @@ public class PropertyBinding implements Binding { this.property = property; this.object = object; this.bindingContext = bindingContext; - this.buffer = new ValueBuffer(getModel()); + buffer = new ValueBuffer(getModel()); status = BindingStatus.CLEAN; } + public String getRenderValue() { + return format(getValue(), bindingContext.getFormatter()); + } + public Object getValue() { - if (status == BindingStatus.INVALID_SOURCE_VALUE) { - return sourceValue; - } else if (status == BindingStatus.DIRTY || status == BindingStatus.COMMIT_FAILURE) { - return formatValue(buffer.getValue()); + if (status == BindingStatus.DIRTY || status == BindingStatus.COMMIT_FAILURE) { + return buffer.getValue(); } else { - return formatValue(getModel().getValue()); + return getModel().getValue(); } } + public Class getValueType() { + return getModel().getValueType(); + } + public boolean isEditable() { return isWriteableProperty() && bindingContext.getEditableCondition().isTrue(); } @@ -80,12 +88,17 @@ public class PropertyBinding implements Binding { assertEnabled(); if (sourceValue instanceof String) { try { - buffer.setValue(bindingContext.getFormatter().parse((String) sourceValue, getLocale())); + Object parsed = bindingContext.getFormatter().parse((String) sourceValue, getLocale()); + buffer.setValue(coerseToValueType(parsed)); sourceValue = null; status = BindingStatus.DIRTY; } catch (ParseException e) { this.sourceValue = sourceValue; - sourceValueParseException = e; + invalidSourceValueCause = e; + status = BindingStatus.INVALID_SOURCE_VALUE; + } catch (ConversionFailedException e) { + this.sourceValue = sourceValue; + invalidSourceValueCause = e; status = BindingStatus.INVALID_SOURCE_VALUE; } } else if (sourceValue instanceof String[]) { @@ -98,24 +111,36 @@ public class PropertyBinding implements Binding { for (int i = 0; i < sourceValues.length; i++) { Object parsedValue; try { - parsedValue = bindingContext.getElementFormatter().parse(sourceValues[i], - LocaleContextHolder.getLocale()); + parsedValue = bindingContext.getElementFormatter().parse(sourceValues[i], getLocale()); Array.set(parsed, i, parsedValue); } catch (ParseException e) { this.sourceValue = sourceValue; - sourceValueParseException = e; + invalidSourceValueCause = e; status = BindingStatus.INVALID_SOURCE_VALUE; break; } } if (status != BindingStatus.INVALID_SOURCE_VALUE) { - buffer.setValue(parsed); + try { + buffer.setValue(coerseToValueType(parsed)); + } catch (ConversionFailedException e) { + this.sourceValue = sourceValue; + invalidSourceValueCause = e; + status = BindingStatus.INVALID_SOURCE_VALUE; + } sourceValue = null; status = BindingStatus.DIRTY; } } } + public Object getInvalidSourceValue() { + if (status != BindingStatus.INVALID_SOURCE_VALUE) { + throw new IllegalStateException("No invalid source value"); + } + return sourceValue; + } + public BindingStatus getStatus() { return status; } @@ -128,7 +153,25 @@ public class PropertyBinding implements Binding { } public String getMessage() { - return "Could not parse source value"; + MessageBuilder builder = new MessageBuilder(bindingContext.getMessageSource()); + builder.code(getCode()); + if (invalidSourceValueCause instanceof ParseException) { + ParseException e = (ParseException) invalidSourceValueCause; + builder.arg("label", new ResolvableArgument(property.getName())); + builder.arg("value", sourceValue); + builder.arg("errorOffset", e.getErrorOffset()); + builder.defaultMessage("Failed to bind to property '" + property + "'; the user value " + + StylerUtils.style(sourceValue) + " has an invalid format and could no be parsed"); + } else { + ConversionFailedException e = (ConversionFailedException) invalidSourceValueCause; + builder.arg("label", new ResolvableArgument(property.getName())); + builder.arg("value", sourceValue); + builder.defaultMessage("Failed to bind to property '" + property + "'; the user value " + + StylerUtils.style(sourceValue) + " has could not be converted to " + + e.getTargetType().getName()); + + } + return builder.build(); } public Severity getSeverity() { @@ -136,35 +179,19 @@ public class PropertyBinding implements Binding { } }; } else if (status == BindingStatus.COMMIT_FAILURE) { - if (buffer.getFlushException() instanceof ConversionFailedException) { - return new AbstractAlert() { - public String getCode() { - return "typeMismatch"; - } - - public String getMessage() { - return "Could not convert source value"; - } - - public Severity getSeverity() { - return Severity.ERROR; - } - }; - } else { return new AbstractAlert() { public String getCode() { return "internalError"; } public String getMessage() { - return "Internal error occurred " + buffer.getFlushException(); + return "Internal error occurred; message = [" + buffer.getFlushException().getMessage() + "]"; } public Severity getSeverity() { return Severity.FATAL; } - }; - } + }; } else if (status == BindingStatus.COMMITTED) { return new AbstractAlert() { public String getCode() { @@ -202,7 +229,7 @@ public class PropertyBinding implements Binding { public void revert() { if (status == BindingStatus.INVALID_SOURCE_VALUE) { sourceValue = null; - sourceValueParseException = null; + invalidSourceValueCause = null; status = BindingStatus.CLEAN; } else if (status == BindingStatus.DIRTY || status == BindingStatus.COMMIT_FAILURE) { buffer.clear(); @@ -223,11 +250,6 @@ public class PropertyBinding implements Binding { } public void setValue(Object value) { - TypeDescriptor targetType = new TypeDescriptor(new MethodParameter(property.getWriteMethod(), 0)); - TypeConverter converter = bindingContext.getTypeConverter(); - if (value != null && converter.canConvert(value.getClass(), targetType)) { - value = converter.convert(value, targetType); - } ReflectionUtils.invokeMethod(property.getWriteMethod(), object, value); } }; @@ -275,6 +297,10 @@ public class PropertyBinding implements Binding { } else { formatter = bindingContext.getFormatter(); } + return format(value, formatter); + } + + private String format(Object value, Formatter formatter) { Class formattedType = getFormattedObjectType(formatter.getClass()); value = bindingContext.getTypeConverter().convert(value, formattedType); return formatter.format(value, getLocale()); @@ -327,6 +353,16 @@ public class PropertyBinding implements Binding { return null; } + private Object coerseToValueType(Object parsed) { + TypeDescriptor targetType = new TypeDescriptor(new MethodParameter(property.getWriteMethod(), 0)); + TypeConverter converter = bindingContext.getTypeConverter(); + if (parsed != null && converter.canConvert(parsed.getClass(), targetType)) { + return converter.convert(parsed, targetType); + } else { + return parsed; + } + } + @SuppressWarnings("unused") private CollectionTypeDescriptor getCollectionTypeDescriptor() { Class elementType = GenericCollectionTypeResolver.getCollectionReturnType(property.getReadMethod()); 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 977e0cfb571..ee026ca0385 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,7 +7,6 @@ import static org.junit.Assert.assertTrue; import java.math.BigDecimal; import java.text.ParseException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.LinkedHashMap; @@ -17,13 +16,12 @@ import java.util.Map; 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.Binding.BindingStatus; import org.springframework.ui.format.AnnotationFormatterFactory; import org.springframework.ui.format.Formatted; import org.springframework.ui.format.Formatter; @@ -32,7 +30,6 @@ 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 org.springframework.util.Assert; public class GenericBinderTests { @@ -154,56 +151,66 @@ public class GenericBinderTests { @Test public void getBindingCustomFormatter() { + binder.bindingRule("currency").formatWith(new CurrencyFormatter()); Binding b = binder.getBinding("currency"); assertFalse(b.isList()); assertFalse(b.isMap()); - assertEquals("", b.getValue()); + assertEquals(null, b.getValue()); + assertEquals("", b.getRenderValue()); b.applySourceValue("$23.56"); - assertEquals("$23.56", b.getValue()); + assertEquals(BindingStatus.DIRTY, b.getStatus()); + assertEquals(new BigDecimal("23.56"), b.getValue()); + assertEquals("$23.56", b.getRenderValue()); b.commit(); - assertEquals("$23.56", b.getValue()); + assertEquals(new BigDecimal("23.56"), b.getValue()); + assertEquals("$23.56", b.getRenderValue()); + assertEquals(BindingStatus.COMMITTED, b.getStatus()); } - /* @Test public void getBindingCustomFormatterRequiringTypeCoersion() { // IntegerFormatter formats Longs, so conversion from Integer -> Long is performed - binder.addBinding("integer").formatWith(new IntegerFormatter()); + binder.bindingRule("integer").formatWith(new IntegerFormatter()); Binding b = binder.getBinding("integer"); - b.setValue("2,300"); - assertEquals("2,300", b.getValue()); + b.applySourceValue("2,300"); + assertEquals("2,300", b.getRenderValue()); + b.commit(); + assertEquals(BindingStatus.COMMITTED, b.getStatus()); + assertEquals("2,300", b.getRenderValue()); } @Test public void invalidFormatBindingResultCustomAlertMessage() { MockMessageSource messages = new MockMessageSource(); - messages.addMessage("invalidFormat", Locale.US, + messages.addMessage("typeMismatch", Locale.US, "Please enter an integer in format ### for the #{label} field; you entered #{value}"); binder.setMessageSource(messages); - binder.addBinding("integer").formatWith(new IntegerFormatter()); + binder.bindingRule("integer").formatWith(new IntegerFormatter()); Binding b = binder.getBinding("integer"); - BindingResult result = b.setValue("bogus"); - assertEquals("Please enter an integer in format ### for the integer field; you entered bogus", result - .getAlert().getMessage()); + b.applySourceValue("bogus"); + assertEquals("Please enter an integer in format ### for the integer field; you entered bogus", b.getStatusAlert().getMessage()); } @Test public void getBindingMultiValued() { - binder.addBinding("foos"); Binding b = binder.getBinding("foos"); - assertTrue(b.isIndexable()); - assertEquals(0, b.getCollectionValues().length); - b.setValue(new String[] { "BAR", "BAZ", "BOOP" }); + assertTrue(b.isList()); + assertEquals(null, b.getValue()); + assertEquals("", b.getRenderValue()); + b.applySourceValue(new String[] { "BAR", "BAZ", "BOOP" }); + b.commit(); assertEquals(FooEnum.BAR, bean.getFoos().get(0)); assertEquals(FooEnum.BAZ, bean.getFoos().get(1)); assertEquals(FooEnum.BOOP, bean.getFoos().get(2)); - String[] values = b.getCollectionValues(); - assertEquals(3, values.length); - assertEquals("BAR", values[0]); - assertEquals("BAZ", values[1]); - assertEquals("BOOP", values[2]); + String asString = b.getRenderValue(); + assertEquals("BAR,BAZ,BOOP", asString); + List value = (List) b.getValue(); + assertEquals(FooEnum.BAR, value.get(0)); + assertEquals(FooEnum.BAZ, value.get(1)); + assertEquals(FooEnum.BOOP, value.get(2)); } + /* @Test public void getBindingMultiValuedIndexAccess() { binder.addBinding("foos");