From 9ea656ab97f19dfd99fad875e8fc589b512e0066 Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Wed, 10 Aug 2016 17:41:16 +0300 Subject: Refactor Binder and Binding/BindingImpl to be easier to understand Change-Id: I675dabf9f15b673b04495db9efd315d8742afd7e --- server/src/main/java/com/vaadin/data/Binder.java | 214 +++++++++++++---------- 1 file changed, 121 insertions(+), 93 deletions(-) (limited to 'server/src/main') diff --git a/server/src/main/java/com/vaadin/data/Binder.java b/server/src/main/java/com/vaadin/data/Binder.java index a34db33e51..0fbacce994 100644 --- a/server/src/main/java/com/vaadin/data/Binder.java +++ b/server/src/main/java/com/vaadin/data/Binder.java @@ -1,12 +1,12 @@ /* * Copyright 2000-2014 Vaadin Ltd. - * + * * 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 @@ -46,30 +46,33 @@ import com.vaadin.ui.AbstractComponent; * view, where a select component is used to pick the bean to edit. *

* Unless otherwise specified, {@code Binder} method arguments cannot be null. - * + * * @author Vaadin Ltd. * - * @param + * @param * the bean type - * + * * @see Binding * @see HasValue - * + * * @since */ -public class Binder implements Serializable { +public class Binder implements Serializable { /** - * Represents the binding between a single field and a property. + * Represents the binding between a field and a data property. + * + * @param + * the bean type + * @param + * the value type of the field + * @param + * the target data type of the binding, matches the field type + * until a converter has been set * - * @param - * the item type - * @param - * the field value type - * * @see Binder#forField(HasValue) */ - public interface Binding extends Serializable { + public interface Binding extends Serializable { /** * Completes this binding using the given getter and setter functions @@ -77,8 +80,8 @@ public class Binder implements Serializable { * update the field value from the property and to store the field value * to the property, respectively. *

- * When a bean is bound with {@link Binder#bind(T)}, the field value is - * set to the return value of the given getter. The property value is + * When a bean is bound with {@link Binder#bind(BEAN)}, the field value + * is set to the return value of the given getter. The property value is * then updated via the given setter whenever the field value changes. * The setter may be null; in that case the property value is never * updated and the binding is said to be read-only. @@ -90,17 +93,17 @@ public class Binder implements Serializable { * implementing user-defined conversion or validation. However, in the * most basic use case you can simply pass a pair of method references * to this method as follows: - * + * *

          * class Person {
          *     public String getName() { ... }
          *     public void setName(String name) { ... }
          * }
-         * 
+         *
          * TextField nameField = new TextField();
          * binder.forField(nameField).bind(Person::getName, Person::setName);
          * 
- * + * * @param getter * the function to get the value of the property to the * field, not null @@ -110,21 +113,23 @@ public class Binder implements Serializable { * @throws IllegalStateException * if {@code bind} has already been called on this binding */ - public void bind(Function getter, BiConsumer setter); + public void bind(Function getter, + BiConsumer setter); /** * Adds a validator to this binding. Validators are applied, in * registration order, when the field value is saved to the backing * property. If any validator returns a failure, the property value is * not updated. - * + * * @param validator * the validator to add, not null * @return this binding, for chaining * @throws IllegalStateException * if {@code bind} has already been called */ - public Binding withValidator(Validator validator); + public Binding withValidator( + Validator validator); /** * A convenience method to add a validator to this binding using the @@ -133,10 +138,10 @@ public class Binder implements Serializable { * Validators are applied, in registration order, when the field value * is saved to the backing property. If any validator returns a failure, * the property value is not updated. - * + * * @see #withValidator(Validator) * @see Validator#from(Predicate, String) - * + * * @param predicate * the predicate performing validation, not null * @param message @@ -145,51 +150,70 @@ public class Binder implements Serializable { * @throws IllegalStateException * if {@code bind} has already been called */ - public Binding withValidator(Predicate predicate, - String message); + public Binding withValidator( + Predicate predicate, String message); + + /** + * Gets the field the binding uses. + * + * @return the field for the binding + */ + public HasValue getField(); + } /** * An internal implementation of {@code Binding}. - * - * @param - * the value type + * + * @param + * the bean type, must match the Binder bean type + * @param + * the value type of the field + * @param + * the target data type of the binding, matches the field type + * until a converter has been set */ - protected class BindingImpl implements Binding { + protected static class BindingImpl + implements Binding { + + private Binder binder; - private HasValue field; + private HasValue field; private Registration onValueChange; - private Function getter; - private BiConsumer setter; + private Function getter; + private BiConsumer setter; - private List> validators = new ArrayList<>(); + private List> validators = new ArrayList<>(); /** * Creates a new binding associated with the given field. - * + * + * @param binder + * the binder this instance is connected to * @param field * the field to bind */ - protected BindingImpl(HasValue field) { + protected BindingImpl(Binder binder, HasValue field) { + this.binder = binder; this.field = field; } @Override - public void bind(Function getter, BiConsumer setter) { + public void bind(Function getter, + BiConsumer setter) { checkUnbound(); Objects.requireNonNull(getter, "getter cannot be null"); this.getter = getter; this.setter = setter; - bindings.add(this); - if (bean != null) { - bind(bean); - } + binder.bindings.add(this); + binder.getBean().ifPresent(this::bind); } @Override - public Binding withValidator(Validator validator) { + public Binding withValidator( + Validator validator) { checkUnbound(); Objects.requireNonNull(validator, "validator cannot be null"); validators.add(validator); @@ -197,20 +221,21 @@ public class Binder implements Serializable { } @Override - public Binding withValidator(Predicate predicate, - String message) { + public Binding withValidator( + Predicate predicate, String message) { return withValidator(Validator.from(predicate, message)); } - private void bind(T bean) { + private void bind(BEAN bean) { setFieldValue(bean); onValueChange = field .addValueChangeListener(e -> storeFieldValue(bean)); } - private List> validate() { + private List> validate() { return validators.stream() - .map(validator -> validator.apply(field.getValue())) + .map(validator -> validator + .apply((TARGET) field.getValue())) .filter(Result::isError) .map(result -> new ValidationError<>(field, result.getMessage().orElse(null))) @@ -224,26 +249,26 @@ public class Binder implements Serializable { /** * Sets the field value by invoking the getter function on the given * bean. - * + * * @param bean * the bean to fetch the property value from */ - private void setFieldValue(T bean) { + private void setFieldValue(BEAN bean) { assert bean != null; - field.setValue(getter.apply(bean)); + field.setValue((FIELDVALUE) getter.apply(bean)); } /** * Saves the field value by invoking the setter function on the given * bean, if the value passes all registered validators. - * + * * @param bean * the bean to set the property value to */ - private void storeFieldValue(T bean) { + private void storeFieldValue(BEAN bean) { assert bean != null; if (setter != null) { - setter.accept(bean, field.getValue()); + setter.accept(bean, (TARGET) field.getValue()); } } @@ -254,19 +279,23 @@ public class Binder implements Serializable { } } + @Override + public HasValue getField() { + return field; + } } - private T bean; + private BEAN bean; - private Set> bindings = new LinkedHashSet<>(); + private Set> bindings = new LinkedHashSet<>(); /** * Returns an {@code Optional} of the bean that has been bound with * {@link #bind}, or an empty optional if a bean is not currently bound. - * + * * @return the currently bound bean if any */ - public Optional getBean() { + public Optional getBean() { return Optional.ofNullable(bean); } @@ -276,14 +305,15 @@ public class Binder implements Serializable { * {@link Binding#bind(Function, BiConsumer) Binding.bind} which completes * the binding. Until {@code Binding.bind} is called, the binding has no * effect. - * - * @param + * + * @param * the value type of the field * @param field * the field to be bound, not null * @return the new binding */ - public Binding forField(HasValue field) { + public Binding forField( + HasValue field) { return createBinding(field); } @@ -295,8 +325,8 @@ public class Binder implements Serializable { * Use the {@link #forField(HasValue)} overload instead if you want to * further configure the new binding. *

- * When a bean is bound with {@link Binder#bind(T)}, the field value is set - * to the return value of the given getter. The property value is then + * When a bean is bound with {@link Binder#bind(BEAN)}, the field value is + * set to the return value of the given getter. The property value is then * updated via the given setter whenever the field value changes. The setter * may be null; in that case the property value is never updated and the * binding is said to be read-only. @@ -308,18 +338,18 @@ public class Binder implements Serializable { * implementing user-defined conversion or validation. However, in the most * basic use case you can simply pass a pair of method references to this * method as follows: - * + * *

      * class Person {
      *     public String getName() { ... }
      *     public void setName(String name) { ... }
      * }
-     * 
+     *
      * TextField nameField = new TextField();
      * binder.bind(nameField, Person::getName, Person::setName);
      * 
- * - * @param + * + * @param * the value type of the field * @param field * the field to bind, not null @@ -330,8 +360,9 @@ public class Binder implements Serializable { * the function to save the field value to the property or null * if read-only */ - public void bind(HasValue field, Function getter, - BiConsumer setter) { + public void bind(HasValue field, + Function getter, + BiConsumer setter) { forField(field).bind(getter, setter); } @@ -343,11 +374,11 @@ public class Binder implements Serializable { * corresponding getter functions. Any changes to field values are reflected * back to their corresponding property values of the bean as long as the * bean is bound. - * + * * @param bean * the bean to edit, not null */ - public void bind(T bean) { + public void bind(BEAN bean) { Objects.requireNonNull(bean, "bean cannot be null"); unbind(); this.bean = bean; @@ -359,17 +390,17 @@ public class Binder implements Serializable { * validation as a set of validation errors. *

* Validation is successful if the resulting set is empty. - * + * * @return the validation result. */ public List> validate() { List> resultErrors = new ArrayList<>(); - for (BindingImpl binding : bindings) { - clearError(binding.field); + for (BindingImpl binding : bindings) { + clearError(binding.getField()); List> errors = binding.validate(); resultErrors.addAll(errors); if (!errors.isEmpty()) { - handleError(binding.field, errors.get(0).getMessage()); + handleError(binding.getField(), errors.get(0).getMessage()); } } return resultErrors; @@ -390,16 +421,14 @@ public class Binder implements Serializable { * Reads the bound property values from the given bean to the corresponding * fields. The bean is not otherwise associated with this binder; in * particular its property values are not bound to the field value changes. - * To achieve that, use {@link #bind(T)}. - * + * To achieve that, use {@link #bind(BEAN)}. + * * @param bean * the bean whose property values to read, not null */ - public void load(T bean) { + public void load(BEAN bean) { Objects.requireNonNull(bean, "bean cannot be null"); - bindings.forEach( - - binding -> binding.setFieldValue(bean)); + bindings.forEach(binding -> binding.setFieldValue(bean)); } @@ -413,26 +442,25 @@ public class Binder implements Serializable { * @throws BindingException * if some of the bound field values fail to validate */ - public void save(T bean) { + public void save(BEAN bean) { Objects.requireNonNull(bean, "bean cannot be null"); - bindings.forEach( - - binding -> binding.storeFieldValue(bean)); - + bindings.forEach(binding -> binding.storeFieldValue(bean)); } /** * Creates a new binding with the given field. - * - * @param - * the field value type + * + * @param + * the value type of the field * @param field * the field to bind * @return the new incomplete binding */ - protected BindingImpl createBinding(HasValue field) { + protected Binding createBinding( + HasValue field) { Objects.requireNonNull(field, "field cannot be null"); - BindingImpl b = new BindingImpl<>(field); + BindingImpl b = new BindingImpl<>(this, + field); return b; } @@ -441,7 +469,7 @@ public class Binder implements Serializable { * implementation clears the * {@link AbstractComponent#setComponentError(ErrorMessage) component error} * of the field if it is a Component, otherwise does nothing. - * + * * @param field * the field with an invalid value */ @@ -456,7 +484,7 @@ public class Binder implements Serializable { * given field. The default implementation sets the * {@link AbstractComponent#setComponentError(ErrorMessage) component error} * of the field if it is a Component, otherwise does nothing. - * + * * @param field * the field with the invalid value * @param error -- cgit v1.2.3