more passing tests

This commit is contained in:
Keith Donald
2009-07-20 19:07:32 +00:00
parent f39f91701d
commit 2da1bb8607
6 changed files with 193 additions and 96 deletions

View File

@@ -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

View File

@@ -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<Object> {
@SuppressWarnings("unchecked")
class DefaultFormatter implements Formatter {
public static final Formatter<Object> 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 "";

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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());