Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

BeanPropertySet.java 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. /*
  2. * Copyright 2000-2018 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.io.IOException;
  20. import java.io.Serializable;
  21. import java.lang.reflect.InvocationTargetException;
  22. import java.lang.reflect.Method;
  23. import java.util.HashMap;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.Objects;
  27. import java.util.Optional;
  28. import java.util.concurrent.ConcurrentHashMap;
  29. import java.util.concurrent.ConcurrentMap;
  30. import java.util.function.Function;
  31. import java.util.stream.Collectors;
  32. import java.util.stream.Stream;
  33. import com.vaadin.data.util.BeanUtil;
  34. import com.vaadin.server.Setter;
  35. /**
  36. * A {@link PropertySet} that uses reflection to find bean properties.
  37. *
  38. * @author Vaadin Ltd
  39. *
  40. * @since 8.0
  41. *
  42. * @param <T>
  43. * the type of the bean
  44. */
  45. public class BeanPropertySet<T> implements PropertySet<T> {
  46. /**
  47. * Serialized form of a property set. When deserialized, the property set
  48. * for the corresponding bean type is requested, which either returns the
  49. * existing cached instance or creates a new one.
  50. *
  51. * @see #readResolve()
  52. * @see BeanPropertyDefinition#writeReplace()
  53. */
  54. private static class SerializedPropertySet<T> implements Serializable {
  55. private final InstanceKey<T> instanceKey;
  56. private SerializedPropertySet(InstanceKey<T> instanceKey) {
  57. this.instanceKey = instanceKey;
  58. }
  59. private Object readResolve() {
  60. /*
  61. * When this instance is deserialized, it will be replaced with a
  62. * property set for the corresponding bean type and property name.
  63. */
  64. return get(instanceKey.type, instanceKey.checkNestedDefinitions,
  65. new PropertyFilterDefinition(instanceKey.depth,
  66. instanceKey.ignorePackageNames));
  67. }
  68. }
  69. /**
  70. * Serialized form of a property definition. When deserialized, the property
  71. * set for the corresponding bean type is requested, which either returns
  72. * the existing cached instance or creates a new one. The right property
  73. * definition is then fetched from the property set.
  74. *
  75. * @see #readResolve()
  76. * @see BeanPropertySet#writeReplace()
  77. */
  78. private static class SerializedPropertyDefinition implements Serializable {
  79. private final Class<?> beanType;
  80. private final String propertyName;
  81. private SerializedPropertyDefinition(Class<?> beanType,
  82. String propertyName) {
  83. this.beanType = beanType;
  84. this.propertyName = propertyName;
  85. }
  86. private Object readResolve() throws IOException {
  87. /*
  88. * When this instance is deserialized, it will be replaced with a
  89. * property definition for the corresponding bean type and property
  90. * name.
  91. */
  92. return get(beanType).getProperty(propertyName)
  93. .orElseThrow(() -> new IOException(
  94. beanType + " no longer has a property named "
  95. + propertyName));
  96. }
  97. }
  98. private static class BeanPropertyDefinition<T, V>
  99. extends AbstractBeanPropertyDefinition<T, V> {
  100. public BeanPropertyDefinition(BeanPropertySet<T> propertySet,
  101. Class<T> propertyHolderType, PropertyDescriptor descriptor) {
  102. super(propertySet, propertyHolderType, descriptor);
  103. }
  104. @Override
  105. public ValueProvider<T, V> getGetter() {
  106. return bean -> {
  107. Method readMethod = getDescriptor().getReadMethod();
  108. Object value = invokeWrapExceptions(readMethod, bean);
  109. return getType().cast(value);
  110. };
  111. }
  112. @Override
  113. public Optional<Setter<T, V>> getSetter() {
  114. if (getDescriptor().getWriteMethod() == null) {
  115. return Optional.empty();
  116. }
  117. Setter<T, V> setter = (bean, value) -> {
  118. // Do not "optimize" this getter call,
  119. // if its done outside the code block, that will produce
  120. // NotSerializableException because of some lambda compilation
  121. // magic
  122. Method innerSetter = getDescriptor().getWriteMethod();
  123. invokeWrapExceptions(innerSetter, bean, value);
  124. };
  125. return Optional.of(setter);
  126. }
  127. private Object writeReplace() {
  128. /*
  129. * Instead of serializing this actual property definition, only
  130. * serialize a DTO that when deserialized will get the corresponding
  131. * property definition from the cache.
  132. */
  133. return new SerializedPropertyDefinition(
  134. getPropertySet().instanceKey.type, getName());
  135. }
  136. }
  137. /**
  138. * Contains properties for a bean type which is nested in another
  139. * definition.
  140. *
  141. * @since 8.1
  142. * @param <T>
  143. * the bean type
  144. * @param <V>
  145. * the value type returned by the getter and set by the setter
  146. */
  147. public static class NestedBeanPropertyDefinition<T, V>
  148. extends AbstractBeanPropertyDefinition<T, V> {
  149. /**
  150. * Default maximum depth for scanning nested properties.
  151. *
  152. * @since 8.2
  153. */
  154. protected static final int MAX_PROPERTY_NESTING_DEPTH = 10;
  155. private final PropertyDefinition<T, ?> parent;
  156. public NestedBeanPropertyDefinition(BeanPropertySet<T> propertySet,
  157. PropertyDefinition<T, ?> parent,
  158. PropertyDescriptor descriptor) {
  159. super(propertySet, parent.getType(), descriptor);
  160. this.parent = parent;
  161. }
  162. @Override
  163. public ValueProvider<T, V> getGetter() {
  164. return bean -> {
  165. Method readMethod = getDescriptor().getReadMethod();
  166. Object value = invokeWrapExceptions(readMethod,
  167. parent.getGetter().apply(bean));
  168. return getType().cast(value);
  169. };
  170. }
  171. @Override
  172. public Optional<Setter<T, V>> getSetter() {
  173. if (getDescriptor().getWriteMethod() == null) {
  174. return Optional.empty();
  175. }
  176. Setter<T, V> setter = (bean, value) -> {
  177. // Do not "optimize" this getter call,
  178. // if its done outside the code block, that will produce
  179. // NotSerializableException because of some lambda compilation
  180. // magic
  181. Method innerSetter = getDescriptor().getWriteMethod();
  182. invokeWrapExceptions(innerSetter,
  183. parent.getGetter().apply(bean), value);
  184. };
  185. return Optional.of(setter);
  186. }
  187. @Override
  188. public String getName() {
  189. return parent.getName() + "." + super.getName();
  190. }
  191. @Override
  192. public String getTopLevelName() {
  193. return super.getName();
  194. }
  195. private Object writeReplace() {
  196. /*
  197. * Instead of serializing this actual property definition, only
  198. * serialize a DTO that when deserialized will get the corresponding
  199. * property definition from the cache.
  200. */
  201. return new SerializedPropertyDefinition(
  202. getPropertySet().instanceKey.type, getName());
  203. }
  204. /**
  205. * Gets the parent property definition.
  206. *
  207. * @return the property definition for the parent
  208. */
  209. public PropertyDefinition<T, ?> getParent() {
  210. return parent;
  211. }
  212. }
  213. /**
  214. * Key for identifying cached BeanPropertySet instances.
  215. *
  216. * @since 8.2
  217. */
  218. private static class InstanceKey<T> implements Serializable {
  219. private Class<T> type;
  220. private boolean checkNestedDefinitions;
  221. private int depth;
  222. private List<String> ignorePackageNames;
  223. public InstanceKey(Class<T> type, boolean checkNestedDefinitions,
  224. int depth, List<String> ignorePackageNames) {
  225. this.type = type;
  226. this.checkNestedDefinitions = checkNestedDefinitions;
  227. this.depth = depth;
  228. this.ignorePackageNames = ignorePackageNames;
  229. }
  230. @Override
  231. public int hashCode() {
  232. final int prime = 31;
  233. int result = 1;
  234. result = prime * result + (checkNestedDefinitions ? 1231 : 1237);
  235. result = prime * result + depth;
  236. result = prime * result + ((ignorePackageNames == null) ? 0
  237. : ignorePackageNames.hashCode());
  238. result = prime * result + ((type == null) ? 0 : type.hashCode());
  239. return result;
  240. }
  241. @Override
  242. public boolean equals(Object obj) {
  243. if (this == obj) {
  244. return true;
  245. }
  246. if (obj == null) {
  247. return false;
  248. }
  249. if (getClass() != obj.getClass()) {
  250. return false;
  251. }
  252. InstanceKey other = (InstanceKey) obj;
  253. if (checkNestedDefinitions != other.checkNestedDefinitions) {
  254. return false;
  255. }
  256. if (depth != other.depth) {
  257. return false;
  258. }
  259. if (ignorePackageNames == null) {
  260. if (other.ignorePackageNames != null) {
  261. return false;
  262. }
  263. } else if (!ignorePackageNames.equals(other.ignorePackageNames)) {
  264. return false;
  265. }
  266. return Objects.equals(type, other.type);
  267. }
  268. }
  269. private static final ConcurrentMap<InstanceKey<?>, BeanPropertySet<?>> INSTANCES = new ConcurrentHashMap<>();
  270. private final InstanceKey<T> instanceKey;
  271. private final Map<String, PropertyDefinition<T, ?>> definitions;
  272. private BeanPropertySet(InstanceKey<T> instanceKey) {
  273. this.instanceKey = instanceKey;
  274. try {
  275. definitions = BeanUtil.getBeanPropertyDescriptors(instanceKey.type)
  276. .stream().filter(BeanPropertySet::hasNonObjectReadMethod)
  277. .map(descriptor -> new BeanPropertyDefinition<>(this,
  278. instanceKey.type, descriptor))
  279. .collect(Collectors.toMap(PropertyDefinition::getName,
  280. Function.identity()));
  281. } catch (IntrospectionException e) {
  282. throw new IllegalArgumentException(
  283. "Cannot find property descriptors for "
  284. + instanceKey.type.getName(),
  285. e);
  286. }
  287. }
  288. private BeanPropertySet(InstanceKey<T> instanceKey,
  289. Map<String, PropertyDefinition<T, ?>> definitions) {
  290. this.instanceKey = instanceKey;
  291. this.definitions = new HashMap<>(definitions);
  292. }
  293. private BeanPropertySet(InstanceKey<T> instanceKey,
  294. boolean checkNestedDefinitions,
  295. PropertyFilterDefinition propertyFilterDefinition) {
  296. this(instanceKey);
  297. if (checkNestedDefinitions) {
  298. Objects.requireNonNull(propertyFilterDefinition,
  299. "You must define a property filter callback if using nested property scan.");
  300. findNestedDefinitions(definitions, 0, propertyFilterDefinition);
  301. }
  302. }
  303. private void findNestedDefinitions(
  304. Map<String, PropertyDefinition<T, ?>> parentDefinitions, int depth,
  305. PropertyFilterDefinition filterCallback) {
  306. if (depth >= filterCallback.getMaxNestingDepth()) {
  307. return;
  308. }
  309. if (parentDefinitions == null) {
  310. return;
  311. }
  312. Map<String, PropertyDefinition<T, ?>> moreProps = new HashMap<>();
  313. for (String parentPropertyKey : parentDefinitions.keySet()) {
  314. PropertyDefinition<T, ?> parentProperty = parentDefinitions
  315. .get(parentPropertyKey);
  316. Class<?> type = parentProperty.getType();
  317. if (type.getPackage() == null || type.isEnum()) {
  318. continue;
  319. }
  320. String packageName = type.getPackage().getName();
  321. if (filterCallback.getIgnorePackageNamesStartingWith().stream()
  322. .anyMatch(prefix -> packageName.startsWith(prefix))) {
  323. continue;
  324. }
  325. try {
  326. List<PropertyDescriptor> descriptors = BeanUtil
  327. .getBeanPropertyDescriptors(type).stream()
  328. .filter(BeanPropertySet::hasNonObjectReadMethod)
  329. .collect(Collectors.toList());
  330. for (PropertyDescriptor descriptor : descriptors) {
  331. String name = parentPropertyKey + "."
  332. + descriptor.getName();
  333. PropertyDescriptor subDescriptor = BeanUtil
  334. .getPropertyDescriptor(instanceKey.type, name);
  335. moreProps.put(name, new NestedBeanPropertyDefinition<>(this,
  336. parentProperty, subDescriptor));
  337. }
  338. } catch (IntrospectionException e) {
  339. throw new IllegalArgumentException(
  340. "Error finding nested property descriptors for "
  341. + type.getName(),
  342. e);
  343. }
  344. }
  345. if (moreProps.size() > 0) {
  346. definitions.putAll(moreProps);
  347. findNestedDefinitions(moreProps, ++depth, filterCallback);
  348. }
  349. }
  350. /**
  351. * Gets a {@link BeanPropertySet} for the given bean type.
  352. *
  353. * @param beanType
  354. * the bean type to get a property set for, not <code>null</code>
  355. * @return the bean property set, not <code>null</code>
  356. */
  357. @SuppressWarnings("unchecked")
  358. public static <T> PropertySet<T> get(Class<? extends T> beanType) {
  359. Objects.requireNonNull(beanType, "Bean type cannot be null");
  360. InstanceKey key = new InstanceKey(beanType, false, 0, null);
  361. // Cache the reflection results
  362. return (PropertySet<T>) INSTANCES
  363. .computeIfAbsent(key, ignored -> new BeanPropertySet<>(key))
  364. .copy();
  365. }
  366. private BeanPropertySet<T> copy() {
  367. return new BeanPropertySet<>(instanceKey, definitions);
  368. }
  369. /**
  370. * Gets a {@link BeanPropertySet} for the given bean type.
  371. *
  372. * @param beanType
  373. * the bean type to get a property set for, not <code>null</code>
  374. * @param checkNestedDefinitions
  375. * whether to scan for nested definitions in beanType
  376. * @param filterDefinition
  377. * filtering conditions for nested properties
  378. * @return the bean property set, not <code>null</code>
  379. * @since 8.2
  380. */
  381. @SuppressWarnings("unchecked")
  382. public static <T> PropertySet<T> get(Class<? extends T> beanType,
  383. boolean checkNestedDefinitions,
  384. PropertyFilterDefinition filterDefinition) {
  385. Objects.requireNonNull(beanType, "Bean type cannot be null");
  386. InstanceKey key = new InstanceKey(beanType, false,
  387. filterDefinition.getMaxNestingDepth(),
  388. filterDefinition.getIgnorePackageNamesStartingWith());
  389. return (PropertySet<T>) INSTANCES
  390. .computeIfAbsent(key, k -> new BeanPropertySet<>(key,
  391. checkNestedDefinitions, filterDefinition))
  392. .copy();
  393. }
  394. @Override
  395. public Stream<PropertyDefinition<T, ?>> getProperties() {
  396. return definitions.values().stream();
  397. }
  398. @Override
  399. public Optional<PropertyDefinition<T, ?>> getProperty(String name)
  400. throws IllegalArgumentException {
  401. Optional<PropertyDefinition<T, ?>> definition = Optional
  402. .ofNullable(definitions.get(name));
  403. if (!definition.isPresent() && name.contains(".")) {
  404. try {
  405. String parentName = name.substring(0, name.lastIndexOf('.'));
  406. Optional<PropertyDefinition<T, ?>> parent = getProperty(
  407. parentName);
  408. if (!parent.isPresent()) {
  409. throw new IllegalArgumentException(
  410. "Cannot find property descriptor [" + parentName
  411. + "] for " + instanceKey.type.getName());
  412. }
  413. Optional<PropertyDescriptor> descriptor = Optional.ofNullable(
  414. BeanUtil.getPropertyDescriptor(instanceKey.type, name));
  415. if (descriptor.isPresent()) {
  416. NestedBeanPropertyDefinition<T, ?> nestedDefinition = new NestedBeanPropertyDefinition<>(
  417. this, parent.get(), descriptor.get());
  418. definitions.put(name, nestedDefinition);
  419. return Optional.of(nestedDefinition);
  420. } else {
  421. throw new IllegalArgumentException(
  422. "Cannot find property descriptor [" + name
  423. + "] for " + instanceKey.type.getName());
  424. }
  425. } catch (IntrospectionException e) {
  426. throw new IllegalArgumentException(
  427. "Cannot find property descriptors for "
  428. + instanceKey.type.getName(),
  429. e);
  430. }
  431. }
  432. return definition;
  433. }
  434. /**
  435. * Gets the bean type of this bean property set.
  436. *
  437. * @since 8.2
  438. * @return the bean type of this bean property set
  439. */
  440. public Class<T> getBeanType() {
  441. return instanceKey.type;
  442. }
  443. private static boolean hasNonObjectReadMethod(
  444. PropertyDescriptor descriptor) {
  445. Method readMethod = descriptor.getReadMethod();
  446. return readMethod != null
  447. && readMethod.getDeclaringClass() != Object.class;
  448. }
  449. private static Object invokeWrapExceptions(Method method, Object target,
  450. Object... parameters) {
  451. try {
  452. return method.invoke(target, parameters);
  453. } catch (IllegalAccessException | InvocationTargetException e) {
  454. throw new RuntimeException(e);
  455. }
  456. }
  457. @Override
  458. public String toString() {
  459. return "Property set for bean " + instanceKey.type.getName();
  460. }
  461. private Object writeReplace() {
  462. /*
  463. * Instead of serializing this actual property set, only serialize a DTO
  464. * that when deserialized will get the corresponding property set from
  465. * the cache.
  466. */
  467. return new SerializedPropertySet(instanceKey);
  468. }
  469. }