You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Binder.java 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  1. /*
  2. * Copyright 2000-2014 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.data;
  17. import java.io.Serializable;
  18. import java.util.ArrayList;
  19. import java.util.LinkedHashSet;
  20. import java.util.List;
  21. import java.util.Locale;
  22. import java.util.Objects;
  23. import java.util.Optional;
  24. import java.util.Set;
  25. import java.util.function.BiConsumer;
  26. import java.util.function.Function;
  27. import java.util.function.Predicate;
  28. import com.vaadin.data.util.converter.Converter;
  29. import com.vaadin.data.util.converter.StringToIntegerConverter;
  30. import com.vaadin.event.Registration;
  31. import com.vaadin.server.ErrorMessage;
  32. import com.vaadin.server.UserError;
  33. import com.vaadin.ui.AbstractComponent;
  34. /**
  35. * Connects one or more {@code Field} components to properties of a backing data
  36. * type such as a bean type. With a binder, input components can be grouped
  37. * together into forms to easily create and update business objects with little
  38. * explicit logic needed to move data between the UI and the data layers of the
  39. * application.
  40. * <p>
  41. * A binder is a collection of <i>bindings</i>, each representing the
  42. * association of a single field and a backing property.
  43. * <p>
  44. * A binder instance can be bound to a single bean instance at a time, but can
  45. * be rebound as needed. This allows usage patterns like a <i>master-details</i>
  46. * view, where a select component is used to pick the bean to edit.
  47. * <p>
  48. * Unless otherwise specified, {@code Binder} method arguments cannot be null.
  49. *
  50. * @author Vaadin Ltd.
  51. *
  52. * @param <BEAN>
  53. * the bean type
  54. *
  55. * @see Binding
  56. * @see HasValue
  57. *
  58. * @since
  59. */
  60. public class Binder<BEAN> implements Serializable {
  61. /**
  62. * Represents the binding between a field and a data property.
  63. *
  64. * @param <BEAN>
  65. * the bean type
  66. * @param <FIELDVALUE>
  67. * the value type of the field
  68. * @param <TARGET>
  69. * the target data type of the binding, matches the field type
  70. * until a converter has been set
  71. *
  72. * @see Binder#forField(HasValue)
  73. */
  74. public interface Binding<BEAN, FIELDVALUE, TARGET> extends Serializable {
  75. /**
  76. * Completes this binding using the given getter and setter functions
  77. * representing a backing bean property. The functions are used to
  78. * update the field value from the property and to store the field value
  79. * to the property, respectively.
  80. * <p>
  81. * When a bean is bound with {@link Binder#bind(BEAN)}, the field value
  82. * is set to the return value of the given getter. The property value is
  83. * then updated via the given setter whenever the field value changes.
  84. * The setter may be null; in that case the property value is never
  85. * updated and the binding is said to be <i>read-only</i>.
  86. * <p>
  87. * If the Binder is already bound to some item, the newly bound field is
  88. * associated with the corresponding bean property as described above.
  89. * <p>
  90. * The getter and setter can be arbitrary functions, for instance
  91. * implementing user-defined conversion or validation. However, in the
  92. * most basic use case you can simply pass a pair of method references
  93. * to this method as follows:
  94. *
  95. * <pre>
  96. * class Person {
  97. * public String getName() { ... }
  98. * public void setName(String name) { ... }
  99. * }
  100. *
  101. * TextField nameField = new TextField();
  102. * binder.forField(nameField).bind(Person::getName, Person::setName);
  103. * </pre>
  104. *
  105. * @param getter
  106. * the function to get the value of the property to the
  107. * field, not null
  108. * @param setter
  109. * the function to save the field value to the property or
  110. * null if read-only
  111. * @throws IllegalStateException
  112. * if {@code bind} has already been called on this binding
  113. */
  114. public void bind(Function<BEAN, TARGET> getter,
  115. BiConsumer<BEAN, TARGET> setter);
  116. /**
  117. * Adds a validator to this binding. Validators are applied, in
  118. * registration order, when the field value is saved to the backing
  119. * property. If any validator returns a failure, the property value is
  120. * not updated.
  121. *
  122. * @param validator
  123. * the validator to add, not null
  124. * @return this binding, for chaining
  125. * @throws IllegalStateException
  126. * if {@code bind} has already been called
  127. */
  128. public Binding<BEAN, FIELDVALUE, TARGET> withValidator(
  129. Validator<? super TARGET> validator);
  130. /**
  131. * A convenience method to add a validator to this binding using the
  132. * {@link Validator#from(Predicate, String)} factory method.
  133. * <p>
  134. * Validators are applied, in registration order, when the field value
  135. * is saved to the backing property. If any validator returns a failure,
  136. * the property value is not updated.
  137. *
  138. * @see #withValidator(Validator)
  139. * @see Validator#from(Predicate, String)
  140. *
  141. * @param predicate
  142. * the predicate performing validation, not null
  143. * @param message
  144. * the error message to report in case validation failure
  145. * @return this binding, for chaining
  146. * @throws IllegalStateException
  147. * if {@code bind} has already been called
  148. */
  149. public Binding<BEAN, FIELDVALUE, TARGET> withValidator(
  150. Predicate<? super TARGET> predicate, String message);
  151. /**
  152. * Maps the binding to another data type using the given
  153. * {@link Converter}.
  154. * <p>
  155. * A converter is capable of converting between a presentation type,
  156. * which must match the current target data type of the binding, and a
  157. * model type, which can be any data type and becomes the new target
  158. * type of the binding. When invoking
  159. * {@link #bind(Function, BiConsumer)}, the target type of the binding
  160. * must match the getter/setter types.
  161. * <p>
  162. * For instance, a {@code TextField} can be bound to an integer-typed
  163. * property using an appropriate converter such as a
  164. * {@link StringToIntegerConverter}.
  165. *
  166. * @param <NEWTARGET>
  167. * the type to convert to
  168. * @param converter
  169. * the converter to use, not null
  170. * @return a new binding with the appropriate type
  171. * @throws IllegalStateException
  172. * if {@code bind} has already been called
  173. */
  174. public <NEWTARGET> Binding<BEAN, FIELDVALUE, NEWTARGET> withConverter(
  175. Converter<TARGET, NEWTARGET> converter);
  176. /**
  177. * Maps the binding to another data type using the mapping functions and
  178. * a possible exception as the error message.
  179. * <p>
  180. * The mapping functions are used to convert between a presentation
  181. * type, which must match the current target data type of the binding,
  182. * and a model type, which can be any data type and becomes the new
  183. * target type of the binding. When invoking
  184. * {@link #bind(Function, BiConsumer)}, the target type of the binding
  185. * must match the getter/setter types.
  186. * <p>
  187. * For instance, a {@code TextField} can be bound to an integer-typed
  188. * property using appropriate functions such as:
  189. * <code>withConverter(Integer::valueOf, String::valueOf);</code>
  190. *
  191. * @param <NEWTARGET>
  192. * the type to convert to
  193. * @param toModel
  194. * the function which can convert from the old target type to
  195. * the new target type
  196. * @param toPresentation
  197. * the function which can convert from the new target type to
  198. * the old target type
  199. * @return a new binding with the appropriate type
  200. * @throws IllegalStateException
  201. * if {@code bind} has already been called
  202. */
  203. default public <NEWTARGET> Binding<BEAN, FIELDVALUE, NEWTARGET> withConverter(
  204. Function<TARGET, NEWTARGET> toModel,
  205. Function<NEWTARGET, TARGET> toPresentation) {
  206. return withConverter(Converter.from(toModel, toPresentation,
  207. exception -> exception.getMessage()));
  208. }
  209. /**
  210. * Maps the binding to another data type using the mapping functions and
  211. * the given error error message if a value cannot be converted to the
  212. * new target type.
  213. * <p>
  214. * The mapping functions are used to convert between a presentation
  215. * type, which must match the current target data type of the binding,
  216. * and a model type, which can be any data type and becomes the new
  217. * target type of the binding. When invoking
  218. * {@link #bind(Function, BiConsumer)}, the target type of the binding
  219. * must match the getter/setter types.
  220. * <p>
  221. * For instance, a {@code TextField} can be bound to an integer-typed
  222. * property using appropriate functions such as:
  223. * <code>withConverter(Integer::valueOf, String::valueOf);</code>
  224. *
  225. * @param <NEWTARGET>
  226. * the type to convert to
  227. * @param toModel
  228. * the function which can convert from the old target type to
  229. * the new target type
  230. * @param toPresentation
  231. * the function which can convert from the new target type to
  232. * the old target type
  233. * @param errorMessage
  234. * the error message to use if conversion using
  235. * <code>toModel</code> fails
  236. * @return a new binding with the appropriate type
  237. * @throws IllegalStateException
  238. * if {@code bind} has already been called
  239. */
  240. public default <NEWTARGET> Binding<BEAN, FIELDVALUE, NEWTARGET> withConverter(
  241. Function<TARGET, NEWTARGET> toModel,
  242. Function<NEWTARGET, TARGET> toPresentation,
  243. String errorMessage) {
  244. return withConverter(Converter.from(toModel, toPresentation,
  245. exception -> errorMessage));
  246. }
  247. /**
  248. * Gets the field the binding uses.
  249. *
  250. * @return the field for the binding
  251. */
  252. public HasValue<FIELDVALUE> getField();
  253. }
  254. /**
  255. * An internal implementation of {@code Binding}.
  256. *
  257. * @param <BEAN>
  258. * the bean type, must match the Binder bean type
  259. * @param <FIELDVALUE>
  260. * the value type of the field
  261. * @param <TARGET>
  262. * the target data type of the binding, matches the field type
  263. * until a converter has been set
  264. */
  265. protected static class BindingImpl<BEAN, FIELDVALUE, TARGET>
  266. implements Binding<BEAN, FIELDVALUE, TARGET> {
  267. private final Binder<BEAN> binder;
  268. private final HasValue<FIELDVALUE> field;
  269. private Registration onValueChange;
  270. private Function<BEAN, TARGET> getter;
  271. private BiConsumer<BEAN, TARGET> setter;
  272. /**
  273. * Contains all converters and validators chained together in the
  274. * correct order.
  275. */
  276. private Converter<FIELDVALUE, TARGET> converterValidatorChain;
  277. /**
  278. * Creates a new binding associated with the given field.
  279. *
  280. * @param binder
  281. * the binder this instance is connected to
  282. * @param field
  283. * the field to bind
  284. */
  285. @SuppressWarnings("unchecked")
  286. protected BindingImpl(Binder<BEAN> binder, HasValue<FIELDVALUE> field) {
  287. this(binder, field,
  288. (Converter<FIELDVALUE, TARGET>) Converter.identity());
  289. }
  290. /**
  291. * Creates a new binding associated with the given field using the given
  292. * converter chain.
  293. *
  294. * @param binder
  295. * the binder this instance is connected to
  296. * @param field
  297. * the field to bind
  298. * @param converterValidatorChain
  299. * the converter/validator chain to use
  300. */
  301. protected BindingImpl(Binder<BEAN> binder, HasValue<FIELDVALUE> field,
  302. Converter<FIELDVALUE, TARGET> converterValidatorChain) {
  303. this.field = field;
  304. this.binder = binder;
  305. this.converterValidatorChain = converterValidatorChain;
  306. }
  307. @Override
  308. public void bind(Function<BEAN, TARGET> getter,
  309. BiConsumer<BEAN, TARGET> setter) {
  310. checkUnbound();
  311. Objects.requireNonNull(getter, "getter cannot be null");
  312. this.getter = getter;
  313. this.setter = setter;
  314. binder.bindings.add(this);
  315. binder.getBean().ifPresent(this::bind);
  316. }
  317. @Override
  318. public Binding<BEAN, FIELDVALUE, TARGET> withValidator(
  319. Validator<? super TARGET> validator) {
  320. checkUnbound();
  321. Objects.requireNonNull(validator, "validator cannot be null");
  322. Converter<TARGET, TARGET> validatorAsConverter = new ValidatorAsConverter<>(
  323. validator);
  324. converterValidatorChain = converterValidatorChain
  325. .chain(validatorAsConverter);
  326. return this;
  327. }
  328. @Override
  329. public Binding<BEAN, FIELDVALUE, TARGET> withValidator(
  330. Predicate<? super TARGET> predicate, String message) {
  331. return withValidator(Validator.from(predicate, message));
  332. }
  333. @Override
  334. public <NEWTARGET> Binding<BEAN, FIELDVALUE, NEWTARGET> withConverter(
  335. Converter<TARGET, NEWTARGET> converter) {
  336. checkUnbound();
  337. Objects.requireNonNull(converter, "converter cannot be null");
  338. BindingImpl<BEAN, FIELDVALUE, NEWTARGET> newBinding = new BindingImpl<>(
  339. binder, field, converterValidatorChain.chain(converter));
  340. return newBinding;
  341. }
  342. private void bind(BEAN bean) {
  343. setFieldValue(bean);
  344. onValueChange = field
  345. .addValueChangeListener(e -> storeFieldValue(bean));
  346. }
  347. private Result<TARGET> validate() {
  348. FIELDVALUE fieldValue = field.getValue();
  349. Result<TARGET> dataValue = converterValidatorChain.convertToModel(
  350. fieldValue, ((AbstractComponent) field).getLocale());
  351. return dataValue;
  352. }
  353. /**
  354. * Returns the field value run through all converters and validators.
  355. *
  356. * @return an optional containing the validated and converted value or
  357. * an empty optional if a validator or converter failed
  358. */
  359. private Optional<TARGET> getTargetValue() {
  360. return validate().getValue();
  361. }
  362. private void unbind() {
  363. onValueChange.remove();
  364. }
  365. /**
  366. * Sets the field value by invoking the getter function on the given
  367. * bean.
  368. *
  369. * @param bean
  370. * the bean to fetch the property value from
  371. */
  372. private void setFieldValue(BEAN bean) {
  373. assert bean != null;
  374. field.setValue(convertDataToFieldType(bean));
  375. }
  376. private FIELDVALUE convertDataToFieldType(BEAN bean) {
  377. return converterValidatorChain.convertToPresentation(
  378. getter.apply(bean),
  379. ((AbstractComponent) field).getLocale());
  380. }
  381. /**
  382. * Saves the field value by invoking the setter function on the given
  383. * bean, if the value passes all registered validators.
  384. *
  385. * @param bean
  386. * the bean to set the property value to
  387. */
  388. private void storeFieldValue(BEAN bean) {
  389. assert bean != null;
  390. if (setter != null) {
  391. getTargetValue().ifPresent(value -> setter.accept(bean, value));
  392. }
  393. }
  394. private void checkUnbound() {
  395. if (getter != null) {
  396. throw new IllegalStateException(
  397. "cannot modify binding: already bound to a property");
  398. }
  399. }
  400. @Override
  401. public HasValue<FIELDVALUE> getField() {
  402. return field;
  403. }
  404. }
  405. /**
  406. * Wraps a validator as a converter.
  407. * <p>
  408. * The type of the validator must be of the same type as this converter or a
  409. * super type of it.
  410. *
  411. * @param <T>
  412. * the type of the converter
  413. */
  414. private static class ValidatorAsConverter<T> implements Converter<T, T> {
  415. private Validator<? super T> validator;
  416. /**
  417. * Creates a new converter wrapping the given validator.
  418. *
  419. * @param validator
  420. * the validator to wrap
  421. */
  422. public ValidatorAsConverter(Validator<? super T> validator) {
  423. this.validator = validator;
  424. }
  425. @Override
  426. public Result<T> convertToModel(T value, Locale locale) {
  427. Result<? super T> validationResult = validator.apply(value);
  428. if (validationResult.isError()) {
  429. return Result.error(validationResult.getMessage().get());
  430. } else {
  431. return Result.ok(value);
  432. }
  433. }
  434. @Override
  435. public T convertToPresentation(T value, Locale locale) {
  436. return value;
  437. }
  438. }
  439. private BEAN bean;
  440. private Set<BindingImpl<BEAN, ?, ?>> bindings = new LinkedHashSet<>();
  441. /**
  442. * Returns an {@code Optional} of the bean that has been bound with
  443. * {@link #bind}, or an empty optional if a bean is not currently bound.
  444. *
  445. * @return the currently bound bean if any
  446. */
  447. public Optional<BEAN> getBean() {
  448. return Optional.ofNullable(bean);
  449. }
  450. /**
  451. * Creates a new binding for the given field. The returned binding may be
  452. * further configured before invoking
  453. * {@link Binding#bind(Function, BiConsumer) Binding.bind} which completes
  454. * the binding. Until {@code Binding.bind} is called, the binding has no
  455. * effect.
  456. *
  457. * @param <FIELDVALUE>
  458. * the value type of the field
  459. * @param field
  460. * the field to be bound, not null
  461. * @return the new binding
  462. */
  463. public <FIELDVALUE> Binding<BEAN, FIELDVALUE, FIELDVALUE> forField(
  464. HasValue<FIELDVALUE> field) {
  465. return createBinding(field);
  466. }
  467. /**
  468. * Binds a field to a bean property represented by the given getter and
  469. * setter pair. The functions are used to update the field value from the
  470. * property and to store the field value to the property, respectively.
  471. * <p>
  472. * Use the {@link #forField(HasValue)} overload instead if you want to
  473. * further configure the new binding.
  474. * <p>
  475. * When a bean is bound with {@link Binder#bind(BEAN)}, the field value is
  476. * set to the return value of the given getter. The property value is then
  477. * updated via the given setter whenever the field value changes. The setter
  478. * may be null; in that case the property value is never updated and the
  479. * binding is said to be <i>read-only</i>.
  480. * <p>
  481. * If the Binder is already bound to some item, the newly bound field is
  482. * associated with the corresponding bean property as described above.
  483. * <p>
  484. * The getter and setter can be arbitrary functions, for instance
  485. * implementing user-defined conversion or validation. However, in the most
  486. * basic use case you can simply pass a pair of method references to this
  487. * method as follows:
  488. *
  489. * <pre>
  490. * class Person {
  491. * public String getName() { ... }
  492. * public void setName(String name) { ... }
  493. * }
  494. *
  495. * TextField nameField = new TextField();
  496. * binder.bind(nameField, Person::getName, Person::setName);
  497. * </pre>
  498. *
  499. * @param <FIELDVALUE>
  500. * the value type of the field
  501. * @param field
  502. * the field to bind, not null
  503. * @param getter
  504. * the function to get the value of the property to the field,
  505. * not null
  506. * @param setter
  507. * the function to save the field value to the property or null
  508. * if read-only
  509. */
  510. public <FIELDVALUE> void bind(HasValue<FIELDVALUE> field,
  511. Function<BEAN, FIELDVALUE> getter,
  512. BiConsumer<BEAN, FIELDVALUE> setter) {
  513. forField(field).bind(getter, setter);
  514. }
  515. /**
  516. * Binds the given bean to all the fields added to this Binder. To remove
  517. * the binding, call {@link #unbind()}.
  518. * <p>
  519. * When a bean is bound, the field values are updated by invoking their
  520. * corresponding getter functions. Any changes to field values are reflected
  521. * back to their corresponding property values of the bean as long as the
  522. * bean is bound.
  523. *
  524. * @param bean
  525. * the bean to edit, not null
  526. */
  527. public void bind(BEAN bean) {
  528. Objects.requireNonNull(bean, "bean cannot be null");
  529. unbind();
  530. this.bean = bean;
  531. bindings.forEach(b -> b.bind(bean));
  532. }
  533. /**
  534. * Validates the values of all bound fields and returns the result of the
  535. * validation as a set of validation errors.
  536. * <p>
  537. * Validation is successful if the resulting set is empty.
  538. *
  539. * @return the validation result.
  540. */
  541. public List<ValidationError<?>> validate() {
  542. List<ValidationError<?>> resultErrors = new ArrayList<>();
  543. for (BindingImpl<BEAN, ?, ?> binding : bindings) {
  544. clearError(binding.field);
  545. binding.validate().ifError(errorMessage -> {
  546. resultErrors.add(
  547. new ValidationError<>(binding.field, errorMessage));
  548. handleError(binding.field, errorMessage);
  549. });
  550. }
  551. return resultErrors;
  552. }
  553. /**
  554. * Unbinds the currently bound bean if any. If there is no bound bean, does
  555. * nothing.
  556. */
  557. public void unbind() {
  558. if (bean != null) {
  559. bean = null;
  560. bindings.forEach(BindingImpl::unbind);
  561. }
  562. }
  563. /**
  564. * Reads the bound property values from the given bean to the corresponding
  565. * fields. The bean is not otherwise associated with this binder; in
  566. * particular its property values are not bound to the field value changes.
  567. * To achieve that, use {@link #bind(BEAN)}.
  568. *
  569. * @param bean
  570. * the bean whose property values to read, not null
  571. */
  572. public void load(BEAN bean) {
  573. Objects.requireNonNull(bean, "bean cannot be null");
  574. bindings.forEach(binding -> binding.setFieldValue(bean));
  575. }
  576. /**
  577. * Saves changes from the bound fields to the edited bean. If any value
  578. * fails validation, no values are saved and an {@code BindingException} is
  579. * thrown.
  580. *
  581. * @param bean
  582. * the object to which to save the field values, not null
  583. * @throws BindingException
  584. * if some of the bound field values fail to validate
  585. */
  586. public void save(BEAN bean) {
  587. Objects.requireNonNull(bean, "bean cannot be null");
  588. bindings.forEach(binding -> binding.storeFieldValue(bean));
  589. }
  590. /**
  591. * Creates a new binding with the given field.
  592. *
  593. * @param <FIELDVALUE>
  594. * the value type of the field
  595. * @param field
  596. * the field to bind
  597. * @return the new incomplete binding
  598. */
  599. protected <FIELDVALUE> BindingImpl<BEAN, FIELDVALUE, FIELDVALUE> createBinding(
  600. HasValue<FIELDVALUE> field) {
  601. Objects.requireNonNull(field, "field cannot be null");
  602. BindingImpl<BEAN, FIELDVALUE, FIELDVALUE> b = new BindingImpl<BEAN, FIELDVALUE, FIELDVALUE>(
  603. this, field);
  604. return b;
  605. }
  606. /**
  607. * Clears the error condition of the given field, if any. The default
  608. * implementation clears the
  609. * {@link AbstractComponent#setComponentError(ErrorMessage) component error}
  610. * of the field if it is a Component, otherwise does nothing.
  611. *
  612. * @param field
  613. * the field with an invalid value
  614. */
  615. protected void clearError(HasValue<?> field) {
  616. if (field instanceof AbstractComponent) {
  617. ((AbstractComponent) field).setComponentError(null);
  618. }
  619. }
  620. /**
  621. * Handles a validation error emitted when trying to save the value of the
  622. * given field. The default implementation sets the
  623. * {@link AbstractComponent#setComponentError(ErrorMessage) component error}
  624. * of the field if it is a Component, otherwise does nothing.
  625. *
  626. * @param field
  627. * the field with the invalid value
  628. * @param error
  629. * the error message to set
  630. */
  631. protected void handleError(HasValue<?> field, String error) {
  632. if (field instanceof AbstractComponent) {
  633. ((AbstractComponent) field).setComponentError(new UserError(error));
  634. }
  635. }
  636. }