123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593 |
- /*
- @ITMillApache2LicenseForJavaFiles@
- */
- package com.vaadin.data.util;
-
- import java.beans.PropertyDescriptor;
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.Iterator;
- import java.util.LinkedHashMap;
- import java.util.LinkedList;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
-
- import com.vaadin.data.Container;
- import com.vaadin.data.Property;
- import com.vaadin.data.Container.Filterable;
- import com.vaadin.data.Container.Indexed;
- import com.vaadin.data.Container.ItemSetChangeNotifier;
- import com.vaadin.data.Container.Sortable;
- import com.vaadin.data.Property.ValueChangeEvent;
- import com.vaadin.data.Property.ValueChangeListener;
- import com.vaadin.data.Property.ValueChangeNotifier;
-
- /**
- * An {@link ArrayList} backed container for {@link BeanItem}s.
- * <p>
- * Bean objects act as identifiers. For this reason, they should implement
- * Object.equals(Object) and Object.hashCode().
- * </p>
- *
- * @param <BT>
- *
- * @since 5.4
- */
- @SuppressWarnings("serial")
- public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
- ItemSetChangeNotifier, ValueChangeListener {
- /**
- * The filteredItems variable contains the items that are visible outside
- * the container. If filters are enabled this contains a subset of allItems,
- * if no filters are set this contains the same items as allItems.
- */
- private ListSet<BT> filteredItems = new ListSet<BT>();
-
- /**
- * The allItems variable always contains all the items in the container.
- * Some or all of these are also in the filteredItems list.
- */
- private ListSet<BT> allItems = new ListSet<BT>();
-
- /**
- * Maps all pojos (item ids) in the container (including filtered) to their
- * corresponding BeanItem.
- */
- private final Map<BT, BeanItem<BT>> beanToItem = new HashMap<BT, BeanItem<BT>>();
-
- // internal data model to obtain property IDs etc.
- private final Class<? extends BT> type;
- private transient LinkedHashMap<String, PropertyDescriptor> model;
-
- private List<ItemSetChangeListener> itemSetChangeListeners;
-
- private Set<Filter> filters = new HashSet<Filter>();
-
- /**
- * The item sorter which is used for sorting the container.
- */
- private ItemSorter itemSorter = new DefaultItemSorter();
-
- /* Special serialization to handle method references */
- private void readObject(java.io.ObjectInputStream in) throws IOException,
- ClassNotFoundException {
- in.defaultReadObject();
- model = BeanItem.getPropertyDescriptors(type);
- }
-
- /**
- * Constructs BeanItemContainer for beans of a given type.
- *
- * @param type
- * the class of beans to be used with this containers.
- * @throws IllegalArgumentException
- * If the type is null
- */
- public BeanItemContainer(Class<? extends BT> type) {
- if (type == null) {
- throw new IllegalArgumentException(
- "The type passed to BeanItemContainer must not be null");
- }
- this.type = type;
- model = BeanItem.getPropertyDescriptors(type);
- }
-
- /**
- * Constructs BeanItemContainer with given collection of beans in it. The
- * collection must not be empty or an IllegalArgument is thrown.
- *
- * @param collection
- * non empty {@link Collection} of beans.
- * @throws IllegalArgumentException
- * If the collection is null or empty.
- */
- @SuppressWarnings("unchecked")
- public BeanItemContainer(Collection<BT> collection)
- throws IllegalArgumentException {
- if (collection == null || collection.isEmpty()) {
- throw new IllegalArgumentException(
- "The collection passed to BeanItemContainer must not be null or empty");
- }
-
- type = (Class<? extends BT>) collection.iterator().next().getClass();
- model = BeanItem.getPropertyDescriptors(type);
- addAll(collection);
- }
-
- private void addAll(Collection<BT> collection) {
- // Pre-allocate space for the collection
- allItems.ensureCapacity(allItems.size() + collection.size());
-
- int idx = size();
- for (BT bean : collection) {
- if (internalAddAt(idx, bean) != null) {
- idx++;
- }
- }
-
- // Filter the contents when all items have been added
- filterAll();
- }
-
- /**
- * Unsupported operation.
- *
- * @see com.vaadin.data.Container.Indexed#addItemAt(int)
- */
- public Object addItemAt(int index) throws UnsupportedOperationException {
- throw new UnsupportedOperationException();
- }
-
- /**
- * Adds new item at given index.
- *
- * The bean is used both as the item contents and as the item identifier.
- *
- * @see com.vaadin.data.Container.Indexed#addItemAt(int, Object)
- */
- public BeanItem<BT> addItemAt(int index, Object newItemId)
- throws UnsupportedOperationException {
- if (index < 0 || index > size()) {
- return null;
- } else if (index == 0) {
- // add before any item, visible or not
- return addItemAtInternalIndex(0, newItemId);
- } else {
- // if index==size(), adds immediately after last visible item
- return addItemAfter(getIdByIndex(index - 1), newItemId);
- }
- }
-
- /**
- * Adds new item at given index of the internal (unfiltered) list.
- * <p>
- * The item is also added in the visible part of the list if it passes the
- * filters.
- * </p>
- *
- * @param index
- * Internal index to add the new item.
- * @param newItemId
- * Id of the new item to be added.
- * @return Returns new item or null if the operation fails.
- */
- private BeanItem<BT> addItemAtInternalIndex(int index, Object newItemId) {
- BeanItem<BT> beanItem = internalAddAt(index, (BT) newItemId);
- if (beanItem != null) {
- filterAll();
- }
-
- return beanItem;
- }
-
- /**
- * Adds the bean to all internal data structures at the given position.
- * Fails if the bean is already in the container or is not assignable to the
- * correct type. Returns the new BeanItem if the bean was added
- * successfully.
- *
- * <p>
- * Caller should call {@link #filterAll()} after calling this method to
- * ensure the filtered list is updated.
- * </p>
- *
- * @param position
- * The position at which the bean should be inserted
- * @param bean
- * The bean to insert
- *
- * @return true if the bean was added successfully, false otherwise
- */
- private BeanItem<BT> internalAddAt(int position, BT bean) {
- // Make sure that the item has not been added previously
- if (allItems.contains(bean)) {
- return null;
- }
-
- if (!type.isAssignableFrom(bean.getClass())) {
- return null;
- }
-
- // "filteredList" will be updated in filterAll() which should be invoked
- // by the caller after calling this method.
- allItems.add(position, bean);
- BeanItem<BT> beanItem = new BeanItem<BT>(bean, model);
- beanToItem.put(bean, beanItem);
-
- // add listeners to be able to update filtering on property
- // changes
- for (Filter filter : filters) {
- // addValueChangeListener avoids adding duplicates
- addValueChangeListener(beanItem, filter.propertyId);
- }
-
- return beanItem;
- }
-
- @SuppressWarnings("unchecked")
- public BT getIdByIndex(int index) {
- return filteredItems.get(index);
- }
-
- public int indexOfId(Object itemId) {
- return filteredItems.indexOf(itemId);
- }
-
- /**
- * Unsupported operation.
- *
- * @see com.vaadin.data.Container.Ordered#addItemAfter(Object)
- */
- public Object addItemAfter(Object previousItemId)
- throws UnsupportedOperationException {
- throw new UnsupportedOperationException();
- }
-
- /**
- * Adds new item after the given item.
- *
- * The bean is used both as the item contents and as the item identifier.
- *
- * @see com.vaadin.data.Container.Ordered#addItemAfter(Object, Object)
- */
- public BeanItem<BT> addItemAfter(Object previousItemId, Object newItemId)
- throws UnsupportedOperationException {
- // only add if the previous item is visible
- if (containsId(previousItemId)) {
- return addItemAtInternalIndex(allItems.indexOf(previousItemId) + 1,
- newItemId);
- } else {
- return null;
- }
- }
-
- public BT firstItemId() {
- if (size() > 0) {
- return getIdByIndex(0);
- } else {
- return null;
- }
- }
-
- public boolean isFirstId(Object itemId) {
- return firstItemId() == itemId;
- }
-
- public boolean isLastId(Object itemId) {
- return lastItemId() == itemId;
- }
-
- public BT lastItemId() {
- if (size() > 0) {
- return getIdByIndex(size() - 1);
- } else {
- return null;
- }
- }
-
- public BT nextItemId(Object itemId) {
- int index = indexOfId(itemId);
- if (index >= 0 && index < size() - 1) {
- return getIdByIndex(index + 1);
- } else {
- // out of bounds
- return null;
- }
- }
-
- public BT prevItemId(Object itemId) {
- int index = indexOfId(itemId);
- if (index > 0) {
- return getIdByIndex(index - 1);
- } else {
- // out of bounds
- return null;
- }
- }
-
- public boolean addContainerProperty(Object propertyId, Class<?> type,
- Object defaultValue) throws UnsupportedOperationException {
- throw new UnsupportedOperationException();
- }
-
- /**
- * Unsupported operation.
- *
- * @see com.vaadin.data.Container#addItem()
- */
- public Object addItem() throws UnsupportedOperationException {
- throw new UnsupportedOperationException();
- }
-
- /**
- * Creates a new Item with the bean into the Container.
- *
- * The bean is used both as the item contents and as the item identifier.
- *
- * @see com.vaadin.data.Container#addItem(Object)
- */
- public BeanItem<BT> addBean(BT bean) {
- return addItem(bean);
- }
-
- /**
- * Creates a new Item with the bean into the Container.
- *
- * The bean is used both as the item contents and as the item identifier.
- *
- * @see com.vaadin.data.Container#addItem(Object)
- */
- public BeanItem<BT> addItem(Object itemId)
- throws UnsupportedOperationException {
- if (size() > 0) {
- // add immediately after last visible item
- int lastIndex = allItems.indexOf(lastItemId());
- return addItemAtInternalIndex(lastIndex + 1, itemId);
- } else {
- return addItemAtInternalIndex(0, itemId);
- }
- }
-
- public boolean containsId(Object itemId) {
- // only look at visible items after filtering
- return filteredItems.contains(itemId);
- }
-
- public Property getContainerProperty(Object itemId, Object propertyId) {
- return getItem(itemId).getItemProperty(propertyId);
- }
-
- public Collection<String> getContainerPropertyIds() {
- return model.keySet();
- }
-
- public BeanItem<BT> getItem(Object itemId) {
- return beanToItem.get(itemId);
- }
-
- @SuppressWarnings("unchecked")
- public Collection<BT> getItemIds() {
- return (Collection<BT>) filteredItems.clone();
- }
-
- public Class<?> getType(Object propertyId) {
- return model.get(propertyId).getPropertyType();
- }
-
- public boolean removeAllItems() throws UnsupportedOperationException {
- allItems.clear();
- filteredItems.clear();
- // detach listeners from all BeanItems
- for (BeanItem<BT> item : beanToItem.values()) {
- removeAllValueChangeListeners(item);
- }
- beanToItem.clear();
- fireItemSetChange();
- return true;
- }
-
- public boolean removeContainerProperty(Object propertyId)
- throws UnsupportedOperationException {
- throw new UnsupportedOperationException();
- }
-
- public boolean removeItem(Object itemId)
- throws UnsupportedOperationException {
- if (!allItems.remove(itemId)) {
- return false;
- }
- // detach listeners from Item
- removeAllValueChangeListeners(getItem(itemId));
- // remove item
- beanToItem.remove(itemId);
- filteredItems.remove(itemId);
- fireItemSetChange();
- return true;
- }
-
- private void addValueChangeListener(BeanItem<BT> beanItem, Object propertyId) {
- Property property = beanItem.getItemProperty(propertyId);
- if (property instanceof ValueChangeNotifier) {
- // avoid multiple notifications for the same property if
- // multiple filters are in use
- ValueChangeNotifier notifier = (ValueChangeNotifier) property;
- notifier.removeListener(this);
- notifier.addListener(this);
- }
- }
-
- private void removeValueChangeListener(BeanItem<BT> item, Object propertyId) {
- Property property = item.getItemProperty(propertyId);
- if (property instanceof ValueChangeNotifier) {
- ((ValueChangeNotifier) property).removeListener(this);
- }
- }
-
- private void removeAllValueChangeListeners(BeanItem<BT> item) {
- for (Object propertyId : item.getItemPropertyIds()) {
- removeValueChangeListener(item, propertyId);
- }
- }
-
- public int size() {
- return filteredItems.size();
- }
-
- public Collection<Object> getSortableContainerPropertyIds() {
- LinkedList<Object> sortables = new LinkedList<Object>();
- for (Object propertyId : getContainerPropertyIds()) {
- Class<?> propertyType = getType(propertyId);
- if (Comparable.class.isAssignableFrom(propertyType)) {
- sortables.add(propertyId);
- }
- }
- return sortables;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.data.Container.Sortable#sort(java.lang.Object[],
- * boolean[])
- */
- public void sort(Object[] propertyId, boolean[] ascending) {
- itemSorter.setSortProperties(this, propertyId, ascending);
-
- doSort();
-
- // notifies if anything changes in the filtered list, including order
- filterAll();
- }
-
- /**
- * Perform the sorting of the data structures in the container. This is
- * invoked when the <code>itemSorter</code> has been prepared for the sort
- * operation. Typically this method calls
- * <code>Collections.sort(aCollection, getItemSorter())</code> on all arrays
- * (containing item ids) that need to be sorted.
- *
- */
- protected void doSort() {
- Collections.sort(allItems, getItemSorter());
- }
-
- public void addListener(ItemSetChangeListener listener) {
- if (itemSetChangeListeners == null) {
- itemSetChangeListeners = new LinkedList<ItemSetChangeListener>();
- }
- itemSetChangeListeners.add(listener);
- }
-
- public void removeListener(ItemSetChangeListener listener) {
- if (itemSetChangeListeners != null) {
- itemSetChangeListeners.remove(listener);
- }
- }
-
- private void fireItemSetChange() {
- if (itemSetChangeListeners != null) {
- final Container.ItemSetChangeEvent event = new Container.ItemSetChangeEvent() {
- public Container getContainer() {
- return BeanItemContainer.this;
- }
- };
- for (ItemSetChangeListener listener : itemSetChangeListeners) {
- listener.containerItemSetChange(event);
- }
- }
- }
-
- public void addContainerFilter(Object propertyId, String filterString,
- boolean ignoreCase, boolean onlyMatchPrefix) {
- if (filters.isEmpty()) {
- filteredItems = (ListSet<BT>) allItems.clone();
- }
- // listen to change events to be able to update filtering
- for (BeanItem<BT> item : beanToItem.values()) {
- addValueChangeListener(item, propertyId);
- }
- Filter f = new Filter(propertyId, filterString, ignoreCase,
- onlyMatchPrefix);
- filter(f);
- filters.add(f);
- fireItemSetChange();
- }
-
- /**
- * Filter the view to recreate the visible item list from the unfiltered
- * items, and send a notification if the set of visible items changed in any
- * way.
- */
- protected void filterAll() {
- // avoid notification if the filtering had no effect
- List<BT> originalItems = filteredItems;
- // it is somewhat inefficient to do a (shallow) clone() every time
- filteredItems = (ListSet<BT>) allItems.clone();
- for (Filter f : filters) {
- filter(f);
- }
- // check if exactly the same items are there after filtering to avoid
- // unnecessary notifications
- // this may be slow in some cases as it uses BT.equals()
- if (!originalItems.equals(filteredItems)) {
- fireItemSetChange();
- }
- }
-
- protected void filter(Filter f) {
- Iterator<BT> iterator = filteredItems.iterator();
- while (iterator.hasNext()) {
- BT bean = iterator.next();
- if (!f.passesFilter(getItem(bean))) {
- iterator.remove();
- }
- }
- }
-
- public void removeAllContainerFilters() {
- if (!filters.isEmpty()) {
- filters = new HashSet<Filter>();
- // stop listening to change events for any property
- for (BeanItem<BT> item : beanToItem.values()) {
- removeAllValueChangeListeners(item);
- }
- filterAll();
- }
- }
-
- public void removeContainerFilters(Object propertyId) {
- if (!filters.isEmpty()) {
- for (Iterator<Filter> iterator = filters.iterator(); iterator
- .hasNext();) {
- Filter f = iterator.next();
- if (f.propertyId.equals(propertyId)) {
- iterator.remove();
- }
- }
- // stop listening to change events for the property
- for (BeanItem<BT> item : beanToItem.values()) {
- removeValueChangeListener(item, propertyId);
- }
- filterAll();
- }
- }
-
- public void valueChange(ValueChangeEvent event) {
- // if a property that is used in a filter is changed, refresh filtering
- filterAll();
- }
-
- public ItemSorter getItemSorter() {
- return itemSorter;
- }
-
- public void setItemSorter(ItemSorter itemSorter) {
- this.itemSorter = itemSorter;
- }
-
- }
|