Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

BeanItemContainer.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. package com.vaadin.data.util;
  2. import java.beans.PropertyDescriptor;
  3. import java.io.IOException;
  4. import java.util.ArrayList;
  5. import java.util.Collection;
  6. import java.util.Collections;
  7. import java.util.Comparator;
  8. import java.util.HashMap;
  9. import java.util.HashSet;
  10. import java.util.Iterator;
  11. import java.util.LinkedHashMap;
  12. import java.util.LinkedList;
  13. import java.util.List;
  14. import java.util.Map;
  15. import java.util.Set;
  16. import com.vaadin.data.Container;
  17. import com.vaadin.data.Property;
  18. import com.vaadin.data.Container.Filterable;
  19. import com.vaadin.data.Container.Indexed;
  20. import com.vaadin.data.Container.ItemSetChangeNotifier;
  21. import com.vaadin.data.Container.Sortable;
  22. import com.vaadin.data.Property.ValueChangeEvent;
  23. import com.vaadin.data.Property.ValueChangeListener;
  24. import com.vaadin.data.Property.ValueChangeNotifier;
  25. /**
  26. * An {@link ArrayList} backed container for {@link BeanItem}s.
  27. * <p>
  28. * Bean objects act as identifiers. For this reason, they should implement
  29. * Object.equals(Object) and Object.hashCode().
  30. * </p>
  31. *
  32. * @param <BT>
  33. *
  34. * @since 5.4
  35. */
  36. @SuppressWarnings("serial")
  37. public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
  38. ItemSetChangeNotifier, ValueChangeListener {
  39. // filtered and unfiltered item IDs
  40. private ArrayList<BT> list = new ArrayList<BT>();
  41. private ArrayList<BT> allItems = new ArrayList<BT>();
  42. private final Map<BT, BeanItem> beanToItem = new HashMap<BT, BeanItem>();
  43. // internal data model to obtain property IDs etc.
  44. private final Class<BT> type;
  45. private transient LinkedHashMap<String, PropertyDescriptor> model;
  46. private List<ItemSetChangeListener> itemSetChangeListeners;
  47. private Set<Filter> filters = new HashSet<Filter>();
  48. /* Special serialization to handle method references */
  49. private void readObject(java.io.ObjectInputStream in) throws IOException,
  50. ClassNotFoundException {
  51. in.defaultReadObject();
  52. model = BeanItem.getPropertyDescriptors(type);
  53. }
  54. /**
  55. * Constructs BeanItemContainer for beans of a given type.
  56. *
  57. * @param type
  58. * the class of beans to be used with this containers.
  59. * @throws IllegalArgumentException
  60. * If the type is null
  61. */
  62. public BeanItemContainer(Class<BT> type) {
  63. if (type == null) {
  64. throw new IllegalArgumentException(
  65. "The type passed to BeanItemContainer must not be null");
  66. }
  67. this.type = type;
  68. model = BeanItem.getPropertyDescriptors(type);
  69. }
  70. /**
  71. * Constructs BeanItemContainer with given collection of beans in it. The
  72. * collection must not be empty or an IllegalArgument is thrown.
  73. *
  74. * @param collection
  75. * non empty {@link Collection} of beans.
  76. * @throws IllegalArgumentException
  77. * If the collection is null or empty.
  78. */
  79. public BeanItemContainer(Collection<BT> collection)
  80. throws IllegalArgumentException {
  81. if (collection == null || collection.isEmpty()) {
  82. throw new IllegalArgumentException(
  83. "The collection passed to BeanItemContainer must not be null or empty");
  84. }
  85. type = (Class<BT>) collection.iterator().next().getClass();
  86. model = BeanItem.getPropertyDescriptors(type);
  87. int i = 0;
  88. for (BT bt : collection) {
  89. addItemAt(i++, bt);
  90. }
  91. }
  92. /**
  93. * Unsupported operation.
  94. *
  95. * @see com.vaadin.data.Container.Indexed#addItemAt(int)
  96. */
  97. public Object addItemAt(int index) throws UnsupportedOperationException {
  98. throw new UnsupportedOperationException();
  99. }
  100. /**
  101. * Adds new item at given index.
  102. *
  103. * The bean is used both as the item contents and as the item identifier.
  104. *
  105. * @see com.vaadin.data.Container.Indexed#addItemAt(int, Object)
  106. */
  107. public BeanItem addItemAt(int index, Object newItemId)
  108. throws UnsupportedOperationException {
  109. if (index < 0 || index > size()) {
  110. return null;
  111. } else if (index == 0) {
  112. // add before any item, visible or not
  113. return addItemAtInternalIndex(0, newItemId);
  114. } else {
  115. // if index==size(), adds immediately after last visible item
  116. return addItemAfter(getIdByIndex(index - 1), newItemId);
  117. }
  118. }
  119. /**
  120. * Adds new item at given index of the internal (unfiltered) list.
  121. * <p>
  122. * The item is also added in the visible part of the list if it passes the
  123. * filters.
  124. * </p>
  125. *
  126. * @param index
  127. * Internal index to add the new item.
  128. * @param newItemId
  129. * Id of the new item to be added.
  130. * @return Returns new item or null if the operation fails.
  131. */
  132. private BeanItem addItemAtInternalIndex(int index, Object newItemId) {
  133. // Make sure that the Item has not been created yet
  134. if (allItems.contains(newItemId)) {
  135. return null;
  136. }
  137. if (type.isAssignableFrom(newItemId.getClass())) {
  138. BT pojo = (BT) newItemId;
  139. // "list" will be updated in filterAll()
  140. allItems.add(index, pojo);
  141. BeanItem beanItem = new BeanItem(pojo, model);
  142. beanToItem.put(pojo, beanItem);
  143. // add listeners to be able to update filtering on property changes
  144. for (Filter filter : filters) {
  145. // addValueChangeListener avoids adding duplicates
  146. addValueChangeListener(beanItem, filter.propertyId);
  147. }
  148. // it is somewhat suboptimal to filter all items
  149. filterAll();
  150. return beanItem;
  151. } else {
  152. return null;
  153. }
  154. }
  155. public BT getIdByIndex(int index) {
  156. return list.get(index);
  157. }
  158. public int indexOfId(Object itemId) {
  159. return list.indexOf(itemId);
  160. }
  161. /**
  162. * Unsupported operation.
  163. *
  164. * @see com.vaadin.data.Container.Ordered#addItemAfter(Object)
  165. */
  166. public Object addItemAfter(Object previousItemId)
  167. throws UnsupportedOperationException {
  168. throw new UnsupportedOperationException();
  169. }
  170. /**
  171. * Adds new item after the given item.
  172. *
  173. * The bean is used both as the item contents and as the item identifier.
  174. *
  175. * @see com.vaadin.data.Container.Ordered#addItemAfter(Object,
  176. * Object)
  177. */
  178. public BeanItem addItemAfter(Object previousItemId, Object newItemId)
  179. throws UnsupportedOperationException {
  180. // only add if the previous item is visible
  181. if (list.contains(previousItemId)) {
  182. return addItemAtInternalIndex(allItems.indexOf(previousItemId) + 1,
  183. newItemId);
  184. } else {
  185. return null;
  186. }
  187. }
  188. public BT firstItemId() {
  189. if (list.size() > 0) {
  190. return list.get(0);
  191. } else {
  192. return null;
  193. }
  194. }
  195. public boolean isFirstId(Object itemId) {
  196. return firstItemId() == itemId;
  197. }
  198. public boolean isLastId(Object itemId) {
  199. return lastItemId() == itemId;
  200. }
  201. public BT lastItemId() {
  202. if (list.size() > 0) {
  203. return list.get(list.size() - 1);
  204. } else {
  205. return null;
  206. }
  207. }
  208. public BT nextItemId(Object itemId) {
  209. int index = list.indexOf(itemId);
  210. if (index >= 0 && index < list.size() - 1) {
  211. return list.get(index + 1);
  212. } else {
  213. // out of bounds
  214. return null;
  215. }
  216. }
  217. public BT prevItemId(Object itemId) {
  218. int index = list.indexOf(itemId);
  219. if (index > 0) {
  220. return list.get(index - 1);
  221. } else {
  222. // out of bounds
  223. return null;
  224. }
  225. }
  226. @SuppressWarnings("unchecked")
  227. public boolean addContainerProperty(Object propertyId, Class type,
  228. Object defaultValue) throws UnsupportedOperationException {
  229. throw new UnsupportedOperationException();
  230. }
  231. /**
  232. * Unsupported operation.
  233. *
  234. * @see com.vaadin.data.Container#addItem()
  235. */
  236. public Object addItem() throws UnsupportedOperationException {
  237. throw new UnsupportedOperationException();
  238. }
  239. /**
  240. * Creates a new Item with the bean into the Container.
  241. *
  242. * The bean is used both as the item contents and as the item identifier.
  243. *
  244. * @see com.vaadin.data.Container#addItem(Object)
  245. */
  246. public BeanItem addBean(BT bean) {
  247. return addItem(bean);
  248. }
  249. /**
  250. * Creates a new Item with the bean into the Container.
  251. *
  252. * The bean is used both as the item contents and as the item identifier.
  253. *
  254. * @see com.vaadin.data.Container#addItem(Object)
  255. */
  256. public BeanItem addItem(Object itemId) throws UnsupportedOperationException {
  257. if (list.size() > 0) {
  258. // add immediately after last visible item
  259. int lastIndex = allItems.indexOf(lastItemId());
  260. return addItemAtInternalIndex(lastIndex + 1, itemId);
  261. } else {
  262. return addItemAtInternalIndex(0, itemId);
  263. }
  264. }
  265. public boolean containsId(Object itemId) {
  266. // only look at visible items after filtering
  267. return list.contains(itemId);
  268. }
  269. public Property getContainerProperty(Object itemId, Object propertyId) {
  270. return beanToItem.get(itemId).getItemProperty(propertyId);
  271. }
  272. public Collection<String> getContainerPropertyIds() {
  273. return model.keySet();
  274. }
  275. public BeanItem getItem(Object itemId) {
  276. return beanToItem.get(itemId);
  277. }
  278. public Collection<BT> getItemIds() {
  279. return (Collection<BT>) list.clone();
  280. }
  281. public Class<?> getType(Object propertyId) {
  282. return model.get(propertyId).getPropertyType();
  283. }
  284. public boolean removeAllItems() throws UnsupportedOperationException {
  285. allItems.clear();
  286. list.clear();
  287. // detach listeners from all BeanItems
  288. for (BeanItem item : beanToItem.values()) {
  289. removeAllValueChangeListeners(item);
  290. }
  291. beanToItem.clear();
  292. fireItemSetChange();
  293. return true;
  294. }
  295. public boolean removeContainerProperty(Object propertyId)
  296. throws UnsupportedOperationException {
  297. throw new UnsupportedOperationException();
  298. }
  299. public boolean removeItem(Object itemId)
  300. throws UnsupportedOperationException {
  301. if (!allItems.remove(itemId)) {
  302. return false;
  303. }
  304. // detach listeners from Item
  305. removeAllValueChangeListeners(beanToItem.get(itemId));
  306. // remove item
  307. beanToItem.remove(itemId);
  308. list.remove(itemId);
  309. fireItemSetChange();
  310. return true;
  311. }
  312. private void addValueChangeListener(BeanItem beanItem, Object propertyId) {
  313. Property property = beanItem.getItemProperty(propertyId);
  314. if (property instanceof ValueChangeNotifier) {
  315. // avoid multiple notifications for the same property if
  316. // multiple filters are in use
  317. ValueChangeNotifier notifier = (ValueChangeNotifier) property;
  318. notifier.removeListener(this);
  319. notifier.addListener(this);
  320. }
  321. }
  322. private void removeValueChangeListener(BeanItem item, Object propertyId) {
  323. Property property = item.getItemProperty(propertyId);
  324. if (property instanceof ValueChangeNotifier) {
  325. ((ValueChangeNotifier) property).removeListener(this);
  326. }
  327. }
  328. private void removeAllValueChangeListeners(BeanItem item) {
  329. for (Object propertyId : item.getItemPropertyIds()) {
  330. removeValueChangeListener(item, propertyId);
  331. }
  332. }
  333. public int size() {
  334. return list.size();
  335. }
  336. public Collection<Object> getSortableContainerPropertyIds() {
  337. LinkedList<Object> sortables = new LinkedList<Object>();
  338. for (Object propertyId : getContainerPropertyIds()) {
  339. Class<?> propertyType = getType(propertyId);
  340. if (Comparable.class.isAssignableFrom(propertyType)) {
  341. sortables.add(propertyId);
  342. }
  343. }
  344. return sortables;
  345. }
  346. public void sort(Object[] propertyId, boolean[] ascending) {
  347. for (int i = 0; i < ascending.length; i++) {
  348. final boolean asc = ascending[i];
  349. final Object property = propertyId[i];
  350. // sort allItems, then filter and notify
  351. Collections.sort(allItems, new Comparator<BT>() {
  352. @SuppressWarnings("unchecked")
  353. public int compare(BT a, BT b) {
  354. Comparable va, vb;
  355. if (asc) {
  356. va = (Comparable) beanToItem.get(a).getItemProperty(
  357. property).getValue();
  358. vb = (Comparable) beanToItem.get(b).getItemProperty(
  359. property).getValue();
  360. } else {
  361. va = (Comparable) beanToItem.get(b).getItemProperty(
  362. property).getValue();
  363. vb = (Comparable) beanToItem.get(a).getItemProperty(
  364. property).getValue();
  365. }
  366. /*
  367. * Null values are considered less than all others. The
  368. * compareTo method cannot handle null values for the
  369. * standard types.
  370. */
  371. if (va == null) {
  372. return (vb == null) ? 0 : -1;
  373. } else if (vb == null) {
  374. return (va == null) ? 0 : 1;
  375. }
  376. return va.compareTo(vb);
  377. }
  378. });
  379. }
  380. // notifies if anything changes in the filtered list, including order
  381. filterAll();
  382. }
  383. public void addListener(ItemSetChangeListener listener) {
  384. if (itemSetChangeListeners == null) {
  385. itemSetChangeListeners = new LinkedList<ItemSetChangeListener>();
  386. }
  387. itemSetChangeListeners.add(listener);
  388. }
  389. public void removeListener(ItemSetChangeListener listener) {
  390. if (itemSetChangeListeners != null) {
  391. itemSetChangeListeners.remove(listener);
  392. }
  393. }
  394. private void fireItemSetChange() {
  395. if (itemSetChangeListeners != null) {
  396. final Container.ItemSetChangeEvent event = new Container.ItemSetChangeEvent() {
  397. public Container getContainer() {
  398. return BeanItemContainer.this;
  399. }
  400. };
  401. for (ItemSetChangeListener listener : itemSetChangeListeners) {
  402. listener.containerItemSetChange(event);
  403. }
  404. }
  405. }
  406. public void addContainerFilter(Object propertyId, String filterString,
  407. boolean ignoreCase, boolean onlyMatchPrefix) {
  408. if (filters.isEmpty()) {
  409. list = (ArrayList<BT>) allItems.clone();
  410. }
  411. // listen to change events to be able to update filtering
  412. for (BeanItem item : beanToItem.values()) {
  413. addValueChangeListener(item, propertyId);
  414. }
  415. Filter f = new Filter(propertyId, filterString, ignoreCase,
  416. onlyMatchPrefix);
  417. filter(f);
  418. filters.add(f);
  419. fireItemSetChange();
  420. }
  421. /**
  422. * Filter the view to recreate the visible item list from the unfiltered
  423. * items, and send a notification if the set of visible items changed in any
  424. * way.
  425. */
  426. protected void filterAll() {
  427. // avoid notification if the filtering had no effect
  428. List<BT> originalItems = list;
  429. // it is somewhat inefficient to do a (shallow) clone() every time
  430. list = (ArrayList<BT>) allItems.clone();
  431. for (Filter f : filters) {
  432. filter(f);
  433. }
  434. // check if exactly the same items are there after filtering to avoid
  435. // unnecessary notifications
  436. // this may be slow in some cases as it uses BT.equals()
  437. if (!originalItems.equals(list)) {
  438. fireItemSetChange();
  439. }
  440. }
  441. protected void filter(Filter f) {
  442. Iterator<BT> iterator = list.iterator();
  443. while (iterator.hasNext()) {
  444. BT bean = iterator.next();
  445. if (!f.passesFilter(getItem(bean))) {
  446. iterator.remove();
  447. }
  448. }
  449. }
  450. public void removeAllContainerFilters() {
  451. if (!filters.isEmpty()) {
  452. filters = new HashSet<Filter>();
  453. // stop listening to change events for any property
  454. for (BeanItem item : beanToItem.values()) {
  455. removeAllValueChangeListeners(item);
  456. }
  457. filterAll();
  458. }
  459. }
  460. public void removeContainerFilters(Object propertyId) {
  461. if (!filters.isEmpty()) {
  462. for (Iterator<Filter> iterator = filters.iterator(); iterator
  463. .hasNext();) {
  464. Filter f = iterator.next();
  465. if (f.propertyId.equals(propertyId)) {
  466. iterator.remove();
  467. }
  468. }
  469. // stop listening to change events for the property
  470. for (BeanItem item : beanToItem.values()) {
  471. removeValueChangeListener(item, propertyId);
  472. }
  473. filterAll();
  474. }
  475. }
  476. public void valueChange(ValueChangeEvent event) {
  477. // if a property that is used in a filter is changed, refresh filtering
  478. filterAll();
  479. }
  480. }