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

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