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.

BeanBinder.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. /*
  2. * Copyright 2000-2016 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.beans.IntrospectionException;
  18. import java.beans.PropertyDescriptor;
  19. import java.lang.reflect.InvocationTargetException;
  20. import java.lang.reflect.Method;
  21. import java.util.Locale;
  22. import java.util.Objects;
  23. import java.util.function.Function;
  24. import java.util.function.Predicate;
  25. import com.vaadin.data.util.BeanUtil;
  26. import com.vaadin.data.util.converter.Converter;
  27. import com.vaadin.data.validator.BeanValidator;
  28. import com.vaadin.ui.Component;
  29. import com.vaadin.ui.UI;
  30. /**
  31. * A {@code Binder} subclass specialized for binding <em>beans</em>: classes
  32. * that conform to the JavaBeans specification. Bean properties are bound by
  33. * their names. If a JSR-303 bean validation implementation is present on the
  34. * classpath, {@code BeanBinder} adds a {@link BeanValidator} to each binding.
  35. *
  36. * @author Vaadin Ltd.
  37. *
  38. * @param <BEAN>
  39. * the bean type
  40. *
  41. * @since
  42. */
  43. public class BeanBinder<BEAN> extends Binder<BEAN> {
  44. /**
  45. * Represents the binding between a single field and a bean property.
  46. *
  47. * @param <BEAN>
  48. * the bean type
  49. * @param <FIELDVALUE>
  50. * the field value type
  51. * @param <TARGET>
  52. * the target property type
  53. */
  54. public interface BeanBinding<BEAN, FIELDVALUE, TARGET>
  55. extends Binding<BEAN, FIELDVALUE, TARGET> {
  56. @Override
  57. public BeanBinding<BEAN, FIELDVALUE, TARGET> withValidator(
  58. Validator<? super TARGET> validator);
  59. @Override
  60. public default BeanBinding<BEAN, FIELDVALUE, TARGET> withValidator(
  61. Predicate<? super TARGET> predicate, String message) {
  62. return (BeanBinding<BEAN, FIELDVALUE, TARGET>) Binding.super.withValidator(
  63. predicate, message);
  64. }
  65. @Override
  66. public <NEWTARGET> BeanBinding<BEAN, FIELDVALUE, NEWTARGET> withConverter(
  67. Converter<TARGET, NEWTARGET> converter);
  68. @Override
  69. public default <NEWTARGET> BeanBinding<BEAN, FIELDVALUE, NEWTARGET> withConverter(
  70. Function<TARGET, NEWTARGET> toModel,
  71. Function<NEWTARGET, TARGET> toPresentation) {
  72. return (BeanBinding<BEAN, FIELDVALUE, NEWTARGET>) Binding.super.withConverter(
  73. toModel, toPresentation);
  74. }
  75. @Override
  76. public default <NEWTARGET> BeanBinding<BEAN, FIELDVALUE, NEWTARGET> withConverter(
  77. Function<TARGET, NEWTARGET> toModel,
  78. Function<NEWTARGET, TARGET> toPresentation,
  79. String errorMessage) {
  80. return (BeanBinding<BEAN, FIELDVALUE, NEWTARGET>) Binding.super.withConverter(
  81. toModel, toPresentation, errorMessage);
  82. }
  83. /**
  84. * Completes this binding by connecting the field to the property with
  85. * the given name. The getter and setter methods of the property are
  86. * looked up with bean introspection and used to read and write the
  87. * property value.
  88. * <p>
  89. * If a JSR-303 bean validation implementation is present on the
  90. * classpath, adds a {@link BeanValidator} to this binding.
  91. * <p>
  92. * The property must have an accessible getter method. It need not have
  93. * an accessible setter; in that case the property value is never
  94. * updated and the binding is said to be <i>read-only</i>.
  95. *
  96. * @param propertyName
  97. * the name of the property to bind, not null
  98. *
  99. * @throws IllegalArgumentException
  100. * if the property name is invalid
  101. * @throws IllegalArgumentException
  102. * if the property has no accessible getter
  103. *
  104. * @see Binding#bind(Function, java.util.function.BiConsumer)
  105. */
  106. public void bind(String propertyName);
  107. }
  108. /**
  109. * An internal implementation of {@link BeanBinding}.
  110. *
  111. * @param <BEAN>
  112. * the bean type
  113. * @param <FIELDVALUE>
  114. * the field value type
  115. * @param <TARGET>
  116. * the target property type
  117. */
  118. protected static class BeanBindingImpl<BEAN, FIELDVALUE, TARGET>
  119. extends BindingImpl<BEAN, FIELDVALUE, TARGET>
  120. implements BeanBinding<BEAN, FIELDVALUE, TARGET> {
  121. private Method getter;
  122. private Method setter;
  123. /**
  124. * Creates a new bean binding.
  125. *
  126. * @param binder
  127. * the binder this instance is connected to, not null
  128. * @param field
  129. * the field to use, not null
  130. * @param converter
  131. * the initial converter to use, not null
  132. * @param statusChangeHandler
  133. * the handler to notify of status changes, not null
  134. */
  135. protected BeanBindingImpl(BeanBinder<BEAN> binder,
  136. HasValue<FIELDVALUE> field,
  137. Converter<FIELDVALUE, TARGET> converter,
  138. StatusChangeHandler statusChangeHandler) {
  139. super(binder, field, converter, statusChangeHandler);
  140. }
  141. @Override
  142. public BeanBinding<BEAN, FIELDVALUE, TARGET> withValidator(
  143. Validator<? super TARGET> validator) {
  144. return (BeanBinding<BEAN, FIELDVALUE, TARGET>) super.withValidator(
  145. validator);
  146. }
  147. @Override
  148. public <NEWTARGET> BeanBinding<BEAN, FIELDVALUE, NEWTARGET> withConverter(
  149. Converter<TARGET, NEWTARGET> converter) {
  150. return (BeanBinding<BEAN, FIELDVALUE, NEWTARGET>) super.withConverter(
  151. converter);
  152. }
  153. @Override
  154. public void bind(String propertyName) {
  155. checkUnbound();
  156. Binding<BEAN, FIELDVALUE, Object> finalBinding;
  157. finalBinding = withConverter(createConverter());
  158. if (BeanValidator.checkBeanValidationAvailable()) {
  159. finalBinding = finalBinding.withValidator(new BeanValidator(
  160. getBinder().beanType, propertyName, getLocale()));
  161. }
  162. PropertyDescriptor descriptor = getDescriptor(propertyName);
  163. getter = descriptor.getReadMethod();
  164. setter = descriptor.getWriteMethod();
  165. finalBinding.bind(this::getValue, this::setValue);
  166. }
  167. @Override
  168. protected BeanBinder<BEAN> getBinder() {
  169. return (BeanBinder<BEAN>) super.getBinder();
  170. }
  171. private void setValue(BEAN bean, Object value) {
  172. try {
  173. if (setter != null) {
  174. setter.invoke(bean, value);
  175. }
  176. } catch (IllegalAccessException | InvocationTargetException e) {
  177. throw new RuntimeException(e);
  178. }
  179. }
  180. private Object getValue(BEAN bean) {
  181. try {
  182. return getter.invoke(bean);
  183. } catch (IllegalAccessException | InvocationTargetException e) {
  184. throw new RuntimeException(e);
  185. }
  186. }
  187. private PropertyDescriptor getDescriptor(String propertyName) {
  188. final Class<?> beanType = getBinder().beanType;
  189. PropertyDescriptor descriptor = null;
  190. try {
  191. descriptor = BeanUtil.getPropertyDescriptor(beanType,
  192. propertyName);
  193. } catch (IntrospectionException ie) {
  194. throw new IllegalArgumentException(
  195. "Could not resolve bean property name (see the cause): "
  196. + beanType.getName() + "." + propertyName,
  197. ie);
  198. }
  199. if (descriptor == null) {
  200. throw new IllegalArgumentException(
  201. "Could not resolve bean property name (please check spelling and getter visibility): "
  202. + beanType.getName() + "." + propertyName);
  203. }
  204. if (descriptor.getReadMethod() == null) {
  205. throw new IllegalArgumentException(
  206. "Bean property has no accessible getter: "
  207. + beanType.getName() + "." + propertyName);
  208. }
  209. return descriptor;
  210. }
  211. @SuppressWarnings("unchecked")
  212. private Converter<TARGET, Object> createConverter() {
  213. return Converter.from(
  214. fieldValue -> getter.getReturnType().cast(fieldValue),
  215. propertyValue -> (TARGET) propertyValue, exception -> {
  216. throw new RuntimeException(exception);
  217. });
  218. }
  219. private Locale getLocale() {
  220. Locale l = null;
  221. if (getField() instanceof Component) {
  222. l = ((Component) getField()).getLocale();
  223. }
  224. if (l == null && UI.getCurrent() != null) {
  225. l = UI.getCurrent().getLocale();
  226. }
  227. if (l == null) {
  228. l = Locale.getDefault();
  229. }
  230. return l;
  231. }
  232. }
  233. private final Class<? extends BEAN> beanType;
  234. /**
  235. * Creates a new {@code BeanBinder} supporting beans of the given type.
  236. *
  237. * @param beanType
  238. * the bean {@code Class} instance, not null
  239. */
  240. public BeanBinder(Class<? extends BEAN> beanType) {
  241. BeanValidator.checkBeanValidationAvailable();
  242. this.beanType = beanType;
  243. }
  244. @Override
  245. public <FIELDVALUE> BeanBinding<BEAN, FIELDVALUE, FIELDVALUE> forField(
  246. HasValue<FIELDVALUE> field) {
  247. return createBinding(field, Converter.identity(),
  248. this::handleValidationStatusChange);
  249. }
  250. /**
  251. * Binds the given field to the property with the given name. The getter and
  252. * setter methods of the property are looked up with bean introspection and
  253. * used to read and write the property value.
  254. * <p>
  255. * Use the {@link #forField(HasValue)} overload instead if you want to
  256. * further configure the new binding.
  257. * <p>
  258. * The property must have an accessible getter method. It need not have an
  259. * accessible setter; in that case the property value is never updated and
  260. * the binding is said to be <i>read-only</i>.
  261. *
  262. * @param <FIELDVALUE>
  263. * the value type of the field to bind
  264. * @param field
  265. * the field to bind, not null
  266. * @param propertyName
  267. * the name of the property to bind, not null
  268. *
  269. * @throws IllegalArgumentException
  270. * if the property name is invalid
  271. * @throws IllegalArgumentException
  272. * if the property has no accessible getter
  273. *
  274. * @see #bind(HasValue, java.util.function.Function,
  275. * java.util.function.BiConsumer)
  276. */
  277. public <FIELDVALUE> void bind(HasValue<FIELDVALUE> field,
  278. String propertyName) {
  279. forField(field).bind(propertyName);
  280. }
  281. @Override
  282. protected <FIELDVALUE, TARGET> BeanBindingImpl<BEAN, FIELDVALUE, TARGET> createBinding(
  283. HasValue<FIELDVALUE> field, Converter<FIELDVALUE, TARGET> converter,
  284. StatusChangeHandler handler) {
  285. Objects.requireNonNull(field, "field cannot be null");
  286. Objects.requireNonNull(converter, "converter cannot be null");
  287. return new BeanBindingImpl<>(this, field, converter, handler);
  288. }
  289. }