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 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  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.Field;
  20. import java.lang.reflect.InvocationTargetException;
  21. import java.lang.reflect.Method;
  22. import java.lang.reflect.Type;
  23. import java.util.ArrayList;
  24. import java.util.Arrays;
  25. import java.util.HashSet;
  26. import java.util.List;
  27. import java.util.Locale;
  28. import java.util.Objects;
  29. import java.util.Optional;
  30. import java.util.Set;
  31. import java.util.function.BiConsumer;
  32. import com.googlecode.gentyref.GenericTypeReflector;
  33. import com.vaadin.annotations.PropertyId;
  34. import com.vaadin.data.util.BeanUtil;
  35. import com.vaadin.data.util.converter.Converter;
  36. import com.vaadin.data.validator.BeanValidator;
  37. import com.vaadin.server.SerializableBiConsumer;
  38. import com.vaadin.server.SerializableFunction;
  39. import com.vaadin.server.SerializablePredicate;
  40. import com.vaadin.ui.Label;
  41. import com.vaadin.util.ReflectTools;
  42. /**
  43. * A {@code Binder} subclass specialized for binding <em>beans</em>: classes
  44. * that conform to the JavaBeans specification. Bean properties are bound by
  45. * their names. If a JSR-303 bean validation implementation is present on the
  46. * classpath, {@code BeanBinder} adds a {@link BeanValidator} to each binding.
  47. *
  48. * @author Vaadin Ltd.
  49. *
  50. * @param <BEAN>
  51. * the bean type
  52. *
  53. * @since 8.0
  54. */
  55. public class BeanBinder<BEAN> extends Binder<BEAN> {
  56. /**
  57. * Represents the binding between a single field and a bean property.
  58. *
  59. * @param <BEAN>
  60. * the bean type
  61. * @param <TARGET>
  62. * the target property type
  63. */
  64. public interface BeanBindingBuilder<BEAN, TARGET>
  65. extends BindingBuilder<BEAN, TARGET> {
  66. @Override
  67. public BeanBindingBuilder<BEAN, TARGET> withValidator(
  68. Validator<? super TARGET> validator);
  69. @Override
  70. public default BeanBindingBuilder<BEAN, TARGET> withValidator(
  71. SerializablePredicate<? super TARGET> predicate,
  72. String message) {
  73. return (BeanBindingBuilder<BEAN, TARGET>) BindingBuilder.super.withValidator(
  74. predicate, message);
  75. }
  76. @Override
  77. default BeanBindingBuilder<BEAN, TARGET> withValidator(
  78. SerializablePredicate<? super TARGET> predicate,
  79. ErrorMessageProvider errorMessageProvider) {
  80. return (BeanBindingBuilder<BEAN, TARGET>) BindingBuilder.super.withValidator(
  81. predicate, errorMessageProvider);
  82. }
  83. @Override
  84. default BeanBindingBuilder<BEAN, TARGET> withNullRepresentation(
  85. TARGET nullRepresentation) {
  86. return (BeanBindingBuilder<BEAN, TARGET>) BindingBuilder.super.withNullRepresentation(
  87. nullRepresentation);
  88. }
  89. @Override
  90. public BeanBindingBuilder<BEAN, TARGET> setRequired(
  91. ErrorMessageProvider errorMessageProvider);
  92. @Override
  93. public default BeanBindingBuilder<BEAN, TARGET> setRequired(
  94. String errorMessage) {
  95. return (BeanBindingBuilder<BEAN, TARGET>) BindingBuilder.super.setRequired(
  96. errorMessage);
  97. }
  98. @Override
  99. public <NEWTARGET> BeanBindingBuilder<BEAN, NEWTARGET> withConverter(
  100. Converter<TARGET, NEWTARGET> converter);
  101. @Override
  102. public default <NEWTARGET> BeanBindingBuilder<BEAN, NEWTARGET> withConverter(
  103. SerializableFunction<TARGET, NEWTARGET> toModel,
  104. SerializableFunction<NEWTARGET, TARGET> toPresentation) {
  105. return (BeanBindingBuilder<BEAN, NEWTARGET>) BindingBuilder.super.withConverter(
  106. toModel, toPresentation);
  107. }
  108. @Override
  109. public default <NEWTARGET> BeanBindingBuilder<BEAN, NEWTARGET> withConverter(
  110. SerializableFunction<TARGET, NEWTARGET> toModel,
  111. SerializableFunction<NEWTARGET, TARGET> toPresentation,
  112. String errorMessage) {
  113. return (BeanBindingBuilder<BEAN, NEWTARGET>) BindingBuilder.super.withConverter(
  114. toModel, toPresentation, errorMessage);
  115. }
  116. @Override
  117. public BeanBindingBuilder<BEAN, TARGET> withValidationStatusHandler(
  118. ValidationStatusHandler handler);
  119. @Override
  120. public default BeanBindingBuilder<BEAN, TARGET> withStatusLabel(
  121. Label label) {
  122. return (BeanBindingBuilder<BEAN, TARGET>) BindingBuilder.super.withStatusLabel(
  123. label);
  124. }
  125. /**
  126. * Completes this binding by connecting the field to the property with
  127. * the given name. The getter and setter methods of the property are
  128. * looked up with bean introspection and used to read and write the
  129. * property value.
  130. * <p>
  131. * If a JSR-303 bean validation implementation is present on the
  132. * classpath, adds a {@link BeanValidator} to this binding.
  133. * <p>
  134. * The property must have an accessible getter method. It need not have
  135. * an accessible setter; in that case the property value is never
  136. * updated and the binding is said to be <i>read-only</i>.
  137. *
  138. * @param propertyName
  139. * the name of the property to bind, not null
  140. * @return the newly created binding
  141. *
  142. * @throws IllegalArgumentException
  143. * if the property name is invalid
  144. * @throws IllegalArgumentException
  145. * if the property has no accessible getter
  146. *
  147. * @see BindingBuilder#bind(SerializableFunction,
  148. * SerializableBiConsumer)
  149. */
  150. public Binding<BEAN, TARGET> bind(String propertyName);
  151. }
  152. /**
  153. * An internal implementation of {@link BeanBindingBuilder}.
  154. *
  155. * @param <BEAN>
  156. * the bean type
  157. * @param <FIELDVALUE>
  158. * the field value type
  159. * @param <TARGET>
  160. * the target property type
  161. */
  162. protected static class BeanBindingImpl<BEAN, FIELDVALUE, TARGET>
  163. extends BindingBuilderImpl<BEAN, FIELDVALUE, TARGET>
  164. implements BeanBindingBuilder<BEAN, TARGET> {
  165. /**
  166. * Creates a new bean binding.
  167. *
  168. * @param binder
  169. * the binder this instance is connected to, not null
  170. * @param field
  171. * the field to use, not null
  172. * @param converter
  173. * the initial converter to use, not null
  174. * @param statusHandler
  175. * the handler to notify of status changes, not null
  176. */
  177. protected BeanBindingImpl(BeanBinder<BEAN> binder,
  178. HasValue<FIELDVALUE> field,
  179. Converter<FIELDVALUE, TARGET> converter,
  180. ValidationStatusHandler statusHandler) {
  181. super(binder, field, converter, statusHandler);
  182. }
  183. @Override
  184. public BeanBindingBuilder<BEAN, TARGET> withValidator(
  185. Validator<? super TARGET> validator) {
  186. return (BeanBindingBuilder<BEAN, TARGET>) super.withValidator(
  187. validator);
  188. }
  189. @Override
  190. public <NEWTARGET> BeanBindingBuilder<BEAN, NEWTARGET> withConverter(
  191. Converter<TARGET, NEWTARGET> converter) {
  192. return (BeanBindingBuilder<BEAN, NEWTARGET>) super.withConverter(
  193. converter);
  194. }
  195. @Override
  196. public BeanBindingBuilder<BEAN, TARGET> withValidationStatusHandler(
  197. ValidationStatusHandler handler) {
  198. return (BeanBindingBuilder<BEAN, TARGET>) super.withValidationStatusHandler(
  199. handler);
  200. }
  201. @Override
  202. public BeanBindingBuilder<BEAN, TARGET> setRequired(
  203. ErrorMessageProvider errorMessageProvider) {
  204. return (BeanBindingBuilder<BEAN, TARGET>) super.setRequired(
  205. errorMessageProvider);
  206. }
  207. @Override
  208. public Binding<BEAN, TARGET> bind(String propertyName) {
  209. checkUnbound();
  210. BindingBuilder<BEAN, Object> finalBinding;
  211. PropertyDescriptor descriptor = getDescriptor(propertyName);
  212. Method getter = descriptor.getReadMethod();
  213. Method setter = descriptor.getWriteMethod();
  214. finalBinding = withConverter(
  215. createConverter(getter.getReturnType()), false);
  216. if (BeanUtil.checkBeanValidationAvailable()) {
  217. finalBinding = finalBinding.withValidator(
  218. new BeanValidator(getBinder().beanType, propertyName));
  219. }
  220. try {
  221. return (Binding<BEAN, TARGET>) finalBinding.bind(
  222. bean -> invokeWrapExceptions(getter, bean),
  223. (bean, value) -> invokeWrapExceptions(setter, bean,
  224. value));
  225. } finally {
  226. getBinder().boundProperties.add(propertyName);
  227. }
  228. }
  229. @Override
  230. protected BeanBinder<BEAN> getBinder() {
  231. return (BeanBinder<BEAN>) super.getBinder();
  232. }
  233. private static Object invokeWrapExceptions(Method method, Object target,
  234. Object... parameters) {
  235. if (method == null) {
  236. return null;
  237. }
  238. try {
  239. return method.invoke(target, parameters);
  240. } catch (IllegalAccessException | InvocationTargetException e) {
  241. throw new RuntimeException(e);
  242. }
  243. }
  244. private PropertyDescriptor getDescriptor(String propertyName) {
  245. final Class<?> beanType = getBinder().beanType;
  246. PropertyDescriptor descriptor = null;
  247. try {
  248. descriptor = BeanUtil.getPropertyDescriptor(beanType,
  249. propertyName);
  250. } catch (IntrospectionException ie) {
  251. throw new IllegalArgumentException(
  252. "Could not resolve bean property name (see the cause): "
  253. + beanType.getName() + "." + propertyName,
  254. ie);
  255. }
  256. if (descriptor == null) {
  257. throw new IllegalArgumentException(
  258. "Could not resolve bean property name (please check spelling and getter visibility): "
  259. + beanType.getName() + "." + propertyName);
  260. }
  261. if (descriptor.getReadMethod() == null) {
  262. throw new IllegalArgumentException(
  263. "Bean property has no accessible getter: "
  264. + beanType.getName() + "." + propertyName);
  265. }
  266. return descriptor;
  267. }
  268. @SuppressWarnings("unchecked")
  269. private Converter<TARGET, Object> createConverter(Class<?> getterType) {
  270. return Converter.from(fieldValue -> cast(fieldValue, getterType),
  271. propertyValue -> (TARGET) propertyValue, exception -> {
  272. throw new RuntimeException(exception);
  273. });
  274. }
  275. private <T> T cast(TARGET value, Class<T> clazz) {
  276. if (clazz.isPrimitive()) {
  277. return (T) ReflectTools.convertPrimitiveType(clazz).cast(value);
  278. } else {
  279. return clazz.cast(value);
  280. }
  281. }
  282. }
  283. private final Class<? extends BEAN> beanType;
  284. private final Set<String> boundProperties;
  285. /**
  286. * Creates a new {@code BeanBinder} supporting beans of the given type.
  287. *
  288. * @param beanType
  289. * the bean {@code Class} instance, not null
  290. */
  291. public BeanBinder(Class<? extends BEAN> beanType) {
  292. BeanUtil.checkBeanValidationAvailable();
  293. this.beanType = beanType;
  294. boundProperties = new HashSet<>();
  295. }
  296. @Override
  297. public <FIELDVALUE> BeanBindingBuilder<BEAN, FIELDVALUE> forField(
  298. HasValue<FIELDVALUE> field) {
  299. return (BeanBindingBuilder<BEAN, FIELDVALUE>) super.forField(field);
  300. }
  301. /**
  302. * Binds the given field to the property with the given name. The getter and
  303. * setter methods of the property are looked up with bean introspection and
  304. * used to read and write the property value.
  305. * <p>
  306. * Use the {@link #forField(HasValue)} overload instead if you want to
  307. * further configure the new binding.
  308. * <p>
  309. * The property must have an accessible getter method. It need not have an
  310. * accessible setter; in that case the property value is never updated and
  311. * the binding is said to be <i>read-only</i>.
  312. *
  313. * @param <FIELDVALUE>
  314. * the value type of the field to bind
  315. * @param field
  316. * the field to bind, not null
  317. * @param propertyName
  318. * the name of the property to bind, not null
  319. * @return the newly created binding
  320. *
  321. * @throws IllegalArgumentException
  322. * if the property name is invalid
  323. * @throws IllegalArgumentException
  324. * if the property has no accessible getter
  325. *
  326. * @see #bind(HasValue, SerializableFunction, SerializableBiConsumer)
  327. */
  328. public <FIELDVALUE> Binding<BEAN, FIELDVALUE> bind(
  329. HasValue<FIELDVALUE> field, String propertyName) {
  330. return forField(field).bind(propertyName);
  331. }
  332. @Override
  333. public BeanBinder<BEAN> withValidator(Validator<? super BEAN> validator) {
  334. return (BeanBinder<BEAN>) super.withValidator(validator);
  335. }
  336. @Override
  337. protected <FIELDVALUE, TARGET> BeanBindingImpl<BEAN, FIELDVALUE, TARGET> createBinding(
  338. HasValue<FIELDVALUE> field, Converter<FIELDVALUE, TARGET> converter,
  339. ValidationStatusHandler handler) {
  340. Objects.requireNonNull(field, "field cannot be null");
  341. Objects.requireNonNull(converter, "converter cannot be null");
  342. return new BeanBindingImpl<>(this, field, converter, handler);
  343. }
  344. /**
  345. * Binds member fields found in the given object.
  346. * <p>
  347. * This method processes all (Java) member fields whose type extends
  348. * {@link HasValue} and that can be mapped to a property id. Property id
  349. * mapping is done based on the field name or on a @{@link PropertyId}
  350. * annotation on the field. All non-null unbound fields for which a property
  351. * id can be determined are bound to the property id.
  352. * </p>
  353. * <p>
  354. * For example:
  355. *
  356. * <pre>
  357. * public class MyForm extends VerticalLayout {
  358. * private TextField firstName = new TextField("First name");
  359. * &#64;PropertyId("last")
  360. * private TextField lastName = new TextField("Last name");
  361. *
  362. * MyForm myForm = new MyForm();
  363. * ...
  364. * binder.bindMemberFields(myForm);
  365. * </pre>
  366. *
  367. * </p>
  368. * This binds the firstName TextField to a "firstName" property in the item,
  369. * lastName TextField to a "last" property.
  370. * <p>
  371. * It's not always possible to bind a field to a property because their
  372. * types are incompatible. E.g. custom converter is required to bind
  373. * {@code HasValue<String>} and {@code Integer} property (that would be a
  374. * case of "age" property). In such case {@link IllegalStateException} will
  375. * be thrown unless the field has been configured manually before calling
  376. * the {@link #bindInstanceFields(Object)} method.
  377. * <p>
  378. * It's always possible to do custom binding for any field: the
  379. * {@link #bindInstanceFields(Object)} method doesn't override existing
  380. * bindings.
  381. *
  382. * @param objectWithMemberFields
  383. * The object that contains (Java) member fields to bind
  384. * @throws IllegalStateException
  385. * if there are incompatible HasValue<T> and property types
  386. */
  387. public void bindInstanceFields(Object objectWithMemberFields) {
  388. Class<?> objectClass = objectWithMemberFields.getClass();
  389. getFieldsInDeclareOrder(objectClass).stream()
  390. .filter(memberField -> HasValue.class
  391. .isAssignableFrom(memberField.getType()))
  392. .forEach(memberField -> handleProperty(memberField,
  393. (property, type) -> bindProperty(objectWithMemberFields,
  394. memberField, property, type)));
  395. }
  396. /**
  397. * Binds {@code property} with {@code propertyType} to the field in the
  398. * {@code objectWithMemberFields} instance using {@code memberField} as a
  399. * reference to a member.
  400. *
  401. * @param objectWithMemberFields
  402. * the object that contains (Java) member fields to build and
  403. * bind
  404. * @param memberField
  405. * reference to a member field to bind
  406. * @param property
  407. * property name to bind
  408. * @param propertyType
  409. * type of the property
  410. */
  411. protected void bindProperty(Object objectWithMemberFields,
  412. Field memberField, String property, Class<?> propertyType) {
  413. Type valueType = GenericTypeReflector.getTypeParameter(
  414. memberField.getGenericType(),
  415. HasValue.class.getTypeParameters()[0]);
  416. if (valueType == null) {
  417. throw new IllegalStateException(String.format(
  418. "Unable to detect value type for the member '%s' in the "
  419. + "class '%s'.",
  420. memberField.getName(),
  421. objectWithMemberFields.getClass().getName()));
  422. }
  423. if (propertyType.equals(valueType)) {
  424. HasValue<?> field;
  425. // Get the field from the object
  426. try {
  427. field = (HasValue<?>) ReflectTools.getJavaFieldValue(
  428. objectWithMemberFields, memberField, HasValue.class);
  429. } catch (IllegalArgumentException | IllegalAccessException
  430. | InvocationTargetException e) {
  431. // If we cannot determine the value, just skip the field
  432. return;
  433. }
  434. if (field == null) {
  435. field = makeFieldInstance(
  436. (Class<? extends HasValue<?>>) memberField.getType());
  437. initializeField(objectWithMemberFields, memberField, field);
  438. }
  439. forField(field).bind(property);
  440. } else {
  441. throw new IllegalStateException(String.format(
  442. "Property type '%s' doesn't "
  443. + "match the field type '%s'. "
  444. + "Binding should be configured manulaly using converter.",
  445. propertyType.getName(), valueType.getTypeName()));
  446. }
  447. }
  448. /**
  449. * Makes an instance of the field type {@code fieldClass}.
  450. * <p>
  451. * The resulting field instance is used to bind a property to it using the
  452. * {@link #bindInstanceFields(Object)} method.
  453. * <p>
  454. * The default implementation relies on the default constructor of the
  455. * class. If there is no suitable default constructor or you want to
  456. * configure the instantiated class then override this method and provide
  457. * your own implementation.
  458. *
  459. * @see #bindInstanceFields(Object)
  460. * @param fieldClass
  461. * type of the field
  462. * @return a {@code fieldClass} instance object
  463. */
  464. protected HasValue<?> makeFieldInstance(
  465. Class<? extends HasValue<?>> fieldClass) {
  466. try {
  467. return fieldClass.newInstance();
  468. } catch (InstantiationException | IllegalAccessException e) {
  469. throw new IllegalStateException(
  470. String.format("Couldn't create an '%s' type instance",
  471. fieldClass.getName()),
  472. e);
  473. }
  474. }
  475. /**
  476. * Returns an array containing {@link Field} objects reflecting all the
  477. * fields of the class or interface represented by this Class object. The
  478. * elements in the array returned are sorted in declare order from sub class
  479. * to super class.
  480. *
  481. * @param searchClass
  482. * class to introspect
  483. * @return list of all fields in the class considering hierarchy
  484. */
  485. protected List<Field> getFieldsInDeclareOrder(Class<?> searchClass) {
  486. ArrayList<Field> memberFieldInOrder = new ArrayList<>();
  487. while (searchClass != null) {
  488. memberFieldInOrder
  489. .addAll(Arrays.asList(searchClass.getDeclaredFields()));
  490. searchClass = searchClass.getSuperclass();
  491. }
  492. return memberFieldInOrder;
  493. }
  494. private void initializeField(Object objectWithMemberFields,
  495. Field memberField, HasValue<?> value) {
  496. try {
  497. ReflectTools.setJavaFieldValue(objectWithMemberFields, memberField,
  498. value);
  499. } catch (IllegalArgumentException | IllegalAccessException
  500. | InvocationTargetException e) {
  501. throw new IllegalStateException(
  502. String.format("Could not assign value to field '%s'",
  503. memberField.getName()),
  504. e);
  505. }
  506. }
  507. private void handleProperty(Field field,
  508. BiConsumer<String, Class<?>> propertyHandler) {
  509. Optional<PropertyDescriptor> descriptor = getPropertyDescriptor(field);
  510. if (!descriptor.isPresent()) {
  511. return;
  512. }
  513. String propertyName = descriptor.get().getName();
  514. if (boundProperties.contains(propertyName)) {
  515. return;
  516. }
  517. propertyHandler.accept(propertyName,
  518. descriptor.get().getPropertyType());
  519. boundProperties.add(propertyName);
  520. }
  521. private Optional<PropertyDescriptor> getPropertyDescriptor(Field field) {
  522. PropertyId propertyIdAnnotation = field.getAnnotation(PropertyId.class);
  523. String propertyId;
  524. if (propertyIdAnnotation != null) {
  525. // @PropertyId(propertyId) always overrides property id
  526. propertyId = propertyIdAnnotation.value();
  527. } else {
  528. propertyId = field.getName();
  529. }
  530. List<PropertyDescriptor> descriptors;
  531. try {
  532. descriptors = BeanUtil.getBeanPropertyDescriptors(beanType);
  533. } catch (IntrospectionException e) {
  534. throw new IllegalArgumentException(String.format(
  535. "Could not resolve bean '%s' properties (see the cause):",
  536. beanType.getName()), e);
  537. }
  538. Optional<PropertyDescriptor> propertyDescitpor = descriptors.stream()
  539. .filter(descriptor -> minifyFieldName(descriptor.getName())
  540. .equals(minifyFieldName(propertyId)))
  541. .findFirst();
  542. return propertyDescitpor;
  543. }
  544. private String minifyFieldName(String fieldName) {
  545. return fieldName.toLowerCase(Locale.ENGLISH).replace("_", "");
  546. }
  547. }