diff options
author | Matti Tahvonen <matti.tahvonen@itmill.com> | 2010-03-05 13:43:43 +0000 |
---|---|---|
committer | Matti Tahvonen <matti.tahvonen@itmill.com> | 2010-03-05 13:43:43 +0000 |
commit | 7cefe21c703e6e7822ba7456d45fa49a14dd067d (patch) | |
tree | 8891e28e97bce4f9db44dd7f50cbb6a844de8aef /src/com/vaadin/data/util | |
parent | 9b84a02d3f46d1f4dca4cc3b17c388d51ca5194a (diff) | |
parent | 2b1deeeced6d098a22d6e52b70e94dbb9e96df57 (diff) | |
download | vaadin-framework-7cefe21c703e6e7822ba7456d45fa49a14dd067d.tar.gz vaadin-framework-7cefe21c703e6e7822ba7456d45fa49a14dd067d.zip |
merged changes from 6.3 + some container related changes
svn changeset:11664/svn branch:6.3_dd
Diffstat (limited to 'src/com/vaadin/data/util')
-rw-r--r-- | src/com/vaadin/data/util/BeanItemContainer.java | 127 | ||||
-rw-r--r-- | src/com/vaadin/data/util/ContainerHierarchicalWrapper.java | 13 | ||||
-rw-r--r-- | src/com/vaadin/data/util/HierarchicalContainer.java | 312 | ||||
-rw-r--r-- | src/com/vaadin/data/util/IndexedContainer.java | 29 | ||||
-rw-r--r-- | src/com/vaadin/data/util/ListSet.java | 198 |
5 files changed, 593 insertions, 86 deletions
diff --git a/src/com/vaadin/data/util/BeanItemContainer.java b/src/com/vaadin/data/util/BeanItemContainer.java index 7848de5eed..6510229d5a 100644 --- a/src/com/vaadin/data/util/BeanItemContainer.java +++ b/src/com/vaadin/data/util/BeanItemContainer.java @@ -38,9 +38,23 @@ import com.vaadin.data.Property.ValueChangeNotifier; @SuppressWarnings("serial") public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable, ItemSetChangeNotifier, ValueChangeListener { - // filtered and unfiltered item IDs - private ArrayList<BT> list = new ArrayList<BT>(); - private ArrayList<BT> allItems = new ArrayList<BT>(); + /** + * 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. @@ -89,6 +103,7 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable, * @throws IllegalArgumentException * If the collection is null or empty. */ + @SuppressWarnings("unchecked") public BeanItemContainer(Collection<BT> collection) throws IllegalArgumentException { if (collection == null || collection.isEmpty()) { @@ -98,10 +113,22 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable, type = (Class<? extends BT>) collection.iterator().next().getClass(); model = BeanItem.getPropertyDescriptors(type); - int i = 0; - for (BT bt : collection) { - addItemAt(i++, bt); + 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(); } /** @@ -147,36 +174,65 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable, * @return Returns new item or null if the operation fails. */ private BeanItem<BT> addItemAtInternalIndex(int index, Object newItemId) { - // Make sure that the Item has not been created yet - if (allItems.contains(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(newItemId.getClass())) { - BT pojo = (BT) newItemId; - // "list" will be updated in filterAll() - allItems.add(index, pojo); - BeanItem<BT> beanItem = new BeanItem<BT>(pojo, model); - beanToItem.put(pojo, beanItem); - // add listeners to be able to update filtering on property changes - for (Filter filter : filters) { - // addValueChangeListener avoids adding duplicates - addValueChangeListener(beanItem, filter.propertyId); - } - // it is somewhat suboptimal to filter all items - filterAll(); - return beanItem; - } else { + 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 list.get(index); + return filteredItems.get(index); } public int indexOfId(Object itemId) { - return list.indexOf(itemId); + return filteredItems.indexOf(itemId); } /** @@ -296,7 +352,7 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable, public boolean containsId(Object itemId) { // only look at visible items after filtering - return list.contains(itemId); + return filteredItems.contains(itemId); } public Property getContainerProperty(Object itemId, Object propertyId) { @@ -311,8 +367,9 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable, return beanToItem.get(itemId); } + @SuppressWarnings("unchecked") public Collection<BT> getItemIds() { - return (Collection<BT>) list.clone(); + return (Collection<BT>) filteredItems.clone(); } public Class<?> getType(Object propertyId) { @@ -321,7 +378,7 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable, public boolean removeAllItems() throws UnsupportedOperationException { allItems.clear(); - list.clear(); + filteredItems.clear(); // detach listeners from all BeanItems for (BeanItem<BT> item : beanToItem.values()) { removeAllValueChangeListeners(item); @@ -345,7 +402,7 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable, removeAllValueChangeListeners(getItem(itemId)); // remove item beanToItem.remove(itemId); - list.remove(itemId); + filteredItems.remove(itemId); fireItemSetChange(); return true; } @@ -375,7 +432,7 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable, } public int size() { - return list.size(); + return filteredItems.size(); } public Collection<Object> getSortableContainerPropertyIds() { @@ -445,7 +502,7 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable, public void addContainerFilter(Object propertyId, String filterString, boolean ignoreCase, boolean onlyMatchPrefix) { if (filters.isEmpty()) { - list = (ArrayList<BT>) allItems.clone(); + filteredItems = (ListSet<BT>) allItems.clone(); } // listen to change events to be able to update filtering for (BeanItem<BT> item : beanToItem.values()) { @@ -465,22 +522,22 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable, */ protected void filterAll() { // avoid notification if the filtering had no effect - List<BT> originalItems = list; + List<BT> originalItems = filteredItems; // it is somewhat inefficient to do a (shallow) clone() every time - list = (ArrayList<BT>) allItems.clone(); + 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(list)) { + if (!originalItems.equals(filteredItems)) { fireItemSetChange(); } } protected void filter(Filter f) { - Iterator<BT> iterator = list.iterator(); + Iterator<BT> iterator = filteredItems.iterator(); while (iterator.hasNext()) { BT bean = iterator.next(); if (!f.passesFilter(getItem(bean))) { diff --git a/src/com/vaadin/data/util/ContainerHierarchicalWrapper.java b/src/com/vaadin/data/util/ContainerHierarchicalWrapper.java index 80065f9254..8b6fedb8e4 100644 --- a/src/com/vaadin/data/util/ContainerHierarchicalWrapper.java +++ b/src/com/vaadin/data/util/ContainerHierarchicalWrapper.java @@ -220,7 +220,12 @@ public class ContainerHierarchicalWrapper implements Container.Hierarchical, return ((Container.Hierarchical) container) .areChildrenAllowed(itemId); } - return !noChildrenAllowed.contains(itemId); + + if (noChildrenAllowed.contains(itemId)) { + return false; + } + + return containsId(itemId); } /* @@ -284,7 +289,11 @@ public class ContainerHierarchicalWrapper implements Container.Hierarchical, return ((Container.Hierarchical) container).isRoot(itemId); } - return parent.get(itemId) == null; + if (parent.containsKey(itemId)) { + return false; + } + + return containsId(itemId); } /* diff --git a/src/com/vaadin/data/util/HierarchicalContainer.java b/src/com/vaadin/data/util/HierarchicalContainer.java index 2b5519e651..a404d83eb1 100644 --- a/src/com/vaadin/data/util/HierarchicalContainer.java +++ b/src/com/vaadin/data/util/HierarchicalContainer.java @@ -42,16 +42,29 @@ public class HierarchicalContainer extends IndexedContainer implements private final HashMap<Object, LinkedList<Object>> children = new HashMap<Object, LinkedList<Object>>(); /** + * Mapping from Item ID to a list of child IDs when filtered + */ + private HashMap<Object, LinkedList<Object>> filteredChildren = null; + + /** * List that contains all root elements of the container. */ private final LinkedList<Object> roots = new LinkedList<Object>(); + /** + * List that contains all filtered root elements of the container. + */ + private LinkedList<Object> filteredRoots = null; + /* * Can the specified Item have any children? Don't add a JavaDoc comment * here, we use the default documentation from implemented interface. */ public boolean areChildrenAllowed(Object itemId) { - return !noChildrenAllowed.contains(itemId); + if (noChildrenAllowed.contains(itemId)) { + return false; + } + return containsId(itemId); } /* @@ -60,7 +73,14 @@ public class HierarchicalContainer extends IndexedContainer implements * interface. */ public Collection getChildren(Object itemId) { - final Collection c = children.get(itemId); + LinkedList<Object> c; + + if (filteredChildren != null) { + c = filteredChildren.get(itemId); + } else { + c = children.get(itemId); + } + if (c == null) { return null; } @@ -82,7 +102,11 @@ public class HierarchicalContainer extends IndexedContainer implements * interface. */ public boolean hasChildren(Object itemId) { - return children.get(itemId) != null; + if (filteredChildren != null) { + return filteredChildren.containsKey(itemId); + } else { + return children.containsKey(itemId); + } } /* @@ -91,7 +115,15 @@ public class HierarchicalContainer extends IndexedContainer implements * interface. */ public boolean isRoot(Object itemId) { - return parent.get(itemId) == null; + if (filteredRoots != null && !filteredRoots.contains(itemId)) { + return false; + } + + if (parent.containsKey(itemId)) { + return false; + } + + return containsId(itemId); } /* @@ -100,7 +132,11 @@ public class HierarchicalContainer extends IndexedContainer implements * interface. */ public Collection rootItemIds() { - return Collections.unmodifiableCollection(roots); + if (filteredRoots != null) { + return Collections.unmodifiableCollection(filteredRoots); + } else { + return Collections.unmodifiableCollection(roots); + } } /** @@ -139,25 +175,6 @@ public class HierarchicalContainer extends IndexedContainer implements return true; } - @Override - public Item addItemAt(int index, Object newItemId) { - Item retval = super.addItemAt(index, newItemId); - if (getParent(newItemId) == null) { - int refIndex = roots.size() - 1; - int indexOfId = indexOfId(roots.get(refIndex)); - while (indexOfId > index) { - refIndex--; - if (refIndex < 0) { - // inserts as first - break; - } - indexOfId = indexOfId(roots.get(refIndex)); - } - roots.add(refIndex + 1, newItemId); - } - return retval; - } - /** * <p> * Sets the parent of an Item. The new parent item must exist and be able to @@ -191,27 +208,58 @@ public class HierarchicalContainer extends IndexedContainer implements return true; } - // Making root + // Making root? if (newParentId == null) { + // The itemId should become a root so we need to + // - Remove it from the old parent's children list (also filtered + // list) + // - Add it as a root + // - Remove it from the item -> parent list (parent is null for + // roots) // Removes from old parents children list - final LinkedList l = children.get(itemId); + final LinkedList<Object> l = children.get(itemId); if (l != null) { l.remove(itemId); if (l.isEmpty()) { children.remove(itemId); } + + if (filteredChildren != null) { + LinkedList<Object> f = filteredChildren.get(itemId); + if (f != null) { + f.remove(itemId); + if (f.isEmpty()) { + filteredChildren.remove(f); + } + } + } } // Add to be a root roots.add(itemId); + if (filteredRoots != null) { + if (passesFilters(itemId)) { + filteredRoots.add(itemId); + } + } // Updates parent parent.remove(itemId); + fireContentsChange(-1); + return true; } + // We get here when the item should not become a root and we need to + // - Verify the new parent exists and can have children + // - Check that the new parent is not a child of the selected itemId + // - Updated the item -> parent mapping to point to the new parent + // - Remove the item from the roots list if it was a root + // - Remove the item from the old parent's children list if it was not a + // root + // Checks that the new parent exists in container and can have // children if (!containsId(newParentId) || noChildrenAllowed.contains(newParentId)) { @@ -229,29 +277,92 @@ public class HierarchicalContainer extends IndexedContainer implements // Updates parent parent.put(itemId, newParentId); - LinkedList pcl = children.get(newParentId); + LinkedList<Object> pcl = children.get(newParentId); if (pcl == null) { - pcl = new LinkedList(); + // Create an empty list for holding children if one were not + // previously created + pcl = new LinkedList<Object>(); children.put(newParentId, pcl); } pcl.add(itemId); + // Add children list for filtered case also + if (filteredChildren != null) { + LinkedList<Object> f = filteredChildren.get(newParentId); + if (f == null) { + // Create an empty list for holding children if one were not + // previously created + f = new LinkedList<Object>(); + filteredChildren.put(newParentId, f); + } + } + // Removes from old parent or root if (oldParentId == null) { roots.remove(itemId); } else { - final LinkedList l = children.get(oldParentId); + final LinkedList<Object> l = children.get(oldParentId); if (l != null) { l.remove(itemId); if (l.isEmpty()) { children.remove(oldParentId); } } + if (filteredChildren != null) { + LinkedList<Object> f = filteredChildren.get(oldParentId); + if (f != null) { + f.remove(itemId); + if (f.isEmpty()) { + filteredChildren.remove(oldParentId); + } + } + } } + fireContentsChange(-1); + return true; } + /** + * TODO javadoc + * + * @param itemId + * @param siblingId + */ + public void moveAfterSibling(Object itemId, Object siblingId) { + Object parent2 = getParent(itemId); + LinkedList<Object> childrenList; + if (parent2 == null) { + childrenList = roots; + } else { + childrenList = children.get(parent2); + } + if (siblingId == null) { + childrenList.remove(itemId); + childrenList.addFirst(itemId); + + } else { + int oldIndex = childrenList.indexOf(itemId); + int indexOfSibling = childrenList.indexOf(siblingId); + if (indexOfSibling != -1 && oldIndex != -1) { + int newIndex; + if (oldIndex > indexOfSibling) { + newIndex = indexOfSibling + 1; + } else { + newIndex = indexOfSibling; + } + childrenList.remove(oldIndex); + childrenList.add(newIndex, itemId); + } else { + throw new IllegalArgumentException( + "Given identifiers no not have the same parent."); + } + } + fireContentsChange(-1); + + } + /* * (non-Javadoc) * @@ -259,12 +370,21 @@ public class HierarchicalContainer extends IndexedContainer implements */ @Override public Object addItem() { - final Object id = super.addItem(); - if (id != null && !roots.contains(id)) { - roots.add(id); + final Object itemId = super.addItem(); + if (itemId == null) { + return null; } - return id; + if (!roots.contains(itemId)) { + roots.add(itemId); + if (filteredRoots != null) { + if (passesFilters(itemId)) { + filteredRoots.add(itemId); + } + } + } + + return itemId; } /* @@ -275,9 +395,18 @@ public class HierarchicalContainer extends IndexedContainer implements @Override public Item addItem(Object itemId) { final Item item = super.addItem(itemId); - if (item != null) { - roots.add(itemId); + if (item == null) { + return null; + } + + roots.add(itemId); + + if (filteredRoots != null) { + if (passesFilters(itemId)) { + filteredRoots.add(itemId); + } } + return item; } @@ -295,6 +424,12 @@ public class HierarchicalContainer extends IndexedContainer implements parent.clear(); children.clear(); noChildrenAllowed.clear(); + if (filteredRoots != null) { + filteredRoots = null; + } + if (filteredChildren != null) { + filteredChildren = null; + } } return success; } @@ -309,20 +444,44 @@ public class HierarchicalContainer extends IndexedContainer implements final boolean success = super.removeItem(itemId); if (success) { - if (isRoot(itemId)) { - roots.remove(itemId); + // Remove from roots if this was a root + if (roots.remove(itemId)) { + + // If filtering is enabled we might need to remove it from the + // filtered list also + if (filteredRoots != null) { + filteredRoots.remove(itemId); + } } - LinkedList<Object> remove = children.remove(itemId); - if (remove != null) { - for (Object object : remove) { - removeItem(object); + + // Clear the children list. Old children will now become root nodes + LinkedList<Object> childNodeIds = children.remove(itemId); + if (childNodeIds != null) { + if (filteredChildren != null) { + filteredChildren.remove(itemId); + } + for (Object childId : childNodeIds) { + setParent(childId, null); } } - final Object p = parent.get(itemId); - if (p != null) { - final LinkedList c = children.get(p); + + // Parent of the item that we are removing will contain the item id + // in its children list + final Object parentItemId = parent.get(itemId); + if (parentItemId != null) { + final LinkedList<Object> c = children.get(parentItemId); if (c != null) { c.remove(itemId); + + // Found in the children list so might also be in the + // filteredChildren list + if (filteredChildren != null) { + LinkedList<Object> f = filteredChildren + .get(parentItemId); + if (f != null) { + f.remove(parentItemId); + } + } } } parent.remove(itemId); @@ -332,6 +491,35 @@ public class HierarchicalContainer extends IndexedContainer implements return success; } + /** + * Removes the Item identified by ItemId from the Container and all its + * children. + * + * @see #removeItem(Object) + * @param itemId + * the identifier of the Item to remove + * @return true if the operation succeeded + */ + public boolean removeItemRecursively(Object itemId) { + boolean success = true; + Collection<Object> children2 = getChildren(itemId); + if (children2 != null) { + Object[] array = children2.toArray(); + for (int i = 0; i < array.length; i++) { + boolean removeItemRecursively = removeItemRecursively(array[i]); + if (!removeItemRecursively) { + success = false; + } + } + } + boolean removeItem = removeItem(itemId); + if (!removeItem) { + success = false; + } + return success; + + } + /* * (non-Javadoc) * @@ -347,4 +535,40 @@ public class HierarchicalContainer extends IndexedContainer implements } } + /* + * Overridden to provide filtering for root & children items. + * + * (non-Javadoc) + * + * @see com.vaadin.data.util.IndexedContainer#updateContainerFiltering() + */ + @Override + protected void updateContainerFiltering() { + super.updateContainerFiltering(); + + filteredRoots = new LinkedList<Object>(); + filteredChildren = new HashMap<Object, LinkedList<Object>>(); + + // Filter root item ids + for (Object rootId : roots) { + if (passesFilters(rootId)) { + filteredRoots.add(rootId); + } + } + + // Filter children + for (Object parent : children.keySet()) { + if (passesFilters(parent)) { + LinkedList<Object> filtered = new LinkedList<Object>(); + filteredChildren.put(parent, filtered); + for (Object child : children.get(parent)) { + if (passesFilters(child)) { + filtered.add(child); + } + } + } + } + + } + } diff --git a/src/com/vaadin/data/util/IndexedContainer.java b/src/com/vaadin/data/util/IndexedContainer.java index d6d6d9e77c..3bac19cd7a 100644 --- a/src/com/vaadin/data/util/IndexedContainer.java +++ b/src/com/vaadin/data/util/IndexedContainer.java @@ -469,7 +469,13 @@ public class IndexedContainer implements Container.Indexed, return null; } try { - return itemIds.get(itemIds.indexOf(itemId) + 1); + int idx = itemIds.indexOf(itemId); + if (idx == -1) { + // If the given Item is not found in the Container, + // null is returned. + return null; + } + return itemIds.get(idx + 1); } catch (final IndexOutOfBoundsException e) { return null; } @@ -548,6 +554,11 @@ public class IndexedContainer implements Container.Indexed, * java.lang.Object) */ public Item addItemAfter(Object previousItemId, Object newItemId) { + // Adding an item after null item adds the item as first item of the + // ordered container. + if (previousItemId == null) { + return addItemAt(0, newItemId); + } // Get the index of the addition int index = -1; @@ -574,7 +585,11 @@ public class IndexedContainer implements Container.Indexed, // Creates a new id final Object id = generateId(); - return addItemAfter(previousItemId, id); + if (addItemAfter(previousItemId, id) != null) { + return id; + } else { + return null; + } } /* @@ -953,7 +968,7 @@ public class IndexedContainer implements Container.Indexed, * @param addedItemIndex * index of new item if change event was an item addition */ - private void fireContentsChange(int addedItemIndex) { + protected void fireContentsChange(int addedItemIndex) { if (itemSetChangeListeners != null) { final Object[] l = itemSetChangeListeners.toArray(); final Container.ItemSetChangeEvent event = new IndexedContainer.ItemSetChangeEvent( @@ -1549,7 +1564,7 @@ public class IndexedContainer implements Container.Indexed, } } - private void updateContainerFiltering() { + protected void updateContainerFiltering() { // Clearing filters? if (filters == null || filters.isEmpty()) { @@ -1571,7 +1586,7 @@ public class IndexedContainer implements Container.Indexed, // Filter for (final Iterator i = itemIds.iterator(); i.hasNext();) { final Object id = i.next(); - if (passesFilters(new IndexedContainerItem(id))) { + if (passesFilters(id)) { filteredItemIds.add(id); } } @@ -1579,6 +1594,10 @@ public class IndexedContainer implements Container.Indexed, fireContentsChange(-1); } + protected final boolean passesFilters(Object itemId) { + return passesFilters(new IndexedContainerItem(itemId)); + } + private boolean passesFilters(Item item) { if (filters == null) { return true; diff --git a/src/com/vaadin/data/util/ListSet.java b/src/com/vaadin/data/util/ListSet.java new file mode 100644 index 0000000000..8dc8c6bbfd --- /dev/null +++ b/src/com/vaadin/data/util/ListSet.java @@ -0,0 +1,198 @@ +package com.vaadin.data.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+
+/**
+ * ListSet is an internal Vaadin class which implements a combination of a List
+ * and a Set. The main purpose of this class is to provide a fast
+ * {@link #contains(Object)} method. Each inserted object must by unique (as
+ * specified by {@link #equals(Object)}).
+ *
+ * This class is subject to change and should not be used outside Vaadin core.
+ */
+public class ListSet<E> extends ArrayList<E> {
+ private HashSet<E> itemSet = null;
+
+ public ListSet() {
+ super();
+ itemSet = new HashSet<E>();
+ }
+
+ public ListSet(Collection<? extends E> c) {
+ super(c);
+ itemSet = new HashSet<E>(c.size());
+ itemSet.addAll(c);
+ }
+
+ public ListSet(int initialCapacity) {
+ super(initialCapacity);
+ itemSet = new HashSet<E>(initialCapacity);
+ }
+
+ // Delegate contains operations to the set
+ @Override
+ public boolean contains(Object o) {
+ return itemSet.contains(o);
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> c) {
+ return itemSet.containsAll(c);
+ }
+
+ // Methods for updating the set when the list is updated.
+ @Override
+ public boolean add(E e) {
+ if (contains(e)) {
+ // Duplicates are not allowed
+ return false;
+ }
+
+ if (super.add(e)) {
+ itemSet.add(e);
+ return true;
+ } else {
+ return false;
+ }
+ };
+
+ /**
+ * Works as java.util.ArrayList#add(int, java.lang.Object) but returns
+ * immediately if the element is already in the ListSet.
+ */
+ @Override
+ public void add(int index, E element) {
+ if (contains(element)) {
+ // Duplicates are not allowed
+ return;
+ }
+
+ super.add(index, element);
+ itemSet.add(element);
+ }
+
+ @Override
+ public boolean addAll(Collection<? extends E> c) {
+ boolean modified = false;
+ Iterator<? extends E> i = c.iterator();
+ while (i.hasNext()) {
+ E e = i.next();
+ if (contains(e)) {
+ continue;
+ }
+
+ if (add(e)) {
+ itemSet.add(e);
+ modified = true;
+ }
+ }
+ return modified;
+ }
+
+ @Override
+ public boolean addAll(int index, Collection<? extends E> c) {
+ ensureCapacity(size() + c.size());
+
+ boolean modified = false;
+ Iterator<? extends E> i = c.iterator();
+ while (i.hasNext()) {
+ E e = i.next();
+ if (contains(e)) {
+ continue;
+ }
+
+ add(index++, e);
+ itemSet.add(e);
+ modified = true;
+ }
+
+ return modified;
+ }
+
+ @Override
+ public void clear() {
+ super.clear();
+ itemSet.clear();
+ }
+
+ @Override
+ public int indexOf(Object o) {
+ if (!contains(o)) {
+ return -1;
+ }
+
+ return super.indexOf(o);
+ }
+
+ @Override
+ public int lastIndexOf(Object o) {
+ if (!contains(o)) {
+ return -1;
+ }
+
+ return super.lastIndexOf(o);
+ }
+
+ @Override
+ public E remove(int index) {
+ E e = super.remove(index);
+
+ if (e != null) {
+ itemSet.remove(e);
+ }
+
+ return e;
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ if (super.remove(o)) {
+ itemSet.remove(o);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ protected void removeRange(int fromIndex, int toIndex) {
+ HashSet<E> toRemove = new HashSet<E>();
+ for (int idx = fromIndex; idx < toIndex; idx++) {
+ toRemove.add(get(idx));
+ }
+ super.removeRange(fromIndex, toIndex);
+ itemSet.removeAll(toRemove);
+ }
+
+ @Override
+ public E set(int index, E element) {
+ if (contains(element)) {
+ // Element already exist in the list
+ if (get(index) == element) {
+ // At the same position, nothing to be done
+ return element;
+ } else {
+ // At another position, cannot set
+ return null;
+ }
+ }
+
+ E old = super.set(index, element);
+ itemSet.remove(old);
+ itemSet.add(element);
+
+ return old;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Object clone() {
+ ListSet<E> v = (ListSet<E>) super.clone();
+ v.itemSet = new HashSet<E>(itemSet);
+ return v;
+ }
+
+}
|