additional binding metadata
This commit is contained in:
parent
263d502f51
commit
9c78616e11
|
|
@ -34,10 +34,25 @@ public interface Binding {
|
|||
Object getValue();
|
||||
|
||||
/**
|
||||
* If this Binding is read-only.
|
||||
* A read-only Binding cannot have source values applied and cannot be committed.
|
||||
* If this Binding is editable.
|
||||
* Used to determine if the user can edit the field value.
|
||||
* A Binding that is not editable cannot have source values applied and cannot be committed.
|
||||
*/
|
||||
boolean isReadOnly();
|
||||
boolean isEditable();
|
||||
|
||||
/**
|
||||
* If this Binding is enabled.
|
||||
* Used to determine if the user can interact with the field.
|
||||
* A Binding that is not enabled cannot have source values applied and cannot be committed.
|
||||
*/
|
||||
boolean isEnabled();
|
||||
|
||||
/**
|
||||
* If this Binding is visible.
|
||||
* Used to determine if the user can see the field.
|
||||
* A Binding that is not visible cannot have source values applied and cannot be committed.
|
||||
*/
|
||||
boolean isVisible();
|
||||
|
||||
/**
|
||||
* Apply the source value to this binding.
|
||||
|
|
@ -45,7 +60,7 @@ public interface Binding {
|
|||
* Sets to {@link BindingStatus#DIRTY} if succeeds.
|
||||
* Sets to {@link BindingStatus#INVALID_SOURCE_VALUE} if fails.
|
||||
* @param sourceValue
|
||||
* @throws IllegalStateException if {@link #isReadOnly()}
|
||||
* @throws IllegalStateException if {@link #isEditable()}
|
||||
*/
|
||||
void applySourceValue(Object sourceValue);
|
||||
|
||||
|
|
@ -60,11 +75,12 @@ public interface Binding {
|
|||
BindingStatus getStatus();
|
||||
|
||||
/**
|
||||
* An alert that communicates the details of a BindingStatus change to the user.
|
||||
* Returns <code>null</code> if the BindingStatus has never changed.
|
||||
* Returns a {@link Severity#INFO} Alert with code <code>bindSuccess</code> after a successful commit.
|
||||
* Returns a {@link Severity#ERROR} Alert with code <code>typeMismatch</code> if the source value could not be converted to type required by the Model.
|
||||
* Returns a {@link Severity#FATAL} Alert with code <code>internalError</code> if the buffered value could not be committed due to a unexpected runtime exception.
|
||||
* An alert that communicates current status to the user.
|
||||
* Returns <code>null</code> if {@link BindingStatus#CLEAN} and {@link ValidationStatus#NOT_VALIDATED}.
|
||||
* Returns a {@link Severity#INFO} Alert with code <code>bindSuccess</code> when {@link BindingStatus#COMMITTED}.
|
||||
* Returns a {@link Severity#ERROR} Alert with code <code>typeMismatch</code> when {@link BindingStatus#INVALID_SOURCE_VALUE} or {@link BindingStatus#COMMIT_FAILURE} due to a value parse / type conversion error.
|
||||
* Returns a {@link Severity#FATAL} Alert with code <code>internalError</code> when {@link BindingStatus#COMMIT_FAILURE} due to a unexpected runtime exception.
|
||||
* Returns a {@link Severity#INFO} Alert describing results of validation if {@link ValidationStatus#VALID} or {@link ValidationStatus#INVALID}.
|
||||
*/
|
||||
Alert getStatusAlert();
|
||||
|
||||
|
|
@ -72,10 +88,16 @@ public interface Binding {
|
|||
* Commit the buffered value to the model.
|
||||
* Sets to {@link BindingStatus#COMMITTED} if succeeds.
|
||||
* Sets to {@link BindingStatus#COMMIT_FAILURE} if fails.
|
||||
* @throws IllegalStateException if not {@link BindingStatus#DIRTY} or {@link #isReadOnly()}
|
||||
* @throws IllegalStateException if not {@link BindingStatus#DIRTY} or {@link #isEditable()}
|
||||
*/
|
||||
void commit();
|
||||
|
||||
/**
|
||||
* Clear the buffered value without committing.
|
||||
* @throws IllegalStateException if BindingStatus is CLEAN or COMMITTED.
|
||||
*/
|
||||
void revert();
|
||||
|
||||
/**
|
||||
* Access raw model values.
|
||||
*/
|
||||
|
|
@ -91,14 +113,14 @@ public interface Binding {
|
|||
/**
|
||||
* If bound to an indexable Collection, either a {@link java.util.List} or an array.
|
||||
*/
|
||||
boolean isIndexable();
|
||||
boolean isList();
|
||||
|
||||
/**
|
||||
* If a List, get a Binding to a element in the List.
|
||||
* @param index the element index
|
||||
* @return the indexed binding
|
||||
*/
|
||||
Binding getIndexedBinding(int index);
|
||||
Binding getListElementBinding(int index);
|
||||
|
||||
/**
|
||||
* If bound to a {@link java.util.Map}.
|
||||
|
|
@ -110,11 +132,11 @@ public interface Binding {
|
|||
* @param key the map key
|
||||
* @return the keyed binding
|
||||
*/
|
||||
Binding getKeyedBinding(Object key);
|
||||
Binding getMapValueBinding(Object key);
|
||||
|
||||
/**
|
||||
* Format a potential model value for display.
|
||||
* If an indexable binding, expects the model value to be a potential collection element & uses the configured element formatter.
|
||||
* If a list binding, expects the model value to be a potential list element & uses the configured element formatter.
|
||||
* If a map binding, expects the model value to be a potential map value & uses the configured map value formatter.
|
||||
* @param potentialValue the potential value
|
||||
* @return the formatted string
|
||||
|
|
@ -145,7 +167,7 @@ public interface Binding {
|
|||
}
|
||||
|
||||
/**
|
||||
* The states of a Binding.
|
||||
* Binding states.
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public enum BindingStatus {
|
||||
|
|
@ -175,4 +197,26 @@ public interface Binding {
|
|||
*/
|
||||
COMMIT_FAILURE
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation states.
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public enum ValidationStatus {
|
||||
|
||||
/**
|
||||
* Initial state: No validation has run.
|
||||
*/
|
||||
NOT_VALIDATED,
|
||||
|
||||
/**
|
||||
* Validation has succeeded.
|
||||
*/
|
||||
VALID,
|
||||
|
||||
/**
|
||||
* Validation has failed.
|
||||
*/
|
||||
INVALID
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ package org.springframework.ui.binding.config;
|
|||
import org.springframework.ui.format.Formatter;
|
||||
|
||||
/**
|
||||
* A fluent interface for configuring a newly added binding.
|
||||
* A fluent interface for configuring a newly added binding rule.
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public interface BindingRuleConfiguration {
|
||||
|
|
@ -31,23 +31,21 @@ public interface BindingRuleConfiguration {
|
|||
BindingRuleConfiguration formatWith(Formatter<?> formatter);
|
||||
|
||||
/**
|
||||
* If a collection property, set the Formatter to use to format indexed collection elements.
|
||||
* If a indexable map property, set the Formatter to use to format map key indexes.
|
||||
* Default is null.
|
||||
*/
|
||||
BindingRuleConfiguration formatKeysWith(Formatter<?> formatter);
|
||||
|
||||
/**
|
||||
* If an indexable list or map property, set the Formatter to use to format indexed elements.
|
||||
* Default is null.
|
||||
*/
|
||||
BindingRuleConfiguration formatElementsWith(Formatter<?> formatter);
|
||||
|
||||
/**
|
||||
* Mark the binding as required.
|
||||
* A required binding will generate an exception if no sourceValue is provided to a bind invocation.
|
||||
* This attribute is used to detect client configuration errors.
|
||||
* It is not intended to be used as a user data validation constraint.
|
||||
* Examples:
|
||||
* <pre>
|
||||
* name=required - will generate an exception if 'name' is not contained in the source values map.
|
||||
* addresses=required - will generate an exception if 'addresses[n]' is not contained in the source value map for at least one n.
|
||||
* addresses.city=required - will generate an exception if 'addresses[n].city' is not present in the source values map, for every address[n].
|
||||
* </pre>
|
||||
* Mark the binding as read only.
|
||||
* A read-only binding cannot have source values applied and cannot be committed.
|
||||
*/
|
||||
BindingRuleConfiguration required();
|
||||
BindingRuleConfiguration readOnly();
|
||||
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ import org.springframework.ui.binding.Binding;
|
|||
import org.springframework.ui.binding.BindingResult;
|
||||
import org.springframework.ui.binding.BindingResults;
|
||||
import org.springframework.ui.binding.Binding.BindingStatus;
|
||||
import org.springframework.ui.binding.config.BindingRuleConfiguration;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
|
|
@ -77,6 +78,15 @@ public class GenericBinder implements Binder {
|
|||
this.typeConverter = typeConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param propertyPath binding rule property path in format prop.nestedListProp[].nestedMapProp[].nestedProp
|
||||
* @return
|
||||
*/
|
||||
public BindingRuleConfiguration bindingRule(String propertyPath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// implementing Binder
|
||||
|
||||
public Object getModel() {
|
||||
|
|
@ -93,9 +103,9 @@ public class GenericBinder implements Binder {
|
|||
for (PropertyPathElement element : path.getNestedElements()) {
|
||||
if (element.isIndex()) {
|
||||
if (binding.isMap()) {
|
||||
binding = binding.getKeyedBinding(element.getValue());
|
||||
} else if (binding.isIndexable()) {
|
||||
binding = binding.getIndexedBinding(element.getIntValue());
|
||||
binding = binding.getMapValueBinding(element.getValue());
|
||||
} else if (binding.isList()) {
|
||||
binding = binding.getListElementBinding(element.getIntValue());
|
||||
} else {
|
||||
throw new IllegalArgumentException("Attempted to index a property that is not a Collection or Map");
|
||||
}
|
||||
|
|
@ -135,7 +145,7 @@ public class GenericBinder implements Binder {
|
|||
private BindingResult bind(Map.Entry<String, ? extends Object> sourceValue, Binding binding) {
|
||||
String property = sourceValue.getKey();
|
||||
Object value = sourceValue.getValue();
|
||||
if (binding.isReadOnly()) {
|
||||
if (binding.isEditable()) {
|
||||
return new PropertyNotWriteableResult(property, value, messageSource);
|
||||
} else {
|
||||
binding.applySourceValue(value);
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ public class PropertyBinding implements Binding {
|
|||
|
||||
private ValueBuffer buffer;
|
||||
|
||||
private BindingStatus bindingStatus;
|
||||
private BindingStatus status;
|
||||
|
||||
public PropertyBinding(String property, Object model, TypeConverter typeConverter) {
|
||||
this.propertyDescriptor = findPropertyDescriptor(property, model);
|
||||
|
|
@ -61,36 +61,43 @@ public class PropertyBinding implements Binding {
|
|||
this.model = model;
|
||||
this.typeConverter = typeConverter;
|
||||
this.buffer = new ValueBuffer(getModel());
|
||||
bindingStatus = BindingStatus.CLEAN;
|
||||
status = BindingStatus.CLEAN;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
if (bindingStatus == BindingStatus.INVALID_SOURCE_VALUE) {
|
||||
if (status == BindingStatus.INVALID_SOURCE_VALUE) {
|
||||
return sourceValue;
|
||||
} else if (bindingStatus == BindingStatus.DIRTY || bindingStatus == BindingStatus.COMMIT_FAILURE) {
|
||||
} else if (status == BindingStatus.DIRTY || status == BindingStatus.COMMIT_FAILURE) {
|
||||
return formatValue(buffer.getValue());
|
||||
} else {
|
||||
return formatValue(getModel().getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isReadOnly() {
|
||||
return propertyDescriptor.getWriteMethod() == null || markedNotEditable();
|
||||
public boolean isEditable() {
|
||||
return propertyDescriptor.getWriteMethod() != null || !markedNotEditable();
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void applySourceValue(Object sourceValue) {
|
||||
if (isReadOnly()) {
|
||||
throw new IllegalStateException("Property is read only");
|
||||
}
|
||||
assertEditable();
|
||||
assertEnabled();
|
||||
if (sourceValue instanceof String) {
|
||||
try {
|
||||
buffer.setValue(valueFormatter.parse((String) sourceValue, getLocale()));
|
||||
sourceValue = null;
|
||||
bindingStatus = BindingStatus.DIRTY;
|
||||
status = BindingStatus.DIRTY;
|
||||
} catch (ParseException e) {
|
||||
this.sourceValue = sourceValue;
|
||||
sourceValueParseException = e;
|
||||
bindingStatus = BindingStatus.INVALID_SOURCE_VALUE;
|
||||
status = BindingStatus.INVALID_SOURCE_VALUE;
|
||||
}
|
||||
} else if (sourceValue instanceof String[]) {
|
||||
String[] sourceValues = (String[]) sourceValue;
|
||||
|
|
@ -107,24 +114,24 @@ public class PropertyBinding implements Binding {
|
|||
} catch (ParseException e) {
|
||||
this.sourceValue = sourceValue;
|
||||
sourceValueParseException = e;
|
||||
bindingStatus = BindingStatus.INVALID_SOURCE_VALUE;
|
||||
status = BindingStatus.INVALID_SOURCE_VALUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bindingStatus != BindingStatus.INVALID_SOURCE_VALUE) {
|
||||
if (status != BindingStatus.INVALID_SOURCE_VALUE) {
|
||||
buffer.setValue(parsed);
|
||||
sourceValue = null;
|
||||
bindingStatus = BindingStatus.DIRTY;
|
||||
status = BindingStatus.DIRTY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public BindingStatus getStatus() {
|
||||
return bindingStatus;
|
||||
return status;
|
||||
}
|
||||
|
||||
public Alert getStatusAlert() {
|
||||
if (bindingStatus == BindingStatus.INVALID_SOURCE_VALUE) {
|
||||
if (status == BindingStatus.INVALID_SOURCE_VALUE) {
|
||||
return new Alert() {
|
||||
public String getCode() {
|
||||
return "typeMismatch";
|
||||
|
|
@ -138,7 +145,7 @@ public class PropertyBinding implements Binding {
|
|||
return Severity.ERROR;
|
||||
}
|
||||
};
|
||||
} else if (bindingStatus == BindingStatus.COMMIT_FAILURE) {
|
||||
} else if (status == BindingStatus.COMMIT_FAILURE) {
|
||||
if (buffer.getFlushException() instanceof ConversionFailedException) {
|
||||
return new Alert() {
|
||||
public String getCode() {
|
||||
|
|
@ -168,7 +175,7 @@ public class PropertyBinding implements Binding {
|
|||
}
|
||||
};
|
||||
}
|
||||
} else if (bindingStatus == BindingStatus.COMMITTED) {
|
||||
} else if (status == BindingStatus.COMMITTED) {
|
||||
return new Alert() {
|
||||
public String getCode() {
|
||||
return "bindSucces";
|
||||
|
|
@ -188,14 +195,30 @@ public class PropertyBinding implements Binding {
|
|||
}
|
||||
|
||||
public void commit() {
|
||||
if (bindingStatus != BindingStatus.DIRTY) {
|
||||
throw new IllegalStateException("Binding not dirty; nothing to commit");
|
||||
}
|
||||
assertEditable();
|
||||
assertEnabled();
|
||||
if (status == BindingStatus.DIRTY) {
|
||||
buffer.flush();
|
||||
if (buffer.flushFailed()) {
|
||||
bindingStatus = BindingStatus.COMMIT_FAILURE;
|
||||
status = BindingStatus.COMMIT_FAILURE;
|
||||
} else {
|
||||
bindingStatus = BindingStatus.COMMITTED;
|
||||
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;
|
||||
sourceValueParseException = 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");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -210,9 +233,6 @@ public class PropertyBinding implements Binding {
|
|||
}
|
||||
|
||||
public void setValue(Object value) {
|
||||
if (isReadOnly()) {
|
||||
throw new IllegalStateException("Property is read-only");
|
||||
}
|
||||
TypeDescriptor targetType = new TypeDescriptor(new MethodParameter(propertyDescriptor.getWriteMethod(), 0));
|
||||
if (value != null && typeConverter.canConvert(value.getClass(), targetType)) {
|
||||
value = typeConverter.convert(value, targetType);
|
||||
|
|
@ -230,11 +250,11 @@ public class PropertyBinding implements Binding {
|
|||
return new PropertyBinding(nestedProperty, getValue(), typeConverter);
|
||||
}
|
||||
|
||||
public boolean isIndexable() {
|
||||
public boolean isList() {
|
||||
return getModel().getValueType().isArray() || List.class.isAssignableFrom(getModel().getValueType());
|
||||
}
|
||||
|
||||
public Binding getIndexedBinding(int index) {
|
||||
public Binding getListElementBinding(int index) {
|
||||
assertListProperty();
|
||||
//return new IndexedBinding(index, (List) getValue(), getCollectionTypeDescriptor(), typeConverter);
|
||||
return null;
|
||||
|
|
@ -244,7 +264,7 @@ public class PropertyBinding implements Binding {
|
|||
return Map.class.isAssignableFrom(getModel().getValueType());
|
||||
}
|
||||
|
||||
public Binding getKeyedBinding(Object key) {
|
||||
public Binding getMapValueBinding(Object key) {
|
||||
assertMapProperty();
|
||||
if (key instanceof String) {
|
||||
try {
|
||||
|
|
@ -259,7 +279,7 @@ public class PropertyBinding implements Binding {
|
|||
|
||||
public String formatValue(Object value) {
|
||||
Formatter formatter;
|
||||
if (isIndexable() || isMap()) {
|
||||
if (isList() || isMap()) {
|
||||
formatter = indexedValueFormatter;
|
||||
} else {
|
||||
formatter = valueFormatter;
|
||||
|
|
@ -343,7 +363,7 @@ public class PropertyBinding implements Binding {
|
|||
}
|
||||
|
||||
private void assertScalarProperty() {
|
||||
if (isIndexable()) {
|
||||
if (isList()) {
|
||||
throw new IllegalArgumentException("Is a Collection but should be a scalar");
|
||||
}
|
||||
if (isMap()) {
|
||||
|
|
@ -352,75 +372,30 @@ public class PropertyBinding implements Binding {
|
|||
}
|
||||
|
||||
private void assertListProperty() {
|
||||
if (!isIndexable()) {
|
||||
if (!isList()) {
|
||||
throw new IllegalStateException("Not a List property binding");
|
||||
}
|
||||
}
|
||||
|
||||
private void assertMapProperty() {
|
||||
if (!isIndexable()) {
|
||||
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 markedNotEditable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
static class ValueBuffer {
|
||||
|
||||
private Object value;
|
||||
|
||||
private boolean hasValue;
|
||||
|
||||
private Model model;
|
||||
|
||||
private boolean flushFailed;
|
||||
|
||||
private Exception flushFailureCause;
|
||||
|
||||
public ValueBuffer(Model model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public boolean hasValue() {
|
||||
return hasValue;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
if (!hasValue()) {
|
||||
throw new IllegalStateException("No value in buffer");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(Object value) {
|
||||
this.value = value;
|
||||
hasValue = true;
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
try {
|
||||
model.setValue(value);
|
||||
clear();
|
||||
} catch (Exception e) {
|
||||
flushFailed = true;
|
||||
flushFailureCause = e;
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
value = null;
|
||||
hasValue = false;
|
||||
flushFailed = false;
|
||||
}
|
||||
|
||||
public boolean flushFailed() {
|
||||
return flushFailed;
|
||||
}
|
||||
|
||||
public Exception getFlushException() {
|
||||
return flushFailureCause;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import static org.junit.Assert.assertTrue;
|
|||
import java.math.BigDecimal;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
|
@ -21,6 +22,7 @@ import org.springframework.ui.binding.BindingResults;
|
|||
import org.springframework.ui.format.AnnotationFormatterFactory;
|
||||
import org.springframework.ui.format.Formatted;
|
||||
import org.springframework.ui.format.Formatter;
|
||||
import org.springframework.ui.format.date.DateFormatter;
|
||||
import org.springframework.ui.format.number.CurrencyFormat;
|
||||
import org.springframework.ui.format.number.CurrencyFormatter;
|
||||
|
||||
|
|
@ -55,7 +57,6 @@ public class GenericBinderTests {
|
|||
BindingResults results = binder.bind(values);
|
||||
assertEquals(3, results.size());
|
||||
|
||||
System.out.println(results);
|
||||
assertEquals("string", results.get(0).getProperty());
|
||||
assertFalse(results.get(0).isFailure());
|
||||
assertEquals("test", results.get(0).getSourceValue());
|
||||
|
|
@ -87,17 +88,15 @@ public class GenericBinderTests {
|
|||
assertEquals("typeMismatch", results.get(1).getAlert().getCode());
|
||||
}
|
||||
|
||||
/*
|
||||
@Test
|
||||
public void bindSingleValuePropertyFormatter() throws ParseException {
|
||||
BindingRulesBuilder builder = new BindingRulesBuilder(TestBean.class);
|
||||
builder.bind("date").formatWith(new DateFormatter());;
|
||||
GenericBinder binder = new GenericBinder(bean, builder.getBindingRules());
|
||||
|
||||
GenericBinder binder = new GenericBinder(bean);
|
||||
binder.bindingRule("date").formatWith(new DateFormatter());
|
||||
binder.bind(Collections.singletonMap("date", "2009-06-01"));
|
||||
assertEquals(new DateFormatter().parse("2009-06-01", Locale.US), bean.getDate());
|
||||
}
|
||||
|
||||
/*
|
||||
@Test
|
||||
public void bindSingleValuePropertyFormatterParseException() {
|
||||
BindingRulesBuilder builder = new BindingRulesBuilder(TestBean.class);
|
||||
|
|
|
|||
Loading…
Reference in New Issue