Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

AbstractBeanContainer.java 30KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932
  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.v7.data.util;
  17. import java.io.Serializable;
  18. import java.util.Collection;
  19. import java.util.HashMap;
  20. import java.util.Iterator;
  21. import java.util.LinkedHashMap;
  22. import java.util.List;
  23. import java.util.Map;
  24. import com.vaadin.v7.data.Container;
  25. import com.vaadin.v7.data.Container.Filterable;
  26. import com.vaadin.v7.data.Container.PropertySetChangeNotifier;
  27. import com.vaadin.v7.data.Container.SimpleFilterable;
  28. import com.vaadin.v7.data.Container.Sortable;
  29. import com.vaadin.v7.data.Item;
  30. import com.vaadin.v7.data.Property;
  31. import com.vaadin.v7.data.Property.ValueChangeEvent;
  32. import com.vaadin.v7.data.Property.ValueChangeListener;
  33. import com.vaadin.v7.data.Property.ValueChangeNotifier;
  34. import com.vaadin.v7.data.util.MethodProperty.MethodException;
  35. import com.vaadin.v7.data.util.filter.SimpleStringFilter;
  36. import com.vaadin.v7.data.util.filter.UnsupportedFilterException;
  37. /**
  38. * An abstract base class for in-memory containers for JavaBeans.
  39. *
  40. * <p>
  41. * The properties of the container are determined automatically by introspecting
  42. * the used JavaBean class and explicitly adding or removing properties is not
  43. * supported. Only beans of the same type can be added to the container.
  44. * </p>
  45. *
  46. * <p>
  47. * Subclasses should implement any public methods adding items to the container,
  48. * typically calling the protected methods {@link #addItem(Object, Object)},
  49. * {@link #addItemAfter(Object, Object, Object)} and
  50. * {@link #addItemAt(int, Object, Object)}.
  51. * </p>
  52. *
  53. * @param <IDTYPE>
  54. * The type of the item identifier
  55. * @param <BEANTYPE>
  56. * The type of the Bean
  57. *
  58. * @since 6.5
  59. */
  60. @Deprecated
  61. public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE>
  62. extends AbstractInMemoryContainer<IDTYPE, String, BeanItem<BEANTYPE>>
  63. implements Filterable, SimpleFilterable, Sortable, ValueChangeListener,
  64. PropertySetChangeNotifier {
  65. /**
  66. * Resolver that maps beans to their (item) identifiers, removing the need
  67. * to explicitly specify item identifiers when there is no need to customize
  68. * this.
  69. *
  70. * Note that beans can also be added with an explicit id even if a resolver
  71. * has been set.
  72. *
  73. * @param <IDTYPE>
  74. * @param <BEANTYPE>
  75. *
  76. * @since 6.5
  77. */
  78. @Deprecated
  79. public static interface BeanIdResolver<IDTYPE, BEANTYPE>
  80. extends Serializable {
  81. /**
  82. * Return the item identifier for a bean.
  83. *
  84. * @param bean
  85. * @return
  86. */
  87. public IDTYPE getIdForBean(BEANTYPE bean);
  88. }
  89. /**
  90. * A item identifier resolver that returns the value of a bean property.
  91. *
  92. * The bean must have a getter for the property, and the getter must return
  93. * an object of type IDTYPE.
  94. */
  95. @Deprecated
  96. protected class PropertyBasedBeanIdResolver
  97. implements BeanIdResolver<IDTYPE, BEANTYPE> {
  98. private final Object propertyId;
  99. public PropertyBasedBeanIdResolver(Object propertyId) {
  100. if (propertyId == null) {
  101. throw new IllegalArgumentException(
  102. "Property identifier must not be null");
  103. }
  104. this.propertyId = propertyId;
  105. }
  106. @Override
  107. @SuppressWarnings("unchecked")
  108. public IDTYPE getIdForBean(BEANTYPE bean)
  109. throws IllegalArgumentException {
  110. VaadinPropertyDescriptor<BEANTYPE> pd = model.get(propertyId);
  111. if (null == pd) {
  112. throw new IllegalStateException(
  113. "Property " + propertyId + " not found");
  114. }
  115. try {
  116. Property<IDTYPE> property = (Property<IDTYPE>) pd
  117. .createProperty(bean);
  118. return property.getValue();
  119. } catch (MethodException e) {
  120. throw new IllegalArgumentException(e);
  121. }
  122. }
  123. }
  124. /**
  125. * The resolver that finds the item ID for a bean, or null not to use
  126. * automatic resolving.
  127. *
  128. * Methods that add a bean without specifying an ID must not be called if no
  129. * resolver has been set.
  130. */
  131. private BeanIdResolver<IDTYPE, BEANTYPE> beanIdResolver = null;
  132. /**
  133. * Maps all item ids in the container (including filtered) to their
  134. * corresponding BeanItem.
  135. */
  136. private final Map<IDTYPE, BeanItem<BEANTYPE>> itemIdToItem = new HashMap<IDTYPE, BeanItem<BEANTYPE>>();
  137. /**
  138. * The type of the beans in the container.
  139. */
  140. private final Class<? super BEANTYPE> type;
  141. /**
  142. * A description of the properties found in beans of type {@link #type}.
  143. * Determines the property ids that are present in the container.
  144. */
  145. private final LinkedHashMap<String, VaadinPropertyDescriptor<BEANTYPE>> model;
  146. /**
  147. * Constructs a {@code AbstractBeanContainer} for beans of the given type.
  148. *
  149. * @param type
  150. * the type of the beans that will be added to the container.
  151. * @throws IllegalArgumentException
  152. * If {@code type} is null
  153. */
  154. protected AbstractBeanContainer(Class<? super BEANTYPE> type) {
  155. if (type == null) {
  156. throw new IllegalArgumentException(
  157. "The bean type passed to AbstractBeanContainer must not be null");
  158. }
  159. this.type = type;
  160. model = BeanItem.getPropertyDescriptors((Class<BEANTYPE>) type);
  161. }
  162. /*
  163. * (non-Javadoc)
  164. *
  165. * @see com.vaadin.data.Container#getType(java.lang.Object)
  166. */
  167. @Override
  168. public Class<?> getType(Object propertyId) {
  169. VaadinPropertyDescriptor<BEANTYPE> descriptor = model.get(propertyId);
  170. if (descriptor == null) {
  171. return null;
  172. }
  173. return descriptor.getPropertyType();
  174. }
  175. /**
  176. * Create a BeanItem for a bean using pre-parsed bean metadata (based on
  177. * {@link #getBeanType()}).
  178. *
  179. * @param bean
  180. * @return created {@link BeanItem} or null if bean is null
  181. */
  182. protected BeanItem<BEANTYPE> createBeanItem(BEANTYPE bean) {
  183. return bean == null ? null : new BeanItem<BEANTYPE>(bean, model);
  184. }
  185. /**
  186. * Returns the type of beans this Container can contain.
  187. *
  188. * This comes from the bean type constructor parameter, and bean metadata
  189. * (including container properties) is based on this.
  190. *
  191. * @return
  192. */
  193. public Class<? super BEANTYPE> getBeanType() {
  194. return type;
  195. }
  196. /*
  197. * (non-Javadoc)
  198. *
  199. * @see com.vaadin.data.Container#getContainerPropertyIds()
  200. */
  201. @Override
  202. public Collection<String> getContainerPropertyIds() {
  203. return model.keySet();
  204. }
  205. /*
  206. * (non-Javadoc)
  207. *
  208. * @see com.vaadin.data.Container#removeAllItems()
  209. */
  210. @Override
  211. public boolean removeAllItems() {
  212. int origSize = size();
  213. IDTYPE firstItem = getFirstVisibleItem();
  214. internalRemoveAllItems();
  215. // detach listeners from all Items
  216. for (Item item : itemIdToItem.values()) {
  217. removeAllValueChangeListeners(item);
  218. }
  219. itemIdToItem.clear();
  220. // fire event only if the visible view changed, regardless of whether
  221. // filtered out items were removed or not
  222. if (origSize != 0) {
  223. fireItemsRemoved(0, firstItem, origSize);
  224. }
  225. return true;
  226. }
  227. /*
  228. * (non-Javadoc)
  229. *
  230. * @see com.vaadin.data.Container#getItem(java.lang.Object)
  231. */
  232. @Override
  233. public BeanItem<BEANTYPE> getItem(Object itemId) {
  234. // TODO return only if visible?
  235. return getUnfilteredItem(itemId);
  236. }
  237. @Override
  238. protected BeanItem<BEANTYPE> getUnfilteredItem(Object itemId) {
  239. return itemIdToItem.get(itemId);
  240. }
  241. /*
  242. * (non-Javadoc)
  243. *
  244. * @see com.vaadin.data.Container#getItemIds()
  245. */
  246. @Override
  247. @SuppressWarnings("unchecked")
  248. public List<IDTYPE> getItemIds() {
  249. return (List<IDTYPE>) super.getItemIds();
  250. }
  251. /*
  252. * (non-Javadoc)
  253. *
  254. * @see com.vaadin.data.Container#getContainerProperty(java.lang.Object,
  255. * java.lang.Object)
  256. */
  257. @Override
  258. public Property getContainerProperty(Object itemId, Object propertyId) {
  259. Item item = getItem(itemId);
  260. if (item == null) {
  261. return null;
  262. }
  263. return item.getItemProperty(propertyId);
  264. }
  265. /*
  266. * (non-Javadoc)
  267. *
  268. * @see com.vaadin.data.Container#removeItem(java.lang.Object)
  269. */
  270. @Override
  271. public boolean removeItem(Object itemId) {
  272. // TODO should also remove items that are filtered out
  273. int origSize = size();
  274. Item item = getItem(itemId);
  275. int position = indexOfId(itemId);
  276. if (internalRemoveItem(itemId)) {
  277. // detach listeners from Item
  278. removeAllValueChangeListeners(item);
  279. // remove item
  280. itemIdToItem.remove(itemId);
  281. // fire event only if the visible view changed, regardless of
  282. // whether filtered out items were removed or not
  283. if (size() != origSize) {
  284. fireItemRemoved(position, itemId);
  285. }
  286. return true;
  287. } else {
  288. return false;
  289. }
  290. }
  291. /**
  292. * Re-filter the container when one of the monitored properties changes.
  293. */
  294. @Override
  295. public void valueChange(ValueChangeEvent event) {
  296. // if a property that is used in a filter is changed, refresh filtering
  297. filterAll();
  298. }
  299. /*
  300. * (non-Javadoc)
  301. *
  302. * @see
  303. * com.vaadin.data.Container.Filterable#addContainerFilter(java.lang.Object,
  304. * java.lang.String, boolean, boolean)
  305. */
  306. @Override
  307. public void addContainerFilter(Object propertyId, String filterString,
  308. boolean ignoreCase, boolean onlyMatchPrefix) {
  309. try {
  310. addFilter(new SimpleStringFilter(propertyId, filterString,
  311. ignoreCase, onlyMatchPrefix));
  312. } catch (UnsupportedFilterException e) {
  313. // the filter instance created here is always valid for in-memory
  314. // containers
  315. }
  316. }
  317. /*
  318. * (non-Javadoc)
  319. *
  320. * @see com.vaadin.data.Container.Filterable#removeAllContainerFilters()
  321. */
  322. @Override
  323. public void removeAllContainerFilters() {
  324. if (!getFilters().isEmpty()) {
  325. for (Item item : itemIdToItem.values()) {
  326. removeAllValueChangeListeners(item);
  327. }
  328. removeAllFilters();
  329. }
  330. }
  331. /*
  332. * (non-Javadoc)
  333. *
  334. * @see
  335. * com.vaadin.data.Container.Filterable#removeContainerFilters(java.lang
  336. * .Object)
  337. */
  338. @Override
  339. public void removeContainerFilters(Object propertyId) {
  340. Collection<Filter> removedFilters = super.removeFilters(propertyId);
  341. if (!removedFilters.isEmpty()) {
  342. // stop listening to change events for the property
  343. for (Item item : itemIdToItem.values()) {
  344. removeValueChangeListener(item, propertyId);
  345. }
  346. }
  347. }
  348. @Override
  349. public void addContainerFilter(Filter filter)
  350. throws UnsupportedFilterException {
  351. addFilter(filter);
  352. }
  353. @Override
  354. public void removeContainerFilter(Filter filter) {
  355. removeFilter(filter);
  356. }
  357. /*
  358. * (non-Javadoc)
  359. *
  360. * @see com.vaadin.data.util.AbstractInMemoryContainer#hasContainerFilters()
  361. */
  362. @Override
  363. public boolean hasContainerFilters() {
  364. return super.hasContainerFilters();
  365. }
  366. /*
  367. * (non-Javadoc)
  368. *
  369. * @see com.vaadin.data.util.AbstractInMemoryContainer#getContainerFilters()
  370. */
  371. @Override
  372. public Collection<Filter> getContainerFilters() {
  373. return super.getContainerFilters();
  374. }
  375. /**
  376. * Make this container listen to the given property provided it notifies
  377. * when its value changes.
  378. *
  379. * @param item
  380. * The {@link Item} that contains the property
  381. * @param propertyId
  382. * The id of the property
  383. */
  384. private void addValueChangeListener(Item item, Object propertyId) {
  385. Property<?> property = item.getItemProperty(propertyId);
  386. if (property instanceof ValueChangeNotifier) {
  387. // avoid multiple notifications for the same property if
  388. // multiple filters are in use
  389. ValueChangeNotifier notifier = (ValueChangeNotifier) property;
  390. notifier.removeListener(this);
  391. notifier.addListener(this);
  392. }
  393. }
  394. /**
  395. * Remove this container as a listener for the given property.
  396. *
  397. * @param item
  398. * The {@link Item} that contains the property
  399. * @param propertyId
  400. * The id of the property
  401. */
  402. private void removeValueChangeListener(Item item, Object propertyId) {
  403. Property<?> property = item.getItemProperty(propertyId);
  404. if (property instanceof ValueChangeNotifier) {
  405. ((ValueChangeNotifier) property).removeListener(this);
  406. }
  407. }
  408. /**
  409. * Remove this contains as a listener for all the properties in the given
  410. * {@link Item}.
  411. *
  412. * @param item
  413. * The {@link Item} that contains the properties
  414. */
  415. private void removeAllValueChangeListeners(Item item) {
  416. for (Object propertyId : item.getItemPropertyIds()) {
  417. removeValueChangeListener(item, propertyId);
  418. }
  419. }
  420. /*
  421. * (non-Javadoc)
  422. *
  423. * @see com.vaadin.data.Container.Sortable#getSortableContainerPropertyIds()
  424. */
  425. @Override
  426. public Collection<?> getSortableContainerPropertyIds() {
  427. return getSortablePropertyIds();
  428. }
  429. /*
  430. * (non-Javadoc)
  431. *
  432. * @see com.vaadin.data.Container.Sortable#sort(java.lang.Object[],
  433. * boolean[])
  434. */
  435. @Override
  436. public void sort(Object[] propertyId, boolean[] ascending) {
  437. sortContainer(propertyId, ascending);
  438. }
  439. @Override
  440. public ItemSorter getItemSorter() {
  441. return super.getItemSorter();
  442. }
  443. @Override
  444. public void setItemSorter(ItemSorter itemSorter) {
  445. super.setItemSorter(itemSorter);
  446. }
  447. @Override
  448. protected void registerNewItem(int position, IDTYPE itemId,
  449. BeanItem<BEANTYPE> item) {
  450. itemIdToItem.put(itemId, item);
  451. // add listeners to be able to update filtering on property
  452. // changes
  453. for (Filter filter : getFilters()) {
  454. for (String propertyId : getContainerPropertyIds()) {
  455. if (filter.appliesToProperty(propertyId)) {
  456. // addValueChangeListener avoids adding duplicates
  457. addValueChangeListener(item, propertyId);
  458. }
  459. }
  460. }
  461. }
  462. /**
  463. * Check that a bean can be added to the container (is of the correct type
  464. * for the container).
  465. *
  466. * @param bean
  467. * @return
  468. */
  469. private boolean validateBean(BEANTYPE bean) {
  470. return bean != null && getBeanType().isAssignableFrom(bean.getClass());
  471. }
  472. /**
  473. * Adds the bean to the Container.
  474. *
  475. * Note: the behavior of this method changed in Vaadin 6.6 - now items are
  476. * added at the very end of the unfiltered container and not after the last
  477. * visible item if filtering is used.
  478. *
  479. * @see Container#addItem(Object)
  480. */
  481. protected BeanItem<BEANTYPE> addItem(IDTYPE itemId, BEANTYPE bean) {
  482. if (!validateBean(bean)) {
  483. return null;
  484. }
  485. return internalAddItemAtEnd(itemId, createBeanItem(bean), true);
  486. }
  487. /**
  488. * Adds the bean after the given bean.
  489. *
  490. * @see Container.Ordered#addItemAfter(Object, Object)
  491. */
  492. protected BeanItem<BEANTYPE> addItemAfter(IDTYPE previousItemId,
  493. IDTYPE newItemId, BEANTYPE bean) {
  494. if (!validateBean(bean)) {
  495. return null;
  496. }
  497. return internalAddItemAfter(previousItemId, newItemId,
  498. createBeanItem(bean), true);
  499. }
  500. /**
  501. * Adds a new bean at the given index.
  502. *
  503. * The bean is used both as the item contents and as the item identifier.
  504. *
  505. * @param index
  506. * Index at which the bean should be added.
  507. * @param newItemId
  508. * The item id for the bean to add to the container.
  509. * @param bean
  510. * The bean to add to the container.
  511. *
  512. * @return Returns the new BeanItem or null if the operation fails.
  513. */
  514. protected BeanItem<BEANTYPE> addItemAt(int index, IDTYPE newItemId,
  515. BEANTYPE bean) {
  516. if (!validateBean(bean)) {
  517. return null;
  518. }
  519. return internalAddItemAt(index, newItemId, createBeanItem(bean), true);
  520. }
  521. /**
  522. * Adds a bean to the container using the bean item id resolver to find its
  523. * identifier.
  524. *
  525. * A bean id resolver must be set before calling this method.
  526. *
  527. * @see #addItem(Object, Object)
  528. *
  529. * @param bean
  530. * the bean to add
  531. * @return BeanItem<BEANTYPE> item added or null
  532. * @throws IllegalStateException
  533. * if no bean identifier resolver has been set
  534. * @throws IllegalArgumentException
  535. * if an identifier cannot be resolved for the bean
  536. */
  537. protected BeanItem<BEANTYPE> addBean(BEANTYPE bean)
  538. throws IllegalStateException, IllegalArgumentException {
  539. if (bean == null) {
  540. return null;
  541. }
  542. IDTYPE itemId = resolveBeanId(bean);
  543. if (itemId == null) {
  544. throw new IllegalArgumentException(
  545. "Resolved identifier for a bean must not be null");
  546. }
  547. return addItem(itemId, bean);
  548. }
  549. /**
  550. * Adds a bean to the container after a specified item identifier, using the
  551. * bean item id resolver to find its identifier.
  552. *
  553. * A bean id resolver must be set before calling this method.
  554. *
  555. * @see #addItemAfter(Object, Object, Object)
  556. *
  557. * @param previousItemId
  558. * the identifier of the bean after which this bean should be
  559. * added, null to add to the beginning
  560. * @param bean
  561. * the bean to add
  562. * @return BeanItem<BEANTYPE> item added or null
  563. * @throws IllegalStateException
  564. * if no bean identifier resolver has been set
  565. * @throws IllegalArgumentException
  566. * if an identifier cannot be resolved for the bean
  567. */
  568. protected BeanItem<BEANTYPE> addBeanAfter(IDTYPE previousItemId,
  569. BEANTYPE bean)
  570. throws IllegalStateException, IllegalArgumentException {
  571. if (bean == null) {
  572. return null;
  573. }
  574. IDTYPE itemId = resolveBeanId(bean);
  575. if (itemId == null) {
  576. throw new IllegalArgumentException(
  577. "Resolved identifier for a bean must not be null");
  578. }
  579. return addItemAfter(previousItemId, itemId, bean);
  580. }
  581. /**
  582. * Adds a bean at a specified (filtered view) position in the container
  583. * using the bean item id resolver to find its identifier.
  584. *
  585. * A bean id resolver must be set before calling this method.
  586. *
  587. * @see #addItemAfter(Object, Object, Object)
  588. *
  589. * @param index
  590. * the index (in the filtered view) at which to add the item
  591. * @param bean
  592. * the bean to add
  593. * @return BeanItem<BEANTYPE> item added or null
  594. * @throws IllegalStateException
  595. * if no bean identifier resolver has been set
  596. * @throws IllegalArgumentException
  597. * if an identifier cannot be resolved for the bean
  598. */
  599. protected BeanItem<BEANTYPE> addBeanAt(int index, BEANTYPE bean)
  600. throws IllegalStateException, IllegalArgumentException {
  601. if (bean == null) {
  602. return null;
  603. }
  604. IDTYPE itemId = resolveBeanId(bean);
  605. if (itemId == null) {
  606. throw new IllegalArgumentException(
  607. "Resolved identifier for a bean must not be null");
  608. }
  609. return addItemAt(index, itemId, bean);
  610. }
  611. /**
  612. * Adds all the beans from a {@link Collection} in one operation using the
  613. * bean item identifier resolver. More efficient than adding them one by
  614. * one.
  615. *
  616. * A bean id resolver must be set before calling this method.
  617. *
  618. * Note: the behavior of this method changed in Vaadin 6.6 - now items are
  619. * added at the very end of the unfiltered container and not after the last
  620. * visible item if filtering is used.
  621. *
  622. * @param collection
  623. * The collection of beans to add. Must not be null.
  624. * @throws IllegalStateException
  625. * if no bean identifier resolver has been set
  626. * @throws IllegalArgumentException
  627. * if the resolver returns a null itemId for one of the beans in
  628. * the collection
  629. */
  630. protected void addAll(Collection<? extends BEANTYPE> collection)
  631. throws IllegalStateException, IllegalArgumentException {
  632. boolean modified = false;
  633. int origSize = size();
  634. for (BEANTYPE bean : collection) {
  635. // TODO skipping invalid beans - should not allow them in javadoc?
  636. if (bean == null
  637. || !getBeanType().isAssignableFrom(bean.getClass())) {
  638. continue;
  639. }
  640. IDTYPE itemId = resolveBeanId(bean);
  641. if (itemId == null) {
  642. throw new IllegalArgumentException(
  643. "Resolved identifier for a bean must not be null");
  644. }
  645. if (internalAddItemAtEnd(itemId, createBeanItem(bean),
  646. false) != null) {
  647. modified = true;
  648. }
  649. }
  650. if (modified) {
  651. // Filter the contents when all items have been added
  652. if (isFiltered()) {
  653. doFilterContainer(!getFilters().isEmpty());
  654. }
  655. if (visibleNewItemsWasAdded(origSize)) {
  656. // fire event about added items
  657. int firstPosition = origSize;
  658. IDTYPE firstItemId = getVisibleItemIds().get(firstPosition);
  659. int affectedItems = size() - origSize;
  660. fireItemsAdded(firstPosition, firstItemId, affectedItems);
  661. }
  662. }
  663. }
  664. private boolean visibleNewItemsWasAdded(int origSize) {
  665. return size() > origSize;
  666. }
  667. /**
  668. * Use the bean resolver to get the identifier for a bean.
  669. *
  670. * @param bean
  671. * @return resolved bean identifier, null if could not be resolved
  672. * @throws IllegalStateException
  673. * if no bean resolver is set
  674. */
  675. protected IDTYPE resolveBeanId(BEANTYPE bean) {
  676. if (beanIdResolver == null) {
  677. throw new IllegalStateException(
  678. "Bean item identifier resolver is required.");
  679. }
  680. return beanIdResolver.getIdForBean(bean);
  681. }
  682. /**
  683. * Sets the resolver that finds the item id for a bean, or null not to use
  684. * automatic resolving.
  685. *
  686. * Methods that add a bean without specifying an id must not be called if no
  687. * resolver has been set.
  688. *
  689. * Note that methods taking an explicit id can be used whether a resolver
  690. * has been defined or not.
  691. *
  692. * @param beanIdResolver
  693. * to use or null to disable automatic id resolution
  694. */
  695. protected void setBeanIdResolver(
  696. BeanIdResolver<IDTYPE, BEANTYPE> beanIdResolver) {
  697. this.beanIdResolver = beanIdResolver;
  698. }
  699. /**
  700. * Returns the resolver that finds the item ID for a bean.
  701. *
  702. * @return resolver used or null if automatic item id resolving is disabled
  703. */
  704. public BeanIdResolver<IDTYPE, BEANTYPE> getBeanIdResolver() {
  705. return beanIdResolver;
  706. }
  707. /**
  708. * Create an item identifier resolver using a named bean property.
  709. *
  710. * @param propertyId
  711. * property identifier, which must map to a getter in BEANTYPE
  712. * @return created resolver
  713. */
  714. protected BeanIdResolver<IDTYPE, BEANTYPE> createBeanPropertyResolver(
  715. Object propertyId) {
  716. return new PropertyBasedBeanIdResolver(propertyId);
  717. }
  718. /**
  719. * @deprecated As of 7.0, replaced by {@link #addPropertySetChangeListener}
  720. **/
  721. @Deprecated
  722. @Override
  723. public void addListener(Container.PropertySetChangeListener listener) {
  724. addPropertySetChangeListener(listener);
  725. }
  726. @Override
  727. public void addPropertySetChangeListener(
  728. Container.PropertySetChangeListener listener) {
  729. super.addPropertySetChangeListener(listener);
  730. }
  731. /**
  732. * @deprecated As of 7.0, replaced by
  733. * {@link #removePropertySetChangeListener(Container.PropertySetChangeListener)}
  734. **/
  735. @Deprecated
  736. @Override
  737. public void removeListener(Container.PropertySetChangeListener listener) {
  738. removePropertySetChangeListener(listener);
  739. }
  740. @Override
  741. public void removePropertySetChangeListener(
  742. Container.PropertySetChangeListener listener) {
  743. super.removePropertySetChangeListener(listener);
  744. }
  745. @Override
  746. public boolean addContainerProperty(Object propertyId, Class<?> type,
  747. Object defaultValue) throws UnsupportedOperationException {
  748. throw new UnsupportedOperationException(
  749. "Use addNestedContainerProperty(String) to add container properties to a "
  750. + getClass().getSimpleName());
  751. }
  752. /**
  753. * Adds a property for the container and all its items.
  754. *
  755. * Primarily for internal use, may change in future versions.
  756. *
  757. * @param propertyId
  758. * @param propertyDescriptor
  759. * @return true if the property was added
  760. */
  761. protected final boolean addContainerProperty(String propertyId,
  762. VaadinPropertyDescriptor<BEANTYPE> propertyDescriptor) {
  763. if (null == propertyId || null == propertyDescriptor) {
  764. return false;
  765. }
  766. // Fails if the Property is already present
  767. if (model.containsKey(propertyId)) {
  768. return false;
  769. }
  770. model.put(propertyId, propertyDescriptor);
  771. for (BeanItem<BEANTYPE> item : itemIdToItem.values()) {
  772. item.addItemProperty(propertyId,
  773. propertyDescriptor.createProperty(item.getBean()));
  774. }
  775. // Sends a change event
  776. fireContainerPropertySetChange();
  777. return true;
  778. }
  779. /**
  780. * Adds a nested container property for the container, e.g.
  781. * "manager.address.street".
  782. *
  783. * All intermediate getters must exist and should return non-null values
  784. * when the property value is accessed. If an intermediate getter returns
  785. * null, a null value will be returned.
  786. *
  787. * @see NestedMethodProperty
  788. *
  789. * @param propertyId
  790. * @return true if the property was added
  791. */
  792. public boolean addNestedContainerProperty(String propertyId) {
  793. return addContainerProperty(propertyId,
  794. new NestedPropertyDescriptor(propertyId, type));
  795. }
  796. /**
  797. * Adds a nested container properties for all sub-properties of a named
  798. * property to the container. The named property itself is removed from the
  799. * model as its subproperties are added.
  800. *
  801. * All intermediate getters must exist and should return non-null values
  802. * when the property value is accessed. If an intermediate getter returns
  803. * null, a null value will be returned.
  804. *
  805. * @see NestedMethodProperty
  806. * @see #addNestedContainerProperty(String)
  807. *
  808. * @param propertyId
  809. */
  810. @SuppressWarnings("unchecked")
  811. public void addNestedContainerBean(String propertyId) {
  812. Class<?> propertyType = getType(propertyId);
  813. LinkedHashMap<String, VaadinPropertyDescriptor<Object>> pds = BeanItem
  814. .getPropertyDescriptors((Class<Object>) propertyType);
  815. for (String subPropertyId : pds.keySet()) {
  816. String qualifiedPropertyId = propertyId + "." + subPropertyId;
  817. NestedPropertyDescriptor<BEANTYPE> pd = new NestedPropertyDescriptor<BEANTYPE>(
  818. qualifiedPropertyId, (Class<BEANTYPE>) type);
  819. model.put(qualifiedPropertyId, pd);
  820. model.remove(propertyId);
  821. for (BeanItem<BEANTYPE> item : itemIdToItem.values()) {
  822. item.addItemProperty(qualifiedPropertyId,
  823. pd.createProperty(item.getBean()));
  824. item.removeItemProperty(propertyId);
  825. }
  826. }
  827. // Sends a change event
  828. fireContainerPropertySetChange();
  829. }
  830. @Override
  831. public boolean removeContainerProperty(Object propertyId)
  832. throws UnsupportedOperationException {
  833. // Fails if the Property is not present
  834. if (!model.containsKey(propertyId)) {
  835. return false;
  836. }
  837. // Removes the Property to Property list and types
  838. model.remove(propertyId);
  839. // If remove the Property from all Items
  840. for (final Iterator<IDTYPE> i = getAllItemIds().iterator(); i
  841. .hasNext();) {
  842. getUnfilteredItem(i.next()).removeItemProperty(propertyId);
  843. }
  844. // Sends a change event
  845. fireContainerPropertySetChange();
  846. return true;
  847. }
  848. }