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 51d2d803a10..45b93d62e3a 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 @@ -15,7 +15,6 @@ */ package org.springframework.ui.binding; -import org.springframework.core.convert.TypeDescriptor; import org.springframework.ui.alert.Alert; import org.springframework.ui.alert.Severity; @@ -114,33 +113,6 @@ public interface Binding { */ void revert(); - /** - * For accessing the raw bound model object. - * @author Keith Donald - */ - public interface Model { - - /** - * The model value. - */ - Object getValue(); - - /** - * The model value type. - */ - Class getValueType(); - - /** - * The model value type descriptor. - */ - TypeDescriptor getValueTypeDescriptor(); - - /** - * 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" diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/AbstractBinding.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/AbstractBinding.java new file mode 100644 index 00000000000..4c63d63f766 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/AbstractBinding.java @@ -0,0 +1,375 @@ +package org.springframework.ui.binding.support; + +import java.lang.reflect.Array; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.text.ParseException; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.core.GenericTypeResolver; +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; + +public abstract class AbstractBinding implements Binding { + + private BindingContext bindingContext; + + private ValueBuffer buffer; + + private BindingStatus status; + + private Object sourceValue; + + private Exception invalidSourceValueCause; + + public AbstractBinding(BindingContext bindingContext) { + this.bindingContext = bindingContext; + buffer = new ValueBuffer(getValueModel()); + status = BindingStatus.CLEAN; + } + + // implementing Binding + + public String getRenderValue() { + return format(getValue(), bindingContext.getFormatter()); + } + + public Object getValue() { + if (status == BindingStatus.DIRTY || status == BindingStatus.COMMIT_FAILURE) { + return buffer.getValue(); + } else { + return getValueModel().getValue(); + } + } + + public Class getValueType() { + return getValueModel().getValueType(); + } + + public boolean isEditable() { + return bindingContext.getEditableCondition().isTrue(); + } + + public boolean isEnabled() { + return bindingContext.getEnabledCondition().isTrue(); + } + + public boolean isVisible() { + return bindingContext.getVisibleCondition().isTrue(); + } + + public void applySourceValue(Object sourceValue) { + assertEditable(); + assertEnabled(); + if (sourceValue instanceof String) { + try { + Object parsed = bindingContext.getFormatter().parse((String) sourceValue, getLocale()); + buffer.setValue(coerseToValueType(parsed)); + sourceValue = null; + status = BindingStatus.DIRTY; + } catch (ParseException e) { + this.sourceValue = sourceValue; + 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[]) { + String[] sourceValues = (String[]) sourceValue; + Class parsedType = getFormattedObjectType(bindingContext.getElementFormatter().getClass()); + if (parsedType == null) { + parsedType = String.class; + } + Object parsed = Array.newInstance(parsedType, sourceValues.length); + for (int i = 0; i < sourceValues.length; i++) { + Object parsedValue; + try { + parsedValue = bindingContext.getElementFormatter().parse(sourceValues[i], getLocale()); + Array.set(parsed, i, parsedValue); + } catch (ParseException e) { + this.sourceValue = sourceValue; + invalidSourceValueCause = e; + status = BindingStatus.INVALID_SOURCE_VALUE; + break; + } + } + if (status != BindingStatus.INVALID_SOURCE_VALUE) { + try { + buffer.setValue(coerseToValueType(parsed)); + sourceValue = null; + status = BindingStatus.DIRTY; + } catch (ConversionFailedException e) { + this.sourceValue = sourceValue; + invalidSourceValueCause = e; + status = BindingStatus.INVALID_SOURCE_VALUE; + } + } + } else { + try { + buffer.setValue(coerseToValueType(sourceValue)); + sourceValue = null; + status = BindingStatus.DIRTY; + } catch (ConversionFailedException e) { + this.sourceValue = sourceValue; + invalidSourceValueCause = e; + status = BindingStatus.INVALID_SOURCE_VALUE; + } + } + } + + public Object getInvalidSourceValue() { + if (status != BindingStatus.INVALID_SOURCE_VALUE) { + throw new IllegalStateException("No invalid source value"); + } + return sourceValue; + } + + public BindingStatus getStatus() { + return status; + } + + public Alert getStatusAlert() { + if (status == BindingStatus.INVALID_SOURCE_VALUE) { + return new AbstractAlert() { + public String getCode() { + return "typeMismatch"; + } + + public String getMessage() { + MessageBuilder builder = new MessageBuilder(bindingContext.getMessageSource()); + builder.code(getCode()); + if (invalidSourceValueCause instanceof ParseException) { + ParseException e = (ParseException) invalidSourceValueCause; + builder.arg("label", bindingContext.getLabel()); + builder.arg("value", sourceValue); + builder.arg("errorOffset", e.getErrorOffset()); + builder.defaultMessage("Failed to bind '" + bindingContext.getLabel() + "'; the source value " + + StylerUtils.style(sourceValue) + " has an invalid format and could no be parsed"); + } else { + ConversionFailedException e = (ConversionFailedException) invalidSourceValueCause; + builder.arg("label", new ResolvableArgument(bindingContext.getLabel())); + builder.arg("value", sourceValue); + builder.defaultMessage("Failed to bind '" + bindingContext.getLabel() + "'; the source value " + + StylerUtils.style(sourceValue) + " has could not be converted to " + + e.getTargetType().getName()); + + } + return builder.build(); + } + + public Severity getSeverity() { + return Severity.ERROR; + } + }; + } else if (status == BindingStatus.COMMIT_FAILURE) { + return new AbstractAlert() { + public String getCode() { + return "internalError"; + } + + public String getMessage() { + buffer.getFlushException().printStackTrace(); + 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() { + return "bindSuccess"; + } + + public String getMessage() { + return "Binding successful"; + } + + public Severity getSeverity() { + return Severity.INFO; + } + }; + } else { + return null; + } + } + + public void commit() { + assertEditable(); + assertEnabled(); + if (status == BindingStatus.DIRTY) { + buffer.flush(); + if (buffer.flushFailed()) { + status = BindingStatus.COMMIT_FAILURE; + } else { + status = BindingStatus.COMMITTED; + } + } else { + throw new IllegalStateException("Binding is not dirty; nothing to commit"); + } + } + + public void revert() { + if (status == BindingStatus.INVALID_SOURCE_VALUE) { + sourceValue = null; + invalidSourceValueCause = 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"); + } + } + + public Binding getBinding(String property) { + return bindingContext.getBinding(property); + } + + public boolean isList() { + return List.class.isAssignableFrom(getValueType()); + } + + public Binding getListElementBinding(int index) { + return bindingContext.getListElementBinding(index); + } + + public boolean isMap() { + return Map.class.isAssignableFrom(getValueType()); + } + + public Binding getMapValueBinding(Object key) { + return bindingContext.getMapValueBinding(key); + } + + @SuppressWarnings("unchecked") + public String formatValue(Object value) { + Formatter formatter; + if (isList() || isMap()) { + formatter = getBindingContext().getElementFormatter(); + } else { + formatter = getBindingContext().getFormatter(); + } + return format(value, formatter); + } + + // subclassing hooks + + protected BindingContext getBindingContext() { + return bindingContext; + } + + protected abstract ValueModel getValueModel(); + + protected Locale getLocale() { + return LocaleContextHolder.getLocale(); + } + + protected String format(Object value, Formatter formatter) { + Class formattedType = getFormattedObjectType(formatter.getClass()); + value = bindingContext.getTypeConverter().convert(value, formattedType); + return formatter.format(value, getLocale()); + } + + private Class getFormattedObjectType(Class formatterClass) { + Class classToIntrospect = formatterClass; + while (classToIntrospect != null) { + Type[] ifcs = classToIntrospect.getGenericInterfaces(); + for (Type ifc : ifcs) { + if (ifc instanceof ParameterizedType) { + ParameterizedType paramIfc = (ParameterizedType) ifc; + Type rawType = paramIfc.getRawType(); + if (Formatter.class.equals(rawType)) { + Type arg = paramIfc.getActualTypeArguments()[0]; + if (arg instanceof TypeVariable) { + arg = GenericTypeResolver.resolveTypeVariable((TypeVariable) arg, formatterClass); + } + if (arg instanceof Class) { + return (Class) arg; + } + } else if (Formatter.class.isAssignableFrom((Class) rawType)) { + return getFormattedObjectType((Class) rawType); + } + } else if (Formatter.class.isAssignableFrom((Class) ifc)) { + return getFormattedObjectType((Class) ifc); + } + } + classToIntrospect = classToIntrospect.getSuperclass(); + } + return null; + } + + private Object coerseToValueType(Object parsed) { + TypeDescriptor targetType = getValueModel().getValueTypeDescriptor(); + TypeConverter converter = bindingContext.getTypeConverter(); + if (parsed != null && converter.canConvert(parsed.getClass(), targetType)) { + return converter.convert(parsed, targetType); + } else { + return parsed; + } + } + + private void assertEditable() { + if (!isEditable()) { + throw new IllegalStateException("Binding is not editable"); + } + } + + private void assertEnabled() { + if (!isEditable()) { + throw new IllegalStateException("Binding is not enabled"); + } + } + + // internal helpers + + static abstract class AbstractAlert implements Alert { + public String toString() { + return getCode() + " - " + getMessage(); + } + } + + /** + * For accessing the raw bound model object. + * @author Keith Donald + */ + public interface ValueModel { + + /** + * The model value. + */ + Object getValue(); + + /** + * The model value type. + */ + Class getValueType(); + + /** + * The model value type descriptor. + */ + TypeDescriptor getValueTypeDescriptor(); + + /** + * Set the model value. + */ + void setValue(Object value); + } + +} 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 b0f24a9be0b..235dca54d73 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 @@ -52,15 +52,15 @@ public class GenericBinder implements Binder { private Object model; private Map bindingRules; - + private FormatterRegistry formatterRegistry; - + private TypeConverter typeConverter; private MessageSource messageSource; private String[] requiredProperties = new String[0]; - + /** * Creates a new binder for the model object. * @param model the model object containing properties this binder will bind to @@ -82,7 +82,7 @@ public class GenericBinder implements Binder { Assert.notNull(formatterRegistry, "The FormatterRegistry is required"); this.formatterRegistry = formatterRegistry; } - + /** * Configure the MessageSource that resolves localized {@link BindingResult} alert messages. * @param messageSource the message source @@ -116,7 +116,7 @@ public class GenericBinder implements Binder { } return rule; } - + // implementing Binder public Object getModel() { @@ -126,6 +126,7 @@ public class GenericBinder implements Binder { public Binding getBinding(String property) { PropertyPath path = new PropertyPath(property); Binding binding = getBindingRule(path.getFirstElement().getValue()).getBinding(model); + System.out.println(path); for (PropertyPathElement element : path.getNestedElements()) { if (element.isIndex()) { if (binding.isMap()) { @@ -158,7 +159,7 @@ public class GenericBinder implements Binder { } // subclassing hooks - + /** * 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. @@ -190,7 +191,7 @@ public class GenericBinder implements Binder { throw new MissingSourceValuesException(missingRequired, sourceValues); } } - + private GenericBindingRule getBindingRule(String property) { GenericBindingRule rule = bindingRules.get(property); if (rule == null) { @@ -199,7 +200,7 @@ public class GenericBinder implements Binder { } return rule; } - + private BindingResult bind(Map.Entry sourceValue, Binding binding) { String property = sourceValue.getKey(); Object value = sourceValue.getValue(); @@ -218,41 +219,42 @@ public class GenericBinder implements Binder { class GenericBindingRule implements BindingRuleConfiguration, BindingContext { private Class modelClass; - + private PropertyDescriptor property; - + private Formatter formatter; - + private Formatter elementFormatter; - + private Formatter keyFormatter; - + private Condition editableCondition = Condition.ALWAYS_TRUE; - + private Condition enabledCondition = Condition.ALWAYS_TRUE; - - private Condition visibleCondition = Condition.ALWAYS_TRUE; - + + private Condition visibleCondition = Condition.ALWAYS_TRUE; + private Map nestedBindingRules; - + private Binding binding; - + + private Map listElementBindings; + public GenericBindingRule(String property, Class modelClass) { this.modelClass = modelClass; this.property = findPropertyDescriptor(property); - nestedBindingRules = new HashMap(); } - + // implementing BindingContext public MessageSource getMessageSource() { return messageSource; } - + public TypeConverter getTypeConverter() { return typeConverter; - } - + } + public Formatter getFormatter() { if (formatter != null) { return formatter; @@ -288,9 +290,82 @@ public class GenericBinder implements Binder { public Condition getVisibleCondition() { return visibleCondition; } - - public Binding getBinding(String property, Object model) { - return getBindingRule(property, model.getClass()).getBinding(model); + + public Binding getBinding(String property) { + createValueIfNecessary(); + return getBindingRule(property, binding.getValueType()).getBinding(binding.getValue()); + } + + public Binding getListElementBinding(final int index) { + if (listElementBindings == null) { + listElementBindings = new HashMap(); + } + growListIfNecessary(index); + Binding binding = listElementBindings.get(index); + if (binding == null) { + BindingContext listContext = new BindingContext() { + public MessageSource getMessageSource() { + return GenericBindingRule.this.getMessageSource(); + } + + public TypeConverter getTypeConverter() { + return GenericBindingRule.this.getTypeConverter(); + } + + public Binding getBinding(String property) { + Object model = ((List) GenericBindingRule.this.binding.getValue()).get(index); + return GenericBindingRule.this.getBindingRule(property, getElementType()).getBinding(model); + } + + public Formatter getFormatter() { + return GenericBindingRule.this.getElementFormatter(); + } + + public Formatter getElementFormatter() { + return null; + } + + public Formatter getKeyFormatter() { + return null; + } + + public Condition getEditableCondition() { + return GenericBindingRule.this.getEditableCondition(); + } + + public Condition getEnabledCondition() { + return GenericBindingRule.this.getEnabledCondition(); + } + + public Condition getVisibleCondition() { + return GenericBindingRule.this.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() { + return GenericBindingRule.this.getLabel() + "[" + index + "]"; + } + + }; + binding = new ListElementBinding(index, getElementType(), (List) this.binding.getValue(), listContext); + listElementBindings.put(index, binding); + } + return binding; + } + + public Binding getMapValueBinding(Object key) { + return null; + } + + public String getLabel() { + return property.getName(); } // implementing BindingRuleConfiguration @@ -302,7 +377,8 @@ 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"); + throw new IllegalStateException( + "Bound property is not a List or an array; cannot set a element formatter"); } elementFormatter = formatter; return this; @@ -330,7 +406,7 @@ public class GenericBinder implements Binder { visibleCondition = condition; return this; } - + // internal helpers private Class getElementType() { @@ -344,8 +420,11 @@ public class GenericBinder implements Binder { GenericBindingRule getBindingRule(String property) { return getBindingRule(property, this.property.getPropertyType()); } - - GenericBindingRule getBindingRule(String property, Class modelClass) { + + GenericBindingRule getBindingRule(String property, Class modelClass) { + if (nestedBindingRules == null) { + nestedBindingRules = new HashMap(); + } GenericBindingRule rule = nestedBindingRules.get(property); if (rule == null) { rule = new GenericBindingRule(property, modelClass); @@ -353,12 +432,14 @@ public class GenericBinder implements Binder { } return rule; } - + + // internal helpers + Binding getBinding(Object model) { if (binding == null) { binding = new PropertyBinding(property, model, this); } - return binding; + return binding; } private PropertyDescriptor findPropertyDescriptor(String property) { @@ -378,12 +459,55 @@ public class GenericBinder implements Binder { throw new IllegalStateException("Unable to introspect model type " + clazz); } } + + private void createValueIfNecessary() { + Object value = binding.getValue(); + if (value == null) { + value = newValue(binding.getValueType()); + binding.applySourceValue(value); + binding.commit(); + } + } + + private void growListIfNecessary(int index) { + List list = (List) binding.getValue(); + if (list == null) { + list = newListValue(binding.getValueType()); + binding.applySourceValue(list); + binding.commit(); + list = (List) binding.getValue(); + } + if (index >= list.size()) { + for (int i = list.size(); i <= index; i++) { + list.add(newValue(getElementType())); + } + } + } + + private List newListValue(Class type) { + if (type.isInterface()) { + return (List) newValue(ArrayList.class); + } else { + return (List) newValue(type); + } + } + + private Object newValue(Class type) { + try { + return type.newInstance(); + } catch (InstantiationException e) { + throw new IllegalStateException("Could not instantiate element of type [" + type.getName() + "]", e); + } catch (IllegalAccessException e) { + throw new IllegalStateException("Could not instantiate element of type [" + type.getName() + "]", e); + } + } + } - + public interface BindingContext { - + MessageSource getMessageSource(); - + TypeConverter getTypeConverter(); Condition getEditableCondition(); @@ -392,17 +516,20 @@ public class GenericBinder implements Binder { Condition getVisibleCondition(); - Binding getBinding(String property, Object model); + Binding getBinding(String property); - @SuppressWarnings("unchecked") Formatter getFormatter(); - @SuppressWarnings("unchecked") Formatter getElementFormatter(); - @SuppressWarnings("unchecked") Formatter getKeyFormatter(); + Binding getListElementBinding(int index); + + Binding getMapValueBinding(Object key); + + String getLabel(); + } public void setRequired(String[] propertyPaths) { diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/ListElementBinding.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/ListElementBinding.java new file mode 100644 index 00000000000..ab55dc6238e --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/ListElementBinding.java @@ -0,0 +1,51 @@ +package org.springframework.ui.binding.support; + +import java.util.List; + +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.ui.binding.support.GenericBinder.BindingContext; + +public class ListElementBinding extends AbstractBinding { + + private List list; + + private int index; + + private Class elementType; + + private BindingContext bindingContext; + + public ListElementBinding(int index, Class elementType, List list, BindingContext bindingContext) { + super(bindingContext); + this.index = index; + this.elementType = elementType; + this.list = list; + this.bindingContext = bindingContext; + } + + @Override + protected ValueModel getValueModel() { + return new ValueModel() { + public Object getValue() { + return list.get(index); + } + + public Class getValueType() { + if (elementType != null) { + return elementType; + } else { + return getValue().getClass(); + } + } + + public TypeDescriptor getValueTypeDescriptor() { + return TypeDescriptor.valueOf(getValueType()); + } + + public void setValue(Object value) { + list.set(index, value); + } + }; + } + +} \ No newline at end of file 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 0e85b41c1ac..0e6aafbbb1d 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 @@ -1,263 +1,36 @@ package org.springframework.ui.binding.support; import java.beans.PropertyDescriptor; -import java.lang.reflect.Array; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -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.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") -public class PropertyBinding implements Binding { +public class PropertyBinding extends AbstractBinding implements Binding { private PropertyDescriptor property; private Object object; - private BindingContext bindingContext; - - private Object sourceValue; - - private Exception invalidSourceValueCause; - - private ValueBuffer buffer; - - private BindingStatus status; - - private Map listElementBindings; - - private Class elementType; - public PropertyBinding(PropertyDescriptor property, Object object, BindingContext bindingContext) { + super(bindingContext); this.property = property; this.object = object; - this.bindingContext = bindingContext; - buffer = new ValueBuffer(getModel()); - status = BindingStatus.CLEAN; - if (isList()) { - listElementBindings = new HashMap(); - elementType = GenericCollectionTypeResolver.getCollectionReturnType(property.getReadMethod()); - } - } - - public String getRenderValue() { - return format(getValue(), getFormatter()); - } - - public Object getValue() { - if (status == BindingStatus.DIRTY || status == BindingStatus.COMMIT_FAILURE) { - return buffer.getValue(); - } else { - return getModel().getValue(); - } - } - - public Class getValueType() { - return getModel().getValueType(); } + // Binding overrides + + @Override public boolean isEditable() { - return isWriteableProperty() && bindingContext.getEditableCondition().isTrue(); + return isWriteable() && super.isEditable(); } - public boolean isEnabled() { - return bindingContext.getEnabledCondition().isTrue(); - } - - public boolean isVisible() { - return bindingContext.getVisibleCondition().isTrue(); - } - - public void applySourceValue(Object sourceValue) { - assertEditable(); - assertEnabled(); - if (sourceValue instanceof String) { - try { - Object parsed = getFormatter().parse((String) sourceValue, getLocale()); - buffer.setValue(coerseToValueType(parsed)); - sourceValue = null; - status = BindingStatus.DIRTY; - } catch (ParseException e) { - this.sourceValue = sourceValue; - 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[]) { - String[] sourceValues = (String[]) sourceValue; - Class parsedType = getFormattedObjectType(bindingContext.getElementFormatter().getClass()); - if (parsedType == null) { - parsedType = String.class; - } - Object parsed = Array.newInstance(parsedType, sourceValues.length); - for (int i = 0; i < sourceValues.length; i++) { - Object parsedValue; - try { - parsedValue = bindingContext.getElementFormatter().parse(sourceValues[i], getLocale()); - Array.set(parsed, i, parsedValue); - } catch (ParseException e) { - this.sourceValue = sourceValue; - invalidSourceValueCause = e; - status = BindingStatus.INVALID_SOURCE_VALUE; - break; - } - } - if (status != BindingStatus.INVALID_SOURCE_VALUE) { - try { - buffer.setValue(coerseToValueType(parsed)); - sourceValue = null; - status = BindingStatus.DIRTY; - } catch (ConversionFailedException e) { - this.sourceValue = sourceValue; - invalidSourceValueCause = e; - status = BindingStatus.INVALID_SOURCE_VALUE; - } - } - } else { - try { - buffer.setValue(coerseToValueType(sourceValue)); - sourceValue = null; - status = BindingStatus.DIRTY; - } catch (ConversionFailedException e) { - this.sourceValue = sourceValue; - invalidSourceValueCause = e; - status = BindingStatus.INVALID_SOURCE_VALUE; - } - } - } - - public Object getInvalidSourceValue() { - if (status != BindingStatus.INVALID_SOURCE_VALUE) { - throw new IllegalStateException("No invalid source value"); - } - return sourceValue; - } - - public BindingStatus getStatus() { - return status; - } - - public Alert getStatusAlert() { - if (status == BindingStatus.INVALID_SOURCE_VALUE) { - return new AbstractAlert() { - public String getCode() { - return "typeMismatch"; - } - - public String getMessage() { - 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() { - return Severity.ERROR; - } - }; - } else if (status == BindingStatus.COMMIT_FAILURE) { - return new AbstractAlert() { - public String getCode() { - return "internalError"; - } - - public String getMessage() { - 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() { - return "bindSucces"; - } - - public String getMessage() { - return "Binding successful"; - } - - public Severity getSeverity() { - return Severity.INFO; - } - }; - } else { - return null; - } - } - - public void commit() { - assertEditable(); - assertEnabled(); - if (status == BindingStatus.DIRTY) { - buffer.flush(); - if (buffer.flushFailed()) { - status = BindingStatus.COMMIT_FAILURE; - } else { - status = BindingStatus.COMMITTED; - } - } else { - throw new IllegalStateException("Binding is not dirty; nothing to commit"); - } - } - - public void revert() { - if (status == BindingStatus.INVALID_SOURCE_VALUE) { - sourceValue = null; - invalidSourceValueCause = 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"); - } - } - - public Model getModel() { - return new Model() { + // implementing + + protected ValueModel getValueModel() { + return new ValueModel() { public Object getValue() { return ReflectionUtils.invokeMethod(property.getReadMethod(), object); } @@ -266,6 +39,7 @@ public class PropertyBinding implements Binding { return property.getPropertyType(); } + @SuppressWarnings("unchecked") public TypeDescriptor getValueTypeDescriptor() { return new TypeDescriptor(new MethodParameter(property.getReadMethod(), -1)); } @@ -275,232 +49,10 @@ public class PropertyBinding implements Binding { } }; } - - public Binding getBinding(String property) { - assertScalarProperty(); - if (getValue() == null) { - getModel().setValue(newValue(getValueType())); - } - return bindingContext.getBinding(property, getValue()); - } - - public boolean isList() { - return List.class.isAssignableFrom(getValueType()); - } - - public Binding getListElementBinding(int index) { - assertListProperty(); - if (index < 0) { - throw new IllegalArgumentException("Invalid index " + index); - } - growListIfNecessary(index); - ListElementBinding binding = listElementBindings.get(index); - if (binding == null) { - binding = new ListElementBinding(index); - listElementBindings.put(index, binding); - } - return binding; - } - - public boolean isMap() { - return Map.class.isAssignableFrom(getValueType()); - } - - public Binding getMapValueBinding(Object key) { - assertMapProperty(); - if (key instanceof String) { - try { - key = bindingContext.getKeyFormatter().parse((String) key, getLocale()); - } catch (ParseException e) { - throw new IllegalArgumentException("Invald key", e); - } - } - //TODO return new KeyedPropertyBinding(key, (Map) getValue(), getMapTypeDescriptor()); - return null; - } - - public String formatValue(Object value) { - Formatter formatter; - if (isList() || isMap()) { - formatter = bindingContext.getElementFormatter(); - } else { - formatter = bindingContext.getFormatter(); - } - return format(value, formatter); - } - - // subclassing hooks - - protected Formatter getFormatter() { - return bindingContext.getFormatter(); - } - + // internal helpers - private String format(Object value, Formatter formatter) { - Class formattedType = getFormattedObjectType(formatter.getClass()); - value = bindingContext.getTypeConverter().convert(value, formattedType); - return formatter.format(value, getLocale()); - } - - private Locale getLocale() { - return LocaleContextHolder.getLocale(); - } - - private Class getFormattedObjectType(Class formatterClass) { - Class classToIntrospect = formatterClass; - while (classToIntrospect != null) { - Type[] ifcs = classToIntrospect.getGenericInterfaces(); - for (Type ifc : ifcs) { - if (ifc instanceof ParameterizedType) { - ParameterizedType paramIfc = (ParameterizedType) ifc; - Type rawType = paramIfc.getRawType(); - if (Formatter.class.equals(rawType)) { - Type arg = paramIfc.getActualTypeArguments()[0]; - if (arg instanceof TypeVariable) { - arg = GenericTypeResolver.resolveTypeVariable((TypeVariable) arg, formatterClass); - } - if (arg instanceof Class) { - return (Class) arg; - } - } else if (Formatter.class.isAssignableFrom((Class) rawType)) { - return getFormattedObjectType((Class) rawType); - } - } else if (Formatter.class.isAssignableFrom((Class) ifc)) { - return getFormattedObjectType((Class) ifc); - } - } - classToIntrospect = classToIntrospect.getSuperclass(); - } - return null; - } - - private Object coerseToValueType(Object parsed) { - TypeDescriptor targetType = getModel().getValueTypeDescriptor(); - TypeConverter converter = bindingContext.getTypeConverter(); - if (parsed != null && converter.canConvert(parsed.getClass(), targetType)) { - return converter.convert(parsed, targetType); - } else { - return parsed; - } - } - - private void assertScalarProperty() { - if (isList()) { - throw new IllegalArgumentException("Is a Collection but should be a scalar"); - } - if (isMap()) { - throw new IllegalArgumentException("Is a Map but should be a scalar"); - } - } - - private void assertListProperty() { - if (!isList()) { - throw new IllegalStateException("Not a List property binding"); - } - } - - private void assertMapProperty() { - 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 isWriteableProperty() { + private boolean isWriteable() { return property.getWriteMethod() != null; } - - private void growListIfNecessary(int index) { - List list = (List) getValue(); - if (list == null) { - getModel().setValue(newListValue(getValueType())); - list = (List) getValue(); - } - if (index >= list.size()) { - for (int i = list.size(); i <= index; i++) { - list.add(newValue(elementType)); - } - } - } - - private Object newListValue(Class type) { - if (type.isInterface()) { - return newValue(ArrayList.class); - } else { - return newValue(type); - } - } - - private Object newValue(Class type) { - try { - return type.newInstance(); - } catch (InstantiationException e) { - throw new IllegalStateException("Could not instantiate element of type [" + type.getName() + "]", e); - } catch (IllegalAccessException e) { - throw new IllegalStateException("Could not instantiate element of type [" + type.getName() + "]", e); - } - } - - static abstract class AbstractAlert implements Alert { - public String toString() { - return getCode() + " - " + getMessage(); - } - } - - class ListElementBinding extends PropertyBinding { - - private int index; - - public ListElementBinding(int index) { - super(property, object, bindingContext); - this.index = index; - } - - protected Formatter getFormatter() { - return bindingContext.getElementFormatter(); - } - - public Model getModel() { - return new Model() { - public Object getValue() { - return getList().get(index); - } - - public Class getValueType() { - if (elementType != null) { - return elementType; - } else { - return getValue().getClass(); - } - } - - public TypeDescriptor getValueTypeDescriptor() { - return TypeDescriptor.valueOf(getValueType()); - } - - public void setValue(Object value) { - getList().set(index, value); - } - }; - } - - // internal helpers - - private List getList() { - return (List) PropertyBinding.this.getValue(); - } - } - } \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/PropertyPath.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/PropertyPath.java index d34f3fa1016..5528a48bdd9 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/PropertyPath.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/PropertyPath.java @@ -46,5 +46,9 @@ public class PropertyPath implements Iterable { public Iterator iterator() { return elements.iterator(); } + + public String toString() { + return elements.toString(); + } } \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/PropertyPathElement.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/PropertyPathElement.java index 777106e7b45..9c6ec0d7988 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/PropertyPathElement.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/PropertyPathElement.java @@ -25,4 +25,8 @@ public class PropertyPathElement { public int getIntValue() { return Integer.parseInt(value); } + + public String toString() { + return value + ";index=" + index; + } } \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/ValueBuffer.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/ValueBuffer.java index 7faefe3555a..9f1d1d79e81 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/ValueBuffer.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/ValueBuffer.java @@ -3,7 +3,7 @@ */ package org.springframework.ui.binding.support; -import org.springframework.ui.binding.Binding.Model; +import org.springframework.ui.binding.support.AbstractBinding.ValueModel; class ValueBuffer { @@ -11,13 +11,13 @@ class ValueBuffer { private boolean hasValue; - private Model model; + private ValueModel model; private boolean flushFailed; private Exception flushException; - public ValueBuffer(Model model) { + public ValueBuffer(ValueModel model) { this.model = model; } diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/WebBinder.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/WebBinder.java index 38a87110366..3680cb01d05 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/WebBinder.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/WebBinder.java @@ -83,7 +83,7 @@ public class WebBinder extends GenericBinder { } protected Object getEmptyValue(PropertyBinding binding) { - Class type = binding.getModel().getValueType(); + Class type = binding.getValueModel().getValueType(); if (boolean.class.equals(type) || Boolean.class.equals(type)) { return Boolean.FALSE; } else { 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 bf3d4a99d91..984e4b9e507 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 @@ -63,7 +63,6 @@ public class GenericBinderTests { values.put("integer", "3"); values.put("foo", "BAR"); BindingResults results = binder.bind(values); - System.out.println(results); assertEquals(3, results.size()); assertEquals("string", results.get(0).getProperty()); @@ -251,7 +250,7 @@ public class GenericBinderTests { values.put("addresses[0]", "4655 Macy Lane:Melbourne:FL:35452"); values.put("addresses[1]", "1234 Rostock Circle:Palm Bay:FL:32901"); values.put("addresses[5]", "1977 Bel Aire Estates:Coker:AL:12345"); - binder.bind(values); + BindingResults results = binder.bind(values); Assert.assertEquals(6, bean.addresses.size()); assertEquals("4655 Macy Lane", bean.addresses.get(0).street); assertEquals("Melbourne", bean.addresses.get(0).city);