data binding system polish; package improvements

This commit is contained in:
Keith Donald 2009-07-22 19:37:06 +00:00
parent f519406c37
commit 6c5fb23e79
32 changed files with 1081 additions and 472 deletions

View File

@ -0,0 +1,36 @@
/*
* Copyright 2004-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.binding;
/**
* A factory for model property bindings.
* @author Keith Donald
*/
public interface BindingFactory {
/**
* The model object upon which bindings may be accessed.
*/
Object getModel();
/**
* Get a binding to a model property.
* @param property the property path
* @throws NoSuchBindingException if no binding to the property exists
*/
Binding getBinding(String property);
}

View File

@ -1,19 +0,0 @@
package org.springframework.ui.binding;
/**
* Thrown by a BindingFactory when no binding to a property exists.
* @author Keith Donald
* @since 3.0
* @see BindingFactory#getBinding(String)
*/
@SuppressWarnings("serial")
public class NoSuchBindingException extends RuntimeException {
/**
* Creates a new no such binding exception.
* @param property the requested property for which there is no binding
*/
public NoSuchBindingException(String property) {
super("No binding to property '" + property + "' exists");
}
}

View File

@ -1,14 +1,24 @@
/**
/*
* Copyright 2004-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.binding.support;
package org.springframework.ui.binding.binder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.springframework.ui.binding.BindingResult;
import org.springframework.ui.binding.BindingResults;
class ArrayListBindingResults implements BindingResults {

View File

@ -13,31 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.binding;
package org.springframework.ui.binding.binder;
import java.util.Map;
import org.springframework.ui.binding.Binding;
/**
* Binds user-entered values to properties of a model object.
* @author Keith Donald
* @since 3.0
* @see #bind(String)
* @see #getBinding(String)
* @see #bind(Map)
*/
public interface Binder {
/**
* The model object for which property bindings may be accessed.
* The model this binder binds to.
*/
Object getModel();
/**
* Get a binding to a model property..
* @param property the property path
* @throws NoSuchBindingException if no binding to the property exists
*/
Binding getBinding(String property);
public Object getModel();
/**
* Bind the source values to the properties of the model.

View File

@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.binding;
package org.springframework.ui.binding.binder;
import org.springframework.ui.alert.Alert;
import org.springframework.ui.binding.Binding;
/**
* The result of a bind operation.

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.binding;
package org.springframework.ui.binding.binder;
import java.util.List;

View File

@ -1,11 +1,22 @@
/**
/*
* Copyright 2004-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.binding.support;
package org.springframework.ui.binding.binder;
import org.springframework.ui.alert.Alert;
import org.springframework.ui.alert.Severity;
import org.springframework.ui.binding.BindingResult;
class BindingStatusResult implements BindingResult {

View File

@ -0,0 +1,145 @@
/*
* Copyright 2004-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.binding.binder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.springframework.context.MessageSource;
import org.springframework.core.convert.TypeConverter;
import org.springframework.ui.binding.Binding;
import org.springframework.ui.binding.BindingFactory;
import org.springframework.ui.binding.Binding.BindingStatus;
import org.springframework.ui.binding.support.PropertyNotFoundException;
import org.springframework.util.Assert;
/**
* A generic {@link Binder binder} suitable for use in most environments.
* @author Keith Donald
* @since 3.0
* @see #setMessageSource(MessageSource)
* @see #setTypeConverter(TypeConverter)
* @see #bind(Map)
*/
public class GenericBinder implements Binder {
private BindingFactory bindingFactory;
private String[] requiredProperties;
private MessageSource messageSource;
public GenericBinder(BindingFactory bindingFactory) {
Assert.notNull(bindingFactory, "The BindingFactory is required");
this.bindingFactory = bindingFactory;
}
/**
* Configure the MessageSource that resolves localized {@link BindingResult} alert messages.
* @param messageSource the message source
*/
public void setMessageSource(MessageSource messageSource) {
Assert.notNull(messageSource, "The MessageSource is required");
this.messageSource = messageSource;
}
/**
* Configure the properties for which source values must be present in each bind attempt.
* @param propertyPaths the property path expressions
* @see MissingSourceValuesException
*/
public void setRequired(String[] propertyPaths) {
this.requiredProperties = propertyPaths;
}
public Object getModel() {
return bindingFactory.getModel();
}
protected Binding getBinding(String property) {
return bindingFactory.getBinding(property);
}
// implementing Binder
public BindingResults bind(Map<String, ? extends Object> sourceValues) {
sourceValues = filter(sourceValues);
checkRequired(sourceValues);
ArrayListBindingResults results = new ArrayListBindingResults(sourceValues.size());
for (Map.Entry<String, ? extends Object> sourceValue : sourceValues.entrySet()) {
try {
Binding binding = getBinding(sourceValue.getKey());
results.add(bind(sourceValue, binding));
} catch (PropertyNotFoundException e) {
results.add(new PropertyNotFoundResult(sourceValue.getKey(), sourceValue.getValue(), messageSource));
}
}
return results;
}
// 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.
* For example, a Binder might insert empty or default values for fields that are not present.
* As another example, a Binder might collapse multiple source values into a single source value.
* @param sourceValues the original source values map provided by the caller
* @return the filtered source values map that will be used to bind
*/
protected Map<String, ? extends Object> filter(Map<String, ? extends Object> sourceValues) {
return sourceValues;
}
// internal helpers
private void checkRequired(Map<String, ? extends Object> sourceValues) {
if (requiredProperties == null) {
return;
}
List<String> missingRequired = new ArrayList<String>();
for (String required : requiredProperties) {
boolean found = false;
for (String property : sourceValues.keySet()) {
if (property.equals(required)) {
found = true;
}
}
if (!found) {
missingRequired.add(required);
}
}
if (!missingRequired.isEmpty()) {
throw new MissingSourceValuesException(missingRequired, sourceValues);
}
}
private BindingResult bind(Map.Entry<String, ? extends Object> sourceValue, Binding binding) {
String property = sourceValue.getKey();
Object value = sourceValue.getValue();
if (!binding.isEditable()) {
return new PropertyNotEditableResult(property, value, messageSource);
} else {
binding.applySourceValue(value);
if (binding.getStatus() == BindingStatus.DIRTY) {
binding.commit();
}
return new BindingStatusResult(property, value, binding.getStatusAlert());
}
}
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.binding;
package org.springframework.ui.binding.binder;
import java.util.List;
import java.util.Map;

View File

@ -1,13 +1,24 @@
/**
/*
* Copyright 2004-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.binding.support;
package org.springframework.ui.binding.binder;
import org.springframework.context.MessageSource;
import org.springframework.core.style.StylerUtils;
import org.springframework.ui.alert.Alert;
import org.springframework.ui.alert.Severity;
import org.springframework.ui.binding.BindingResult;
import org.springframework.ui.message.MessageBuilder;
import org.springframework.ui.message.ResolvableArgument;

View File

@ -1,10 +1,24 @@
package org.springframework.ui.binding.support;
/*
* Copyright 2004-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.binding.binder;
import org.springframework.context.MessageSource;
import org.springframework.core.style.StylerUtils;
import org.springframework.ui.alert.Alert;
import org.springframework.ui.alert.Severity;
import org.springframework.ui.binding.BindingResult;
import org.springframework.ui.message.MessageBuilder;
import org.springframework.ui.message.ResolvableArgument;

View File

@ -13,11 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.binding.support;
package org.springframework.ui.binding.binder;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.ui.binding.Binding;
import org.springframework.ui.binding.BindingFactory;
/**
* A binder designed for use in HTTP (web) environments.
* Suited for binding user-provided HTTP query parameters to model properties.
@ -36,8 +39,8 @@ public class WebBinder extends GenericBinder {
* Creates a new web binder for the model object.
* @param model the model object containing properties this binder will bind to
*/
public WebBinder(Object model) {
super(model);
public WebBinder(BindingFactory bindingFactory) {
super(bindingFactory);
}
/**
@ -72,7 +75,7 @@ public class WebBinder extends GenericBinder {
} else if (field.startsWith(presentPrefix)) {
field = field.substring(presentPrefix.length());
if (!sourceValues.containsKey(field) && !sourceValues.containsKey(defaultPrefix + field)) {
value = getEmptyValue((PropertyBinding) getBinding(field));
value = getEmptyValue(getBinding(field));
filteredValues.put(field, value);
}
} else {
@ -82,8 +85,8 @@ public class WebBinder extends GenericBinder {
return filteredValues;
}
protected Object getEmptyValue(PropertyBinding binding) {
Class<?> type = binding.getValueModel().getValueType();
protected Object getEmptyValue(Binding binding) {
Class<?> type = binding.getValueType();
if (boolean.class.equals(type) || Boolean.class.equals(type)) {
return Boolean.FALSE;
} else {

View File

@ -1,3 +1,18 @@
/*
* Copyright 2004-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.binding.config;
public interface Condition {

View File

@ -0,0 +1,50 @@
/*
* Copyright 2004-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.binding.support;
import org.springframework.context.MessageSource;
import org.springframework.core.convert.TypeConverter;
import org.springframework.ui.binding.Binding;
import org.springframework.ui.binding.config.Condition;
import org.springframework.ui.format.Formatter;
public interface BindingContext {
MessageSource getMessageSource();
TypeConverter getTypeConverter();
Condition getEditableCondition();
Condition getEnabledCondition();
Condition getVisibleCondition();
Binding getBinding(String property);
Formatter getFormatter();
Formatter getElementFormatter();
Formatter getKeyFormatter();
Binding getListElementBinding(int index);
Binding getMapValueBinding(Object key);
String getLabel();
}

View File

@ -1,3 +1,18 @@
/*
* Copyright 2004-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.binding.support;
import org.springframework.ui.binding.config.Condition;

View File

@ -1,5 +1,17 @@
/**
/*
* Copyright 2004-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.binding.support;

View File

@ -1,54 +1,31 @@
/**
/*
* Copyright 2004-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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;
@SuppressWarnings("unchecked")
// TODO should this delegate to type converter...
class DefaultFormatter implements Formatter {
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

@ -1,11 +1,27 @@
/*
* Copyright 2004-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@ -19,12 +35,13 @@ 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 {
public class GenericBinding implements Binding {
private ValueModel valueModel;
private BindingContext bindingContext;
@ -36,9 +53,10 @@ public abstract class AbstractBinding implements Binding {
private Exception invalidSourceValueCause;
public AbstractBinding(BindingContext bindingContext) {
public GenericBinding(ValueModel valueModel, BindingContext bindingContext) {
this.valueModel = valueModel;
this.bindingContext = bindingContext;
buffer = new ValueBuffer(getValueModel());
buffer = new ValueBuffer(valueModel);
status = BindingStatus.CLEAN;
}
@ -52,16 +70,16 @@ public abstract class AbstractBinding implements Binding {
if (status == BindingStatus.DIRTY || status == BindingStatus.COMMIT_FAILURE) {
return buffer.getValue();
} else {
return getValueModel().getValue();
return valueModel.getValue();
}
}
public Class<?> getValueType() {
return getValueModel().getValueType();
return valueModel.getValueType();
}
public boolean isEditable() {
return bindingContext.getEditableCondition().isTrue();
return valueModel.isWriteable() && bindingContext.getEditableCondition().isTrue();
}
public boolean isEnabled() {
@ -91,17 +109,20 @@ public abstract class AbstractBinding implements Binding {
status = BindingStatus.INVALID_SOURCE_VALUE;
}
} else if (sourceValue instanceof String[]) {
Object parsed;
if (isMap()) {
String[] sourceValues = (String[]) sourceValue;
Class<?> parsedType = getFormattedObjectType(bindingContext.getElementFormatter().getClass());
if (parsedType == null) {
parsedType = String.class;
}
Object parsed = Array.newInstance(parsedType, sourceValues.length);
Formatter keyFormatter = bindingContext.getKeyFormatter();
Formatter valueFormatter = bindingContext.getElementFormatter();
Map map = new LinkedHashMap(sourceValues.length);
for (int i = 0; i < sourceValues.length; i++) {
String entryString = sourceValues[i];
Object parsedValue;
try {
parsedValue = bindingContext.getElementFormatter().parse(sourceValues[i], getLocale());
Array.set(parsed, i, parsedValue);
String[] keyValue = entryString.split("=");
Object parsedMapKey = keyFormatter.parse(keyValue[0], getLocale());
Object parsedMapValue = valueFormatter.parse(keyValue[1], getLocale());
map.put(parsedMapKey, parsedMapValue);
} catch (ParseException e) {
this.sourceValue = sourceValue;
invalidSourceValueCause = e;
@ -109,6 +130,24 @@ public abstract class AbstractBinding implements Binding {
break;
}
}
parsed = map;
} else {
String[] sourceValues = (String[]) sourceValue;
List list = new ArrayList(sourceValues.length);
for (int i = 0; i < sourceValues.length; i++) {
Object parsedValue;
try {
parsedValue = bindingContext.getElementFormatter().parse(sourceValues[i], getLocale());
list.add(parsedValue);
} catch (ParseException e) {
this.sourceValue = sourceValue;
invalidSourceValueCause = e;
status = BindingStatus.INVALID_SOURCE_VALUE;
break;
}
}
parsed = list;
}
if (status != BindingStatus.INVALID_SOURCE_VALUE) {
try {
buffer.setValue(coerseToValueType(parsed));
@ -256,6 +295,13 @@ public abstract class AbstractBinding implements Binding {
}
public Binding getMapValueBinding(Object key) {
if (key instanceof String) {
try {
key = bindingContext.getKeyFormatter().parse((String) key, getLocale());
} catch (ParseException e) {
throw new IllegalArgumentException("Unable to parse map key '" + key + "'", e);
}
}
return bindingContext.getMapValueBinding(key);
}
@ -263,31 +309,25 @@ public abstract class AbstractBinding implements Binding {
public String formatValue(Object value) {
Formatter formatter;
if (Collection.class.isAssignableFrom(getValueType()) || getValueType().isArray() || isMap()) {
formatter = getBindingContext().getElementFormatter();
formatter = bindingContext.getElementFormatter();
} else {
formatter = getBindingContext().getFormatter();
formatter = bindingContext.getFormatter();
}
return format(value, formatter);
}
// subclassing hooks
// internal helpers
protected BindingContext getBindingContext() {
return bindingContext;
}
protected abstract ValueModel getValueModel();
protected Locale getLocale() {
return LocaleContextHolder.getLocale();
}
protected String format(Object value, Formatter formatter) {
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) {
@ -317,7 +357,7 @@ public abstract class AbstractBinding implements Binding {
}
private Object coerseToValueType(Object parsed) {
TypeDescriptor targetType = getValueModel().getValueTypeDescriptor();
TypeDescriptor targetType = valueModel.getValueTypeDescriptor();
TypeConverter converter = bindingContext.getTypeConverter();
if (parsed != null && converter.canConvert(parsed.getClass(), targetType)) {
return converter.convert(parsed, targetType);
@ -338,39 +378,10 @@ public abstract class AbstractBinding implements Binding {
}
}
// 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);
}
}

View File

@ -21,19 +21,19 @@ import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.codehaus.groovy.binding.PropertyBinding;
import org.springframework.context.MessageSource;
import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.convert.TypeConverter;
import org.springframework.core.convert.support.DefaultTypeConverter;
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.MissingSourceValuesException;
import org.springframework.ui.binding.Binding.BindingStatus;
import org.springframework.ui.binding.BindingFactory;
import org.springframework.ui.binding.binder.Binder;
import org.springframework.ui.binding.binder.BindingResult;
import org.springframework.ui.binding.config.BindingRuleConfiguration;
import org.springframework.ui.binding.config.Condition;
import org.springframework.ui.format.Formatter;
@ -47,7 +47,7 @@ import org.springframework.util.Assert;
* @see #setTypeConverter(TypeConverter)
* @see #bind(Map)
*/
public class GenericBinder implements Binder {
public class GenericBindingFactory implements BindingFactory {
private Object model;
@ -59,13 +59,11 @@ public class GenericBinder implements Binder {
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
*/
public GenericBinder(Object model) {
public GenericBindingFactory(Object model) {
Assert.notNull(model, "The model to bind to is required");
this.model = model;
bindingRules = new HashMap<String, GenericBindingRule>();
@ -104,9 +102,9 @@ public class GenericBinder implements Binder {
}
/**
*
* Add a new binding rule for the property at the path specified.
* @param propertyPath binding rule property path in format prop.nestedProp
* @return
* @return a builder for the binding rule
*/
public BindingRuleConfiguration bindingRule(String propertyPath) {
PropertyPath path = new PropertyPath(propertyPath);
@ -142,55 +140,6 @@ public class GenericBinder implements Binder {
return binding;
}
public BindingResults bind(Map<String, ? extends Object> sourceValues) {
sourceValues = filter(sourceValues);
checkRequired(sourceValues);
ArrayListBindingResults results = new ArrayListBindingResults(sourceValues.size());
for (Map.Entry<String, ? extends Object> sourceValue : sourceValues.entrySet()) {
try {
Binding binding = getBinding(sourceValue.getKey());
results.add(bind(sourceValue, binding));
} catch (PropertyNotFoundException e) {
results.add(new PropertyNotFoundResult(sourceValue.getKey(), sourceValue.getValue(), messageSource));
}
}
return results;
}
// 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.
* For example, a Binder might insert empty or default values for fields that are not present.
* As another example, a Binder might collapse multiple source values into a single source value.
* @param sourceValues the original source values map provided by the caller
* @return the filtered source values map that will be used to bind
*/
protected Map<String, ? extends Object> filter(Map<String, ? extends Object> sourceValues) {
return sourceValues;
}
// internal helpers
private void checkRequired(Map<String, ? extends Object> sourceValues) {
List<String> missingRequired = new ArrayList<String>();
for (String required : requiredProperties) {
boolean found = false;
for (String property : sourceValues.keySet()) {
if (property.equals(required)) {
found = true;
}
}
if (!found) {
missingRequired.add(required);
}
}
if (!missingRequired.isEmpty()) {
throw new MissingSourceValuesException(missingRequired, sourceValues);
}
}
private GenericBindingRule getBindingRule(String property) {
GenericBindingRule rule = bindingRules.get(property);
if (rule == null) {
@ -200,20 +149,6 @@ public class GenericBinder implements Binder {
return rule;
}
private BindingResult bind(Map.Entry<String, ? extends Object> sourceValue, Binding binding) {
String property = sourceValue.getKey();
Object value = sourceValue.getValue();
if (!binding.isEditable()) {
return new PropertyNotEditableResult(property, value, messageSource);
} else {
binding.applySourceValue(value);
if (binding.getStatus() == BindingStatus.DIRTY) {
binding.commit();
}
return new BindingStatusResult(property, value, binding.getStatusAlert());
}
}
@SuppressWarnings("unchecked")
class GenericBindingRule implements BindingRuleConfiguration, BindingContext {
@ -223,10 +158,10 @@ public class GenericBinder implements Binder {
private Formatter formatter;
private Formatter elementFormatter;
private Formatter keyFormatter;
private Formatter elementFormatter;
private Condition editableCondition = Condition.ALWAYS_TRUE;
private Condition enabledCondition = Condition.ALWAYS_TRUE;
@ -239,6 +174,8 @@ public class GenericBinder implements Binder {
private Map<Integer, Binding> listElementBindings;
private Map<Object, Binding> mapValueBindings;
public GenericBindingRule(String property, Class modelClass) {
this.modelClass = modelClass;
this.property = findPropertyDescriptor(property);
@ -262,14 +199,6 @@ public class GenericBinder implements Binder {
}
}
public Formatter<?> getElementFormatter() {
if (elementFormatter != null) {
return formatter;
} else {
return formatterRegistry.getFormatter(getElementType());
}
}
public Formatter<?> getKeyFormatter() {
if (keyFormatter != null) {
return keyFormatter;
@ -278,6 +207,14 @@ public class GenericBinder implements Binder {
}
}
public Formatter<?> getElementFormatter() {
if (elementFormatter != null) {
return formatter;
} else {
return formatterRegistry.getFormatter(getElementType());
}
}
public Condition getEnabledCondition() {
return enabledCondition;
}
@ -295,79 +232,34 @@ public class GenericBinder implements Binder {
return getBindingRule(property, binding.getValueType()).getBinding(binding.getValue());
}
public Binding getListElementBinding(final int index) {
public Binding getListElementBinding(int index) {
if (listElementBindings == null) {
listElementBindings = new HashMap<Integer, Binding>();
}
growListIfNecessary(index);
Binding binding = listElementBindings.get(index);
if (binding == null) {
final Map<String, Binding> nestedBindings = new HashMap<String, Binding>();
BindingContext listContext = new BindingContext() {
public MessageSource getMessageSource() {
return GenericBindingRule.this.getMessageSource();
}
public TypeConverter getTypeConverter() {
return GenericBindingRule.this.getTypeConverter();
}
public Binding getBinding(String property) {
GenericBindingRule rule = GenericBindingRule.this.getBindingRule(property, getElementType());
Object model = ((List) GenericBindingRule.this.binding.getValue()).get(index);
Binding binding = nestedBindings.get(property);
if (binding == null) {
binding = new PropertyBinding(rule.property, model, rule);
nestedBindings.put(property, binding);
}
return binding;
}
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);
BindingContext bindingContext = new ListElementBindingContext(index, this);
ValueModel valueModel = new ListElementValueModel(index, getElementType(), (List) this.binding.getValue());
binding = new GenericBinding(valueModel, bindingContext);
listElementBindings.put(index, binding);
}
return binding;
}
public Binding getMapValueBinding(Object key) {
return null;
if (mapValueBindings == null) {
mapValueBindings = new HashMap<Object, Binding>();
}
createMapValueIfNecessary();
Binding binding = mapValueBindings.get(key);
if (binding == null) {
BindingContext bindingContext = new MapValueBindingContext(key, this);
ValueModel valueModel = new MapValueValueModel(key, getElementType(), (Map) this.binding.getValue(), bindingContext);
binding = new GenericBinding(valueModel, bindingContext);
mapValueBindings.put(key, binding);
}
return binding;
}
public String getLabel() {
@ -416,8 +308,12 @@ public class GenericBinder implements Binder {
// internal helpers
private Class<?> getElementType() {
if (Map.class.isAssignableFrom(property.getPropertyType())) {
return GenericCollectionTypeResolver.getMapValueReturnType(property.getReadMethod());
} else {
return GenericCollectionTypeResolver.getCollectionReturnType(property.getReadMethod());
}
}
private Class<?> getKeyType() {
return GenericCollectionTypeResolver.getMapKeyReturnType(property.getReadMethod());
@ -443,7 +339,8 @@ public class GenericBinder implements Binder {
Binding getBinding(Object model) {
if (binding == null) {
binding = new PropertyBinding(property, model, this);
PropertyValueModel valueModel = new PropertyValueModel(property, model);
binding = new GenericBinding(valueModel, this);
}
return binding;
}
@ -475,6 +372,15 @@ public class GenericBinder implements Binder {
}
}
private void createMapValueIfNecessary() {
Object value = binding.getValue();
if (value == null) {
value = newMapValue(binding.getValueType());
binding.applySourceValue(value);
binding.commit();
}
}
private void growListIfNecessary(int index) {
List list = (List) binding.getValue();
if (list == null) {
@ -490,6 +396,14 @@ public class GenericBinder implements Binder {
}
}
private Map newMapValue(Class<?> type) {
if (type.isInterface()) {
return (Map) newValue(LinkedHashMap.class);
} else {
return (Map) newValue(type);
}
}
private List newListValue(Class<?> type) {
if (type.isInterface()) {
return (List) newValue(ArrayList.class);
@ -510,36 +424,153 @@ public class GenericBinder implements Binder {
}
public interface BindingContext {
private static class ListElementBindingContext implements BindingContext {
MessageSource getMessageSource();
private int index;
TypeConverter getTypeConverter();
private GenericBindingRule listBindingContext;
Condition getEditableCondition();
Condition getEnabledCondition();
Condition getVisibleCondition();
Binding getBinding(String property);
Formatter getFormatter();
Formatter getElementFormatter();
Formatter getKeyFormatter();
Binding getListElementBinding(int index);
Binding getMapValueBinding(Object key);
String getLabel();
final Map<String, Binding> nestedBindings = new HashMap<String, Binding>();
public ListElementBindingContext(int index, GenericBindingRule listBindingContext) {
this.index = index;
this.listBindingContext = listBindingContext;
}
public void setRequired(String[] propertyPaths) {
this.requiredProperties = propertyPaths;
public MessageSource getMessageSource() {
return listBindingContext.getMessageSource();
}
public TypeConverter getTypeConverter() {
return listBindingContext.getTypeConverter();
}
public Binding getBinding(String property) {
Object model = ((List) listBindingContext.binding.getValue()).get(index);
Class<?> elementType = listBindingContext.getElementType();
if (elementType == null) {
elementType = model.getClass();
}
GenericBindingRule rule = listBindingContext.getBindingRule(property, elementType);
Binding binding = nestedBindings.get(property);
if (binding == null) {
PropertyValueModel valueModel = new PropertyValueModel(rule.property, model);
binding = new GenericBinding(valueModel, rule);
nestedBindings.put(property, binding);
}
return binding;
}
public Formatter getFormatter() {
return listBindingContext.getElementFormatter();
}
public Formatter getElementFormatter() {
return null;
}
public Formatter getKeyFormatter() {
return null;
}
public Condition getEditableCondition() {
return listBindingContext.getEditableCondition();
}
public Condition getEnabledCondition() {
return listBindingContext.getEnabledCondition();
}
public Condition getVisibleCondition() {
return listBindingContext.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 listBindingContext.getLabel() + "[" + index + "]";
}
};
private static class MapValueBindingContext implements BindingContext {
private Object key;
private GenericBindingRule mapBindingContext;
final Map<String, Binding> nestedBindings = new HashMap<String, Binding>();
public MapValueBindingContext(Object key, GenericBindingRule mapBindingContext) {
this.key = key;
this.mapBindingContext = mapBindingContext;
}
public MessageSource getMessageSource() {
return mapBindingContext.getMessageSource();
}
public TypeConverter getTypeConverter() {
return mapBindingContext.getTypeConverter();
}
public Binding getBinding(String property) {
Object model = ((Map) mapBindingContext.binding.getValue()).get(key);
Class<?> elementType = mapBindingContext.getElementType();
if (elementType == null) {
elementType = model.getClass();
}
GenericBindingRule rule = mapBindingContext.getBindingRule(property, elementType);
Binding binding = nestedBindings.get(property);
if (binding == null) {
PropertyValueModel valueModel = new PropertyValueModel(rule.property, model);
binding = new GenericBinding(valueModel, rule);
nestedBindings.put(property, binding);
}
return binding;
}
public Formatter getFormatter() {
return mapBindingContext.getElementFormatter();
}
public Formatter getElementFormatter() {
return null;
}
public Formatter getKeyFormatter() {
return null;
}
public Condition getEditableCondition() {
return mapBindingContext.getEditableCondition();
}
public Condition getEnabledCondition() {
return mapBindingContext.getEnabledCondition();
}
public Condition getVisibleCondition() {
return mapBindingContext.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 mapBindingContext.getLabel() + "[" + key + "]";
}
};
}

View File

@ -17,20 +17,31 @@ package org.springframework.ui.binding.support;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
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.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.TypeConverter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.ConversionUtils;
import org.springframework.core.convert.support.DefaultTypeConverter;
import org.springframework.ui.format.AnnotationFormatterFactory;
import org.springframework.ui.format.Formatted;
import org.springframework.ui.format.Formatter;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* A generic implementation of {@link FormatterRegistry} suitable for use in most binding environments.
@ -48,7 +59,14 @@ public class GenericFormatterRegistry implements FormatterRegistry {
private Map<Class, AnnotationFormatterFactory> annotationFormatters = new HashMap<Class, AnnotationFormatterFactory>();
private TypeConverter typeConverter = new DefaultTypeConverter();
public void setTypeConverter(TypeConverter typeConverter) {
this.typeConverter = typeConverter;
}
public Formatter<?> getFormatter(PropertyDescriptor property) {
Assert.notNull(property, "The PropertyDescriptor is required");
TypeDescriptor<?> propertyType = new TypeDescriptor(new MethodParameter(property.getReadMethod(), -1));
Annotation[] annotations = propertyType.getAnnotations();
for (Annotation a : annotations) {
@ -60,11 +78,13 @@ public class GenericFormatterRegistry implements FormatterRegistry {
Formatter<?> formatter = null;
Class<?> type;
if (propertyType.isCollection() || propertyType.isArray()) {
formatter = collectionTypeFormatters.get(new GenericCollectionPropertyType(propertyType.getType(), propertyType.getElementType()));
GenericCollectionPropertyType collectionType = new GenericCollectionPropertyType(propertyType.getType(),
propertyType.getElementType());
formatter = collectionTypeFormatters.get(collectionType);
if (formatter != null) {
return formatter;
} else {
return DefaultFormatter.COLLECTION_FORMATTER;
return new DefaultCollectionFormatter(collectionType, this);
}
} else {
type = propertyType.getType();
@ -73,6 +93,7 @@ public class GenericFormatterRegistry implements FormatterRegistry {
}
public Formatter<?> getFormatter(Class<?> type) {
Assert.notNull(type, "The Class of the object to format is required");
Formatter formatter = typeFormatters.get(type);
if (formatter != null) {
return formatter;
@ -83,20 +104,20 @@ public class GenericFormatterRegistry implements FormatterRegistry {
try {
formatter = (Formatter) formatterClass.newInstance();
} catch (InstantiationException e) {
// TODO better runtime exception
throw new IllegalStateException(e);
throw new IllegalStateException(
"Formatter referenced by @Formatted annotation does not have default constructor", e);
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
throw new IllegalStateException(
"Formatter referenced by @Formatted annotation does not have public constructor", e);
}
typeFormatters.put(type, formatter);
return formatter;
} else {
return DefaultFormatter.INSTANCE;
return new DefaultFormatter(type, typeConverter);
}
}
}
public void add(Class<?> propertyType, Formatter<?> formatter) {
if (propertyType.isAnnotation()) {
annotationFormatters.put(propertyType, new SimpleAnnotationFormatterFactory(formatter));
@ -159,4 +180,124 @@ public class GenericFormatterRegistry implements FormatterRegistry {
}
private static class DefaultFormatter implements Formatter {
public static final Formatter DEFAULT_INSTANCE = new DefaultFormatter(null, null);
private Class<?> objectType;
private TypeConverter typeConverter;
public DefaultFormatter(Class<?> objectType, TypeConverter typeConverter) {
this.objectType = objectType;
this.typeConverter = typeConverter;
}
public String format(Object object, Locale locale) {
if (object == null) {
return "";
} else {
if (typeConverter != null && typeConverter.canConvert(object.getClass(), String.class)) {
return typeConverter.convert(object, String.class);
} else {
return object.toString();
}
}
}
public Object parse(String formatted, Locale locale) throws ParseException {
if (formatted == "") {
return null;
} else {
if (typeConverter != null && typeConverter.canConvert(String.class, objectType)) {
try {
return typeConverter.convert(formatted, objectType);
} catch (ConversionFailedException e) {
throw new ParseException(formatted, -1);
}
} else {
return formatted;
}
}
}
}
private static class DefaultCollectionFormatter implements Formatter {
private GenericCollectionPropertyType collectionType;
private Formatter elementFormatter;
public DefaultCollectionFormatter(GenericCollectionPropertyType collectionType,
GenericFormatterRegistry formatterRegistry) {
this.collectionType = collectionType;
this.elementFormatter = collectionType.getElementType() != null ? formatterRegistry
.getFormatter(collectionType.getElementType()) : DefaultFormatter.DEFAULT_INSTANCE;
}
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(elementFormatter.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(elementFormatter.format(it.next(), locale));
if (it.hasNext()) {
buffer.append(",");
}
}
}
return buffer.toString();
}
}
public Object parse(String formatted, Locale locale) throws ParseException {
String[] fields = StringUtils.commaDelimitedListToStringArray(formatted);
if (collectionType.getCollectionType().isArray()) {
Object array = Array.newInstance(getElementType(), fields.length);
for (int i = 0; i < fields.length; i++) {
Array.set(array, i, elementFormatter.parse(fields[i], locale));
}
return array;
} else {
Collection collection = newCollection();
for (int i = 0; i < fields.length; i++) {
collection.add(elementFormatter.parse(fields[i], locale));
}
return collection;
}
}
private Class<?> getElementType() {
if (collectionType.getElementType() != null) {
return collectionType.getElementType();
} else {
return String.class;
}
}
private Collection newCollection() {
try {
Class<? extends Collection> implType = ConversionUtils
.getCollectionImpl((Class<? extends Collection>) collectionType.getCollectionType());
return (Collection) implType.newInstance();
} catch (InstantiationException e) {
throw new IllegalStateException("Should not happen", e);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Should not happen", e);
}
}
};
}

View File

@ -1,51 +0,0 @@
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);
}
};
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright 2004-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.binding.support;
import java.util.List;
import org.springframework.core.convert.TypeDescriptor;
class ListElementValueModel implements ValueModel {
private List list;
private int index;
private Class<?> elementType;
public ListElementValueModel(int index, Class<?> elementType, List list) {
this.index = index;
this.elementType = elementType;
this.list = list;
}
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 boolean isWriteable() {
return true;
}
public void setValue(Object value) {
list.set(index, value);
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright 2004-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.binding.support;
import java.util.Map;
import org.springframework.core.convert.TypeDescriptor;
public class MapValueValueModel implements ValueModel {
private Object key;
private Class<?> elementType;
private Map map;
public MapValueValueModel(Object key, Class<?> elementType, Map map, BindingContext bindingContext) {
this.key = key;
this.elementType = elementType;
this.map = map;
}
public Object getValue() {
return map.get(key);
}
public Class<?> getValueType() {
if (elementType != null) {
return elementType;
} else {
return getValue().getClass();
}
}
public TypeDescriptor<?> getValueTypeDescriptor() {
return TypeDescriptor.valueOf(getValueType());
}
public boolean isWriteable() {
return true;
}
public void setValue(Object value) {
map.put(key, value);
}
}

View File

@ -1,58 +0,0 @@
package org.springframework.ui.binding.support;
import java.beans.PropertyDescriptor;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.ui.binding.Binding;
import org.springframework.ui.binding.support.GenericBinder.BindingContext;
import org.springframework.util.ReflectionUtils;
public class PropertyBinding extends AbstractBinding implements Binding {
private PropertyDescriptor property;
private Object object;
public PropertyBinding(PropertyDescriptor property, Object object, BindingContext bindingContext) {
super(bindingContext);
this.property = property;
this.object = object;
}
// Binding overrides
@Override
public boolean isEditable() {
return isWriteable() && super.isEditable();
}
// implementing
protected ValueModel getValueModel() {
return new ValueModel() {
public Object getValue() {
return ReflectionUtils.invokeMethod(property.getReadMethod(), object);
}
public Class<?> getValueType() {
return property.getPropertyType();
}
@SuppressWarnings("unchecked")
public TypeDescriptor<?> getValueTypeDescriptor() {
return new TypeDescriptor(new MethodParameter(property.getReadMethod(), -1));
}
public void setValue(Object value) {
ReflectionUtils.invokeMethod(property.getWriteMethod(), object, value);
}
};
}
// internal helpers
private boolean isWriteable() {
return property.getWriteMethod() != null;
}
}

View File

@ -1,3 +1,18 @@
/*
* Copyright 2004-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.binding.support;
public class PropertyNotFoundException extends RuntimeException {

View File

@ -1,5 +1,17 @@
/**
/*
* Copyright 2004-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.binding.support;

View File

@ -1,5 +1,17 @@
/**
/*
* Copyright 2004-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.binding.support;

View File

@ -0,0 +1,56 @@
/*
* Copyright 2004-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.binding.support;
import java.beans.PropertyDescriptor;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.util.ReflectionUtils;
public class PropertyValueModel implements ValueModel {
private PropertyDescriptor property;
private Object object;
public PropertyValueModel(PropertyDescriptor property, Object object) {
this.property = property;
this.object = object;
}
public Object getValue() {
return ReflectionUtils.invokeMethod(property.getReadMethod(), object);
}
public Class<?> getValueType() {
return property.getPropertyType();
}
@SuppressWarnings("unchecked")
public TypeDescriptor<?> getValueTypeDescriptor() {
return new TypeDescriptor(new MethodParameter(property.getReadMethod(), -1));
}
public boolean isWriteable() {
return property.getWriteMethod() != null;
}
public void setValue(Object value) {
ReflectionUtils.invokeMethod(property.getWriteMethod(), object, value);
}
}

View File

@ -1,9 +1,20 @@
/**
/*
* Copyright 2004-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.binding.support;
import org.springframework.ui.binding.support.AbstractBinding.ValueModel;
class ValueBuffer {

View File

@ -0,0 +1,36 @@
package org.springframework.ui.binding.support;
import org.springframework.core.convert.TypeDescriptor;
/**
* 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();
/**
* If the model is writeable.
*/
boolean isWriteable();
/**
* Set the model value.
* @throws IllegalStateException if not writeable
*/
void setValue(Object value);
}

View File

@ -20,9 +20,9 @@ import java.util.List;
import java.util.Map;
import org.springframework.ui.alert.AlertContext;
import org.springframework.ui.binding.Binder;
import org.springframework.ui.binding.BindingResult;
import org.springframework.ui.binding.BindingResults;
import org.springframework.ui.binding.binder.Binder;
import org.springframework.ui.binding.binder.BindingResult;
import org.springframework.ui.binding.binder.BindingResults;
import org.springframework.ui.validation.ValidationFailure;
import org.springframework.ui.validation.Validator;

View File

@ -15,7 +15,7 @@
*/
package org.springframework.ui.lifecycle;
import org.springframework.ui.binding.BindingResults;
import org.springframework.ui.binding.binder.BindingResults;
/**
* Decides if validation should run for an execution of the bind and validate lifecycle.