diff --git a/impl/src/main/java/org/jboss/weld/extensions/util/collections/AbstractMapEntry.java b/impl/src/main/java/org/jboss/weld/extensions/util/collections/AbstractMapEntry.java new file mode 100644 index 0000000..b69be59 --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/extensions/util/collections/AbstractMapEntry.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jboss.weld.extensions.util.collections; + +import java.util.Map.Entry; + +/** + * Implementation of the {@code equals}, {@code hashCode}, and {@code toString} + * methods of {@code Entry}. + * + * @author Jared Levy + */ +abstract class AbstractMapEntry implements Entry { + + public abstract K getKey(); + + public abstract V getValue(); + + public V setValue(V value) { + throw new UnsupportedOperationException(); + } + + @Override public boolean equals(Object object) { + if (object instanceof Entry) { + Entry that = (Entry) object; + return Objects.equal(this.getKey(), that.getKey()) + && Objects.equal(this.getValue(), that.getValue()); + } + return false; + } + + @Override public int hashCode() { + K k = getKey(); + V v = getValue(); + return ((k == null) ? 0 : k.hashCode()) ^ ((v == null) ? 0 : v.hashCode()); + } + + /** + * Returns a string representation of the form {key}={value}. + */ + @Override public String toString() { + return getKey() + "=" + getValue(); + } +} diff --git a/impl/src/main/java/org/jboss/weld/extensions/util/collections/AbstractMultimap.java b/impl/src/main/java/org/jboss/weld/extensions/util/collections/AbstractMultimap.java new file mode 100644 index 0000000..c7e78e5 --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/extensions/util/collections/AbstractMultimap.java @@ -0,0 +1,1852 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jboss.weld.extensions.util.collections; + +import static org.jboss.weld.extensions.util.collections.Preconditions.checkArgument; +import static org.jboss.weld.extensions.util.collections.Preconditions.checkNotNull; +import static org.jboss.weld.extensions.util.collections.Preconditions.checkState; + +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.RandomAccess; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; + +/** + * Basic implementation of the {@link Multimap} interface. This class represents + * a multimap as a map that associates each key with a collection of values. All + * methods of {@link Multimap} are supported, including those specified as + * optional in the interface. + * + *

+ * To implement a multimap, a subclass must define the method + * {@link #createCollection()}, which creates an empty collection of values for + * a key. + * + *

+ * The multimap constructor takes a map that has a single entry for each + * distinct key. When you insert a key-value pair with a key that isn't already + * in the multimap, {@code AbstractMultimap} calls {@link #createCollection()} + * to create the collection of values for that key. The subclass should not call + * {@link #createCollection()} directly, and a new instance should be created + * every time the method is called. + * + *

+ * For example, the subclass could pass a {@link java.util.TreeMap} during + * construction, and {@link #createCollection()} could return a + * {@link java.util.TreeSet}, in which case the multimap's iterators would + * propagate through the keys and values in sorted order. + * + *

+ * Keys and values may be null, as long as the underlying collection classes + * support null elements. + * + *

+ * The collections created by {@link #createCollection()} may or may not allow + * duplicates. If the collection, such as a {@link Set}, does not support + * duplicates, an added key-value pair will replace an existing pair with the + * same key and value, if such a pair is present. With collections like + * {@link List} that allow duplicates, the collection will keep the existing + * key-value pairs while adding a new pair. + * + *

+ * This class is not threadsafe when any concurrent operations update the + * multimap, even if the underlying map and {@link #createCollection()} method + * return threadsafe classes. Concurrent read operations will work correctly. To + * allow concurrent update operations, wrap your multimap with a call to + * Multimaps#synchronizedMultimap. + * + *

+ * For serialization to work, the subclass must specify explicit {@code + * readObject} and {@code writeObject} methods. + * + * @author Jared Levy + */ +abstract class AbstractMultimap implements Multimap, Serializable +{ + /* + * Here's an outline of the overall design. + * + * The map variable contains the collection of values associated with each + * key. When a key-value pair is added to a multimap that didn't previously + * contain any values for that key, a new collection generated by + * createCollection is added to the map. That same collection instance + * remains in the map as long as the multimap has any values for the key. If + * all values for the key are removed, the key and collection are removed + * from the map. + * + * The get method returns a WrappedCollection, which decorates the collection + * in the map (if the key is present) or an empty collection (if the key is + * not present). When the collection delegate in the WrappedCollection is + * empty, the multimap may contain subsequently added values for that key. To + * handle that situation, the WrappedCollection checks whether map contains + * an entry for the provided key, and if so replaces the delegate. + */ + + private transient Map> map; + private transient int totalSize; + + /** + * Creates a new multimap that uses the provided map. + * + * @param map place to store the mapping from each key to its corresponding + * values + * @throws IllegalArgumentException if {@code map} is not empty + */ + protected AbstractMultimap(Map> map) + { + checkArgument(map.isEmpty()); + this.map = map; + } + + /** Used during deserialization only. */ + final void setMap(Map> map) + { + this.map = map; + totalSize = 0; + for (Collection values : map.values()) + { + checkArgument(!values.isEmpty()); + totalSize += values.size(); + } + } + + /** + * Creates the collection of values for a single key. + * + *

+ * Collections with weak, soft, or phantom references are not supported. Each + * call to {@code createCollection} should create a new instance. + * + *

+ * The returned collection class determines whether duplicate key-value pairs + * are allowed. + * + * @return an empty collection of values + */ + abstract Collection createCollection(); + + /** + * Creates the collection of values for an explicitly provided key. By + * default, it simply calls {@link #createCollection()}, which is the correct + * behavior for most implementations. The LinkedHashMultimap class overrides + * it. + * + * @param key key to associate with values in the collection + * @return an empty collection of values + */ + Collection createCollection(K key) + { + return createCollection(); + } + + Map> backingMap() + { + return map; + } + + // Query Operations + + public int size() + { + return totalSize; + } + + public boolean isEmpty() + { + return totalSize == 0; + } + + public boolean containsKey(Object key) + { + return map.containsKey(key); + } + + public boolean containsValue(Object value) + { + for (Collection collection : map.values()) + { + if (collection.contains(value)) + { + return true; + } + } + + return false; + } + + public boolean containsEntry(Object key, Object value) + { + Collection collection = map.get(key); + return collection != null && collection.contains(value); + } + + // Modification Operations + + public boolean put(K key, V value) + { + Collection collection = getOrCreateCollection(key); + + if (collection.add(value)) + { + totalSize++; + return true; + } + else + { + return false; + } + } + + private Collection getOrCreateCollection(K key) + { + Collection collection = map.get(key); + if (collection == null) + { + collection = createCollection(key); + map.put(key, collection); + } + return collection; + } + + public boolean remove(Object key, Object value) + { + Collection collection = map.get(key); + if (collection == null) + { + return false; + } + + boolean changed = collection.remove(value); + if (changed) + { + totalSize--; + if (collection.isEmpty()) + { + map.remove(key); + } + } + return changed; + } + + // Bulk Operations + + public boolean putAll(K key, Iterable values) + { + if (!values.iterator().hasNext()) + { + return false; + } + Collection collection = getOrCreateCollection(key); + int oldSize = collection.size(); + + boolean changed = false; + if (values instanceof Collection) + { + Collection c = (Collection) values; + changed = collection.addAll(c); + } + else + { + for (V value : values) + { + changed |= collection.add(value); + } + } + + totalSize += (collection.size() - oldSize); + return changed; + } + + public boolean putAll(Multimap multimap) + { + boolean changed = false; + for (Map.Entry entry : multimap.entries()) + { + changed |= put(entry.getKey(), entry.getValue()); + } + return changed; + } + + /** + * {@inheritDoc} + * + *

+ * The returned collection is immutable. + */ + public Collection replaceValues(K key, Iterable values) + { + Iterator iterator = values.iterator(); + if (!iterator.hasNext()) + { + return removeAll(key); + } + + Collection collection = getOrCreateCollection(key); + Collection oldValues = createCollection(); + oldValues.addAll(collection); + + totalSize -= collection.size(); + collection.clear(); + + while (iterator.hasNext()) + { + if (collection.add(iterator.next())) + { + totalSize++; + } + } + + return unmodifiableCollectionSubclass(oldValues); + } + + /** + * {@inheritDoc} + * + *

+ * The returned collection is immutable. + */ + public Collection removeAll(Object key) + { + Collection collection = map.remove(key); + Collection output = createCollection(); + + if (collection != null) + { + output.addAll(collection); + totalSize -= collection.size(); + collection.clear(); + } + + return unmodifiableCollectionSubclass(output); + } + + private Collection unmodifiableCollectionSubclass(Collection collection) + { + if (collection instanceof SortedSet) + { + return Collections.unmodifiableSortedSet((SortedSet) collection); + } + else if (collection instanceof Set) + { + return Collections.unmodifiableSet((Set) collection); + } + else if (collection instanceof List) + { + return Collections.unmodifiableList((List) collection); + } + else + { + return Collections.unmodifiableCollection(collection); + } + } + + public void clear() + { + // Clear each collection, to make previously returned collections empty. + for (Collection collection : map.values()) + { + collection.clear(); + } + map.clear(); + totalSize = 0; + } + + // Views + + /** + * {@inheritDoc} + * + *

+ * The returned collection is not serializable. + */ + public Collection get(K key) + { + Collection collection = map.get(key); + if (collection == null) + { + collection = createCollection(key); + } + return wrapCollection(key, collection); + } + + /** + * Generates a decorated collection that remains consistent with the values + * in the multimap for the provided key. Changes to the multimap may alter + * the returned collection, and vice versa. + */ + private Collection wrapCollection(K key, Collection collection) + { + if (collection instanceof SortedSet) + { + return new WrappedSortedSet(key, (SortedSet) collection, null); + } + else if (collection instanceof Set) + { + return new WrappedSet(key, (Set) collection); + } + else if (collection instanceof List) + { + return wrapList(key, (List) collection, null); + } + else + { + return new WrappedCollection(key, collection, null); + } + } + + private List wrapList(K key, List list, WrappedCollection ancestor) + { + return (list instanceof RandomAccess) ? new RandomAccessWrappedList(key, list, ancestor) : new WrappedList(key, list, ancestor); + } + + /** + * Collection decorator that stays in sync with the multimap values for a + * key. There are two kinds of wrapped collections: full and subcollections. + * Both have a delegate pointing to the underlying collection class. + * + *

+ * Full collections, identified by a null ancestor field, contain all + * multimap values for a given key. Its delegate is a value in + * {@link AbstractMultimap#map} whenever the delegate is non-empty. The + * {@code refreshIfEmpty}, {@code removeIfEmpty}, and {@code addToMap} + * methods ensure that the {@code WrappedCollection} and map remain + * consistent. + * + *

+ * A subcollection, such as a sublist, contains some of the values for a + * given key. Its ancestor field points to the full wrapped collection with + * all values for the key. The subcollection {@code refreshIfEmpty}, {@code + * removeIfEmpty}, and {@code addToMap} methods call the corresponding + * methods of the full wrapped collection. + */ + private class WrappedCollection extends AbstractCollection + { + final K key; + Collection delegate; + final WrappedCollection ancestor; + final Collection ancestorDelegate; + + WrappedCollection(K key, Collection delegate, WrappedCollection ancestor) + { + this.key = key; + this.delegate = delegate; + this.ancestor = ancestor; + this.ancestorDelegate = (ancestor == null) ? null : ancestor.getDelegate(); + } + + /** + * If the delegate collection is empty, but the multimap has values for + * the key, replace the delegate with the new collection for the key. + * + *

+ * For a subcollection, refresh its ancestor and validate that the + * ancestor delegate hasn't changed. + */ + void refreshIfEmpty() + { + if (ancestor != null) + { + ancestor.refreshIfEmpty(); + if (ancestor.getDelegate() != ancestorDelegate) + { + throw new ConcurrentModificationException(); + } + } + else if (delegate.isEmpty()) + { + Collection newDelegate = map.get(key); + if (newDelegate != null) + { + delegate = newDelegate; + } + } + } + + /** + * If collection is empty, remove it from {@code map}. For subcollections, + * check whether the ancestor collection is empty. + */ + void removeIfEmpty() + { + if (ancestor != null) + { + ancestor.removeIfEmpty(); + } + else if (delegate.isEmpty()) + { + map.remove(key); + } + } + + K getKey() + { + return key; + } + + /** + * Add the delegate to the map. Other {@code WrappedCollection} methods + * should call this method after adding elements to a previously empty + * collection. + * + *

+ * Subcollection add the ancestor's delegate instead. + */ + void addToMap() + { + if (ancestor != null) + { + ancestor.addToMap(); + } + else + { + map.put(key, delegate); + } + } + + @Override + public int size() + { + refreshIfEmpty(); + return delegate.size(); + } + + @Override + public boolean equals(Object object) + { + if (object == this) + { + return true; + } + refreshIfEmpty(); + return delegate.equals(object); + } + + @Override + public int hashCode() + { + refreshIfEmpty(); + return delegate.hashCode(); + } + + @Override + public String toString() + { + refreshIfEmpty(); + return delegate.toString(); + } + + Collection getDelegate() + { + return delegate; + } + + @Override + public Iterator iterator() + { + refreshIfEmpty(); + return new WrappedIterator(); + } + + /** Collection iterator for {@code WrappedCollection}. */ + class WrappedIterator implements Iterator + { + final Iterator delegateIterator; + final Collection originalDelegate = delegate; + + WrappedIterator() + { + delegateIterator = iteratorOrListIterator(delegate); + } + + WrappedIterator(Iterator delegateIterator) + { + this.delegateIterator = delegateIterator; + } + + /** + * If the delegate changed since the iterator was created, the iterator + * is no longer valid. + */ + void validateIterator() + { + refreshIfEmpty(); + if (delegate != originalDelegate) + { + throw new ConcurrentModificationException(); + } + } + + public boolean hasNext() + { + validateIterator(); + return delegateIterator.hasNext(); + } + + public V next() + { + validateIterator(); + return delegateIterator.next(); + } + + public void remove() + { + delegateIterator.remove(); + totalSize--; + removeIfEmpty(); + } + + Iterator getDelegateIterator() + { + validateIterator(); + return delegateIterator; + } + } + + @Override + public boolean add(V value) + { + refreshIfEmpty(); + boolean wasEmpty = delegate.isEmpty(); + boolean changed = delegate.add(value); + if (changed) + { + totalSize++; + if (wasEmpty) + { + addToMap(); + } + } + return changed; + } + + WrappedCollection getAncestor() + { + return ancestor; + } + + // The following methods are provided for better performance. + + @Override + public boolean addAll(Collection collection) + { + if (collection.isEmpty()) + { + return false; + } + int oldSize = size(); // calls refreshIfEmpty + boolean changed = delegate.addAll(collection); + if (changed) + { + int newSize = delegate.size(); + totalSize += (newSize - oldSize); + if (oldSize == 0) + { + addToMap(); + } + } + return changed; + } + + @Override + public boolean contains(Object o) + { + refreshIfEmpty(); + return delegate.contains(o); + } + + @Override + public boolean containsAll(Collection c) + { + refreshIfEmpty(); + return delegate.containsAll(c); + } + + @Override + public void clear() + { + int oldSize = size(); // calls refreshIfEmpty + if (oldSize == 0) + { + return; + } + delegate.clear(); + totalSize -= oldSize; + removeIfEmpty(); // maybe shouldn't be removed if this is a sublist + } + + @Override + public boolean remove(Object o) + { + refreshIfEmpty(); + boolean changed = delegate.remove(o); + if (changed) + { + totalSize--; + removeIfEmpty(); + } + return changed; + } + + @Override + public boolean removeAll(Collection c) + { + if (c.isEmpty()) + { + return false; + } + int oldSize = size(); // calls refreshIfEmpty + boolean changed = delegate.removeAll(c); + if (changed) + { + int newSize = delegate.size(); + totalSize += (newSize - oldSize); + removeIfEmpty(); + } + return changed; + } + + @Override + public boolean retainAll(Collection c) + { + checkNotNull(c); + int oldSize = size(); // calls refreshIfEmpty + boolean changed = delegate.retainAll(c); + if (changed) + { + int newSize = delegate.size(); + totalSize += (newSize - oldSize); + removeIfEmpty(); + } + return changed; + } + } + + private Iterator iteratorOrListIterator(Collection collection) + { + return (collection instanceof List) ? ((List) collection).listIterator() : collection.iterator(); + } + + /** Set decorator that stays in sync with the multimap values for a key. */ + private class WrappedSet extends WrappedCollection implements Set + { + WrappedSet(K key, Set delegate) + { + super(key, delegate, null); + } + } + + /** + * SortedSet decorator that stays in sync with the multimap values for a key. + */ + private class WrappedSortedSet extends WrappedCollection implements SortedSet + { + WrappedSortedSet(K key, SortedSet delegate, WrappedCollection ancestor) + { + super(key, delegate, ancestor); + } + + SortedSet getSortedSetDelegate() + { + return (SortedSet) getDelegate(); + } + + public Comparator comparator() + { + return getSortedSetDelegate().comparator(); + } + + public V first() + { + refreshIfEmpty(); + return getSortedSetDelegate().first(); + } + + public V last() + { + refreshIfEmpty(); + return getSortedSetDelegate().last(); + } + + public SortedSet headSet(V toElement) + { + refreshIfEmpty(); + return new WrappedSortedSet(getKey(), getSortedSetDelegate().headSet(toElement), (getAncestor() == null) ? this : getAncestor()); + } + + public SortedSet subSet(V fromElement, V toElement) + { + refreshIfEmpty(); + return new WrappedSortedSet(getKey(), getSortedSetDelegate().subSet(fromElement, toElement), (getAncestor() == null) ? this : getAncestor()); + } + + public SortedSet tailSet(V fromElement) + { + refreshIfEmpty(); + return new WrappedSortedSet(getKey(), getSortedSetDelegate().tailSet(fromElement), (getAncestor() == null) ? this : getAncestor()); + } + } + + /** List decorator that stays in sync with the multimap values for a key. */ + private class WrappedList extends WrappedCollection implements List + { + WrappedList(K key, List delegate, WrappedCollection ancestor) + { + super(key, delegate, ancestor); + } + + List getListDelegate() + { + return (List) getDelegate(); + } + + public boolean addAll(int index, Collection c) + { + if (c.isEmpty()) + { + return false; + } + int oldSize = size(); // calls refreshIfEmpty + boolean changed = getListDelegate().addAll(index, c); + if (changed) + { + int newSize = getDelegate().size(); + totalSize += (newSize - oldSize); + if (oldSize == 0) + { + addToMap(); + } + } + return changed; + } + + public V get(int index) + { + refreshIfEmpty(); + return getListDelegate().get(index); + } + + public V set(int index, V element) + { + refreshIfEmpty(); + return getListDelegate().set(index, element); + } + + public void add(int index, V element) + { + refreshIfEmpty(); + boolean wasEmpty = getDelegate().isEmpty(); + getListDelegate().add(index, element); + totalSize++; + if (wasEmpty) + { + addToMap(); + } + } + + public V remove(int index) + { + refreshIfEmpty(); + V value = getListDelegate().remove(index); + totalSize--; + removeIfEmpty(); + return value; + } + + public int indexOf(Object o) + { + refreshIfEmpty(); + return getListDelegate().indexOf(o); + } + + public int lastIndexOf(Object o) + { + refreshIfEmpty(); + return getListDelegate().lastIndexOf(o); + } + + public ListIterator listIterator() + { + refreshIfEmpty(); + return new WrappedListIterator(); + } + + public ListIterator listIterator(int index) + { + refreshIfEmpty(); + return new WrappedListIterator(index); + } + + public List subList(int fromIndex, int toIndex) + { + refreshIfEmpty(); + return wrapList(getKey(), Platform.subList(getListDelegate(), fromIndex, toIndex), (getAncestor() == null) ? this : getAncestor()); + } + + /** ListIterator decorator. */ + private class WrappedListIterator extends WrappedIterator implements ListIterator + { + WrappedListIterator() + { + } + + public WrappedListIterator(int index) + { + super(getListDelegate().listIterator(index)); + } + + private ListIterator getDelegateListIterator() + { + return (ListIterator) getDelegateIterator(); + } + + public boolean hasPrevious() + { + return getDelegateListIterator().hasPrevious(); + } + + public V previous() + { + return getDelegateListIterator().previous(); + } + + public int nextIndex() + { + return getDelegateListIterator().nextIndex(); + } + + public int previousIndex() + { + return getDelegateListIterator().previousIndex(); + } + + public void set(V value) + { + getDelegateListIterator().set(value); + } + + public void add(V value) + { + boolean wasEmpty = isEmpty(); + getDelegateListIterator().add(value); + totalSize++; + if (wasEmpty) + { + addToMap(); + } + } + } + } + + /** + * List decorator that stays in sync with the multimap values for a key and + * supports rapid random access. + */ + private class RandomAccessWrappedList extends WrappedList implements RandomAccess + { + RandomAccessWrappedList(K key, List delegate, WrappedCollection ancestor) + { + super(key, delegate, ancestor); + } + } + + private transient Set keySet; + + public Set keySet() + { + Set result = keySet; + return (result == null) ? keySet = createKeySet() : result; + } + + private Set createKeySet() + { + return (map instanceof SortedMap) ? new SortedKeySet((SortedMap>) map) : new KeySet(map); + } + + private class KeySet extends AbstractSet + { + + /** + * This is usually the same as map, except when someone requests a + * subcollection of a {@link SortedKeySet}. + */ + final Map> subMap; + + KeySet(final Map> subMap) + { + this.subMap = subMap; + } + + @Override + public int size() + { + return subMap.size(); + } + + @Override + public Iterator iterator() + { + return new Iterator() + { + final Iterator>> entryIterator = subMap.entrySet().iterator(); + Map.Entry> entry; + + public boolean hasNext() + { + return entryIterator.hasNext(); + } + + public K next() + { + entry = entryIterator.next(); + return entry.getKey(); + } + + public void remove() + { + checkState(entry != null); + Collection collection = entry.getValue(); + entryIterator.remove(); + totalSize -= collection.size(); + collection.clear(); + } + }; + } + + // The following methods are included for better performance. + + @Override + public boolean contains(Object key) + { + return subMap.containsKey(key); + } + + @Override + public boolean remove(Object key) + { + int count = 0; + Collection collection = subMap.remove(key); + if (collection != null) + { + count = collection.size(); + collection.clear(); + totalSize -= count; + } + return count > 0; + } + + @Override + public boolean containsAll(Collection c) + { + return subMap.keySet().containsAll(c); + } + + @Override + public boolean equals(Object object) + { + return this == object || this.subMap.keySet().equals(object); + } + + @Override + public int hashCode() + { + return subMap.keySet().hashCode(); + } + } + + private class SortedKeySet extends KeySet implements SortedSet + { + + SortedKeySet(SortedMap> subMap) + { + super(subMap); + } + + SortedMap> sortedMap() + { + return (SortedMap>) subMap; + } + + public Comparator comparator() + { + return sortedMap().comparator(); + } + + public K first() + { + return sortedMap().firstKey(); + } + + public SortedSet headSet(K toElement) + { + return new SortedKeySet(sortedMap().headMap(toElement)); + } + + public K last() + { + return sortedMap().lastKey(); + } + + public SortedSet subSet(K fromElement, K toElement) + { + return new SortedKeySet(sortedMap().subMap(fromElement, toElement)); + } + + public SortedSet tailSet(K fromElement) + { + return new SortedKeySet(sortedMap().tailMap(fromElement)); + } + } + + private transient Multiset multiset; + + public Multiset keys() + { + Multiset result = multiset; + return (result == null) ? multiset = new MultisetView() : result; + } + + /** Multiset view that stays in sync with the multimap keys. */ + private class MultisetView extends AbstractMultiset + { + + @Override + public int remove(Object key, int occurrences) + { + if (occurrences == 0) + { + return count(key); + } + checkArgument(occurrences > 0); + + Collection collection; + try + { + collection = map.get(key); + } + catch (NullPointerException e) + { + return 0; + } + catch (ClassCastException e) + { + return 0; + } + + if (collection == null) + { + return 0; + } + int count = collection.size(); + + if (occurrences >= count) + { + return removeValuesForKey(key); + } + + Iterator iterator = collection.iterator(); + for (int i = 0; i < occurrences; i++) + { + iterator.next(); + iterator.remove(); + } + totalSize -= occurrences; + return count; + } + + @Override + public Set elementSet() + { + return AbstractMultimap.this.keySet(); + } + + transient Set> entrySet; + + @Override + public Set> entrySet() + { + Set> result = entrySet; + return (result == null) ? entrySet = new EntrySet() : result; + } + + private class EntrySet extends AbstractSet> + { + @Override + public Iterator> iterator() + { + return new MultisetEntryIterator(); + } + + @Override + public int size() + { + return map.size(); + } + + // The following methods are included for better performance. + + @Override + public boolean contains(Object o) + { + if (!(o instanceof Multiset.Entry)) + { + return false; + } + Multiset.Entry entry = (Multiset.Entry) o; + Collection collection = map.get(entry.getElement()); + return (collection != null) && (collection.size() == entry.getCount()); + } + + @Override + public void clear() + { + AbstractMultimap.this.clear(); + } + + @Override + public boolean remove(Object o) + { + return contains(o) && (removeValuesForKey(((Multiset.Entry) o).getElement()) > 0); + } + } + + @Override + public Iterator iterator() + { + return new MultisetKeyIterator(); + } + + // The following methods are included for better performance. + + @Override + public int count(Object key) + { + try + { + Collection collection = map.get(key); + return (collection == null) ? 0 : collection.size(); + } + catch (NullPointerException e) + { + return 0; + } + catch (ClassCastException e) + { + return 0; + } + } + + @Override + public int size() + { + return totalSize; + } + + @Override + public void clear() + { + AbstractMultimap.this.clear(); + } + } + + /** + * Removes all values for the provided key. Unlike {@link #removeAll}, it + * returns the number of removed mappings. + */ + private int removeValuesForKey(Object key) + { + Collection collection; + try + { + collection = map.remove(key); + } + catch (NullPointerException e) + { + return 0; + } + catch (ClassCastException e) + { + return 0; + } + + int count = 0; + if (collection != null) + { + count = collection.size(); + collection.clear(); + totalSize -= count; + } + return count; + } + + /** Iterator across each key, repeating once per value. */ + private class MultisetEntryIterator implements Iterator> + { + final Iterator>> asMapIterator = asMap().entrySet().iterator(); + + public boolean hasNext() + { + return asMapIterator.hasNext(); + } + + public Multiset.Entry next() + { + return new MultisetEntry(asMapIterator.next()); + } + + public void remove() + { + asMapIterator.remove(); + } + } + + private class MultisetEntry extends Multisets.AbstractEntry + { + final Map.Entry> entry; + + public MultisetEntry(Map.Entry> entry) + { + this.entry = entry; + } + + public K getElement() + { + return entry.getKey(); + } + + public int getCount() + { + return entry.getValue().size(); + } + } + + /** Iterator across each key, repeating once per value. */ + private class MultisetKeyIterator implements Iterator + { + final Iterator> entryIterator = entries().iterator(); + + public boolean hasNext() + { + return entryIterator.hasNext(); + } + + public K next() + { + return entryIterator.next().getKey(); + } + + public void remove() + { + entryIterator.remove(); + } + } + + private transient Collection valuesCollection; + + /** + * {@inheritDoc} + * + *

+ * The iterator generated by the returned collection traverses the values for + * one key, followed by the values of a second key, and so on. + */ + public Collection values() + { + Collection result = valuesCollection; + return (result == null) ? valuesCollection = new Values() : result; + } + + private class Values extends AbstractCollection + { + @Override + public Iterator iterator() + { + return new ValueIterator(); + } + + @Override + public int size() + { + return totalSize; + } + + // The following methods are included to improve performance. + + @Override + public void clear() + { + AbstractMultimap.this.clear(); + } + + @Override + public boolean contains(Object value) + { + return containsValue(value); + } + } + + /** Iterator across all values. */ + private class ValueIterator implements Iterator + { + final Iterator> entryIterator = createEntryIterator(); + + public boolean hasNext() + { + return entryIterator.hasNext(); + } + + public V next() + { + return entryIterator.next().getValue(); + } + + public void remove() + { + entryIterator.remove(); + } + } + + private transient Collection> entries; + + // TODO: should we copy this javadoc to each concrete class, so that classes + // like LinkedHashMultimap that need to say something different are still + // able to {@inheritDoc} all the way from Multimap? + + /** + * {@inheritDoc} + * + *

+ * The iterator generated by the returned collection traverses the values for + * one key, followed by the values of a second key, and so on. + * + *

+ * Each entry is an immutable snapshot of a key-value mapping in the + * multimap, taken at the time the entry is returned by a method call to the + * collection or its iterator. + */ + public Collection> entries() + { + Collection> result = entries; + return (entries == null) ? entries = createEntries() : result; + } + + private Collection> createEntries() + { + // TODO: can we refactor so we're not doing "this instanceof"? + return (this instanceof SetMultimap) ? new EntrySet() : new Entries(); + } + + /** Entries for multimap. */ + private class Entries extends AbstractCollection> + { + @Override + public Iterator> iterator() + { + return createEntryIterator(); + } + + @Override + public int size() + { + return totalSize; + } + + // The following methods are included to improve performance. + + @Override + public boolean contains(Object o) + { + if (!(o instanceof Map.Entry)) + { + return false; + } + Map.Entry entry = (Map.Entry) o; + return containsEntry(entry.getKey(), entry.getValue()); + } + + @Override + public void clear() + { + AbstractMultimap.this.clear(); + } + + @Override + public boolean remove(Object o) + { + if (!(o instanceof Map.Entry)) + { + return false; + } + Map.Entry entry = (Map.Entry) o; + return AbstractMultimap.this.remove(entry.getKey(), entry.getValue()); + } + } + + /** + * Returns an iterator across all key-value map entries, used by {@code + * entries().iterator()} and {@code values().iterator()}. The default + * behavior, which traverses the values for one key, the values for a second + * key, and so on, suffices for most {@code AbstractMultimap} + * implementations. + * + * @return an iterator across map entries + */ + Iterator> createEntryIterator() + { + return new EntryIterator(); + } + + /** Iterator across all key-value pairs. */ + private class EntryIterator implements Iterator> + { + final Iterator>> keyIterator; + K key; + Collection collection; + Iterator valueIterator; + + EntryIterator() + { + keyIterator = map.entrySet().iterator(); + if (keyIterator.hasNext()) + { + findValueIteratorAndKey(); + } + else + { + valueIterator = Iterators.emptyModifiableIterator(); + } + } + + void findValueIteratorAndKey() + { + Map.Entry> entry = keyIterator.next(); + key = entry.getKey(); + collection = entry.getValue(); + valueIterator = collection.iterator(); + } + + public boolean hasNext() + { + return keyIterator.hasNext() || valueIterator.hasNext(); + } + + public Map.Entry next() + { + if (!valueIterator.hasNext()) + { + findValueIteratorAndKey(); + } + return Maps.immutableEntry(key, valueIterator.next()); + } + + public void remove() + { + valueIterator.remove(); + if (collection.isEmpty()) + { + keyIterator.remove(); + } + totalSize--; + } + } + + /** Entry set for a {@link SetMultimap}. */ + private class EntrySet extends Entries implements Set> + { + @Override + public boolean equals(Object object) + { + return Collections2.setEquals(this, object); + } + + @Override + public int hashCode() + { + return Sets.hashCodeImpl(this); + } + } + + private transient Map> asMap; + + public Map> asMap() + { + Map> result = asMap; + return (result == null) ? asMap = createAsMap() : result; + } + + private Map> createAsMap() + { + return (map instanceof SortedMap) ? new SortedAsMap((SortedMap>) map) : new AsMap(map); + } + + private class AsMap extends AbstractMap> + { + /** + * Usually the same as map, but smaller for the headMap(), tailMap(), or + * subMap() of a SortedAsMap. + */ + final transient Map> submap; + + AsMap(Map> submap) + { + this.submap = submap; + } + + transient Set>> entrySet; + + @Override + public Set>> entrySet() + { + Set>> result = entrySet; + return (entrySet == null) ? entrySet = new AsMapEntries() : result; + } + + // The following methods are included for performance. + + @Override + public boolean containsKey(Object key) + { + return submap.containsKey(key); + } + + @Override + public Collection get(Object key) + { + Collection collection = submap.get(key); + if (collection == null) + { + return null; + } + @SuppressWarnings("unchecked") + K k = (K) key; + return wrapCollection(k, collection); + } + + @Override + public Set keySet() + { + return AbstractMultimap.this.keySet(); + } + + @Override + public Collection remove(Object key) + { + Collection collection = submap.remove(key); + if (collection == null) + { + return null; + } + + Collection output = createCollection(); + output.addAll(collection); + totalSize -= collection.size(); + collection.clear(); + return output; + } + + @Override + public boolean equals(Object object) + { + return this == object || submap.equals(object); + } + + @Override + public int hashCode() + { + return submap.hashCode(); + } + + @Override + public String toString() + { + return submap.toString(); + } + + class AsMapEntries extends AbstractSet>> + { + @Override + public Iterator>> iterator() + { + return new AsMapIterator(); + } + + @Override + public int size() + { + return submap.size(); + } + + // The following methods are included for performance. + + @Override + public boolean contains(Object o) + { + return submap.entrySet().contains(o); + } + + @Override + public boolean remove(Object o) + { + if (!contains(o)) + { + return false; + } + Map.Entry entry = (Map.Entry) o; + removeValuesForKey(entry.getKey()); + return true; + } + } + + /** Iterator across all keys and value collections. */ + class AsMapIterator implements Iterator>> + { + final Iterator>> delegateIterator = submap.entrySet().iterator(); + Collection collection; + + public boolean hasNext() + { + return delegateIterator.hasNext(); + } + + public Map.Entry> next() + { + Map.Entry> entry = delegateIterator.next(); + K key = entry.getKey(); + collection = entry.getValue(); + return Maps.immutableEntry(key, wrapCollection(key, collection)); + } + + public void remove() + { + delegateIterator.remove(); + totalSize -= collection.size(); + collection.clear(); + } + } + } + + private class SortedAsMap extends AsMap implements SortedMap> + { + SortedAsMap(SortedMap> submap) + { + super(submap); + } + + SortedMap> sortedMap() + { + return (SortedMap>) submap; + } + + public Comparator comparator() + { + return sortedMap().comparator(); + } + + public K firstKey() + { + return sortedMap().firstKey(); + } + + public K lastKey() + { + return sortedMap().lastKey(); + } + + public SortedMap> headMap(K toKey) + { + return new SortedAsMap(sortedMap().headMap(toKey)); + } + + public SortedMap> subMap(K fromKey, K toKey) + { + return new SortedAsMap(sortedMap().subMap(fromKey, toKey)); + } + + public SortedMap> tailMap(K fromKey) + { + return new SortedAsMap(sortedMap().tailMap(fromKey)); + } + + SortedSet sortedKeySet; + + // returns a SortedSet, even though returning a Set would be sufficient to + // satisfy the SortedMap.keySet() interface + @Override + public SortedSet keySet() + { + SortedSet result = sortedKeySet; + return (result == null) ? sortedKeySet = new SortedKeySet(sortedMap()) : result; + } + } + + // Comparison and hashing + + @Override + public boolean equals(Object object) + { + if (object == this) + { + return true; + } + if (object instanceof Multimap) + { + Multimap that = (Multimap) object; + return this.map.equals(that.asMap()); + } + return false; + } + + /** + * Returns the hash code for this multimap. + * + *

+ * The hash code of a multimap is defined as the hash code of the map view, + * as returned by {@link Multimap#asMap}. + * + * @see Map#hashCode + */ + @Override + public int hashCode() + { + return map.hashCode(); + } + + /** + * Returns a string representation of the multimap, generated by calling + * {@code toString} on the map returned by {@link Multimap#asMap}. + * + * @return a string representation of the multimap + */ + @Override + public String toString() + { + return map.toString(); + } + + private static final long serialVersionUID = 2447537837011683357L; +} diff --git a/impl/src/main/java/org/jboss/weld/extensions/util/collections/AbstractMultiset.java b/impl/src/main/java/org/jboss/weld/extensions/util/collections/AbstractMultiset.java new file mode 100644 index 0000000..f2d884c --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/extensions/util/collections/AbstractMultiset.java @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jboss.weld.extensions.util.collections; + +import static org.jboss.weld.extensions.util.collections.Multisets.setCountImpl; +import static org.jboss.weld.extensions.util.collections.Preconditions.checkNotNull; +import static org.jboss.weld.extensions.util.collections.Preconditions.checkState; + +import java.util.AbstractCollection; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * This class provides a skeletal implementation of the {@link Multiset} + * interface. A new multiset implementation can be created easily by extending + * this class and implementing the {@link Multiset#entrySet()} method, plus + * optionally overriding {@link #add(Object, int)} and + * {@link #remove(Object, int)} to enable modifications to the multiset. + * + *

The {@link #contains}, {@link #containsAll}, {@link #count}, and + * {@link #size} implementations all iterate across the set returned by + * {@link Multiset#entrySet()}, as do many methods acting on the set returned by + * {@link #elementSet()}. Override those methods for better performance. + * + * @author Kevin Bourrillion + */ +abstract class AbstractMultiset extends AbstractCollection + implements Multiset { + public abstract Set> entrySet(); + + // Query Operations + + @Override public int size() { + long sum = 0L; + for (Entry entry : entrySet()) { + sum += entry.getCount(); + } + return (int) Math.min(sum, Integer.MAX_VALUE); + } + + @Override public boolean isEmpty() { + return entrySet().isEmpty(); + } + + @Override public boolean contains( Object element) { + return elementSet().contains(element); + } + + @Override public Iterator iterator() { + return new MultisetIterator(); + } + + private class MultisetIterator implements Iterator { + private final Iterator> entryIterator; + private Entry currentEntry; + /** Count of subsequent elements equal to current element */ + private int laterCount; + /** Count of all elements equal to current element */ + private int totalCount; + private boolean canRemove; + + MultisetIterator() { + this.entryIterator = entrySet().iterator(); + } + + public boolean hasNext() { + return laterCount > 0 || entryIterator.hasNext(); + } + + public E next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + if (laterCount == 0) { + currentEntry = entryIterator.next(); + totalCount = laterCount = currentEntry.getCount(); + } + laterCount--; + canRemove = true; + return currentEntry.getElement(); + } + + public void remove() { + checkState(canRemove, + "no calls to next() since the last call to remove()"); + if (totalCount == 1) { + entryIterator.remove(); + } else { + AbstractMultiset.this.remove(currentEntry.getElement()); + } + totalCount--; + canRemove = false; + } + } + + public int count(Object element) { + for (Entry entry : entrySet()) { + if (Objects.equal(entry.getElement(), element)) { + return entry.getCount(); + } + } + return 0; + } + + // Modification Operations + + @Override public boolean add( E element) { + add(element, 1); + return true; + } + + public int add(E element, int occurrences) { + throw new UnsupportedOperationException(); + } + + @Override public boolean remove(Object element) { + return remove(element, 1) > 0; + } + + public int remove(Object element, int occurrences) { + throw new UnsupportedOperationException(); + } + + public int setCount(E element, int count) { + return setCountImpl(this, element, count); + } + + public boolean setCount(E element, int oldCount, int newCount) { + return setCountImpl(this, element, oldCount, newCount); + } + + // Bulk Operations + + @Override public boolean containsAll(Collection elements) { + return elementSet().containsAll(elements); + } + + @Override public boolean addAll(Collection elementsToAdd) { + if (elementsToAdd.isEmpty()) { + return false; + } + if (elementsToAdd instanceof Multiset) { + Multiset that = (Multiset) elementsToAdd; + for (Entry entry : that.entrySet()) { + add(entry.getElement(), entry.getCount()); + } + } else { + super.addAll(elementsToAdd); + } + return true; + } + + @Override public boolean removeAll(Collection elementsToRemove) { + Collection collection = (elementsToRemove instanceof Multiset) + ? ((Multiset) elementsToRemove).elementSet() : elementsToRemove; + + return elementSet().removeAll(collection); + // TODO: implement retainAll similarly? + } + + @Override public boolean retainAll(Collection elementsToRetain) { + checkNotNull(elementsToRetain); + Iterator> entries = entrySet().iterator(); + boolean modified = false; + while (entries.hasNext()) { + Entry entry = entries.next(); + if (!elementsToRetain.contains(entry.getElement())) { + entries.remove(); + modified = true; + } + } + return modified; + } + + @Override public void clear() { + entrySet().clear(); + } + + // Views + + private transient Set elementSet; + + public Set elementSet() { + Set result = elementSet; + if (result == null) { + elementSet = result = createElementSet(); + } + return result; + } + + /** + * Creates a new instance of this multiset's element set, which will be + * returned by {@link #elementSet()}. + */ + Set createElementSet() { + return new ElementSet(); + } + + private class ElementSet extends AbstractSet { + @Override public Iterator iterator() { + final Iterator> entryIterator = entrySet().iterator(); + return new Iterator() { + public boolean hasNext() { + return entryIterator.hasNext(); + } + public E next() { + return entryIterator.next().getElement(); + } + public void remove() { + entryIterator.remove(); + } + }; + } + @Override public int size() { + return entrySet().size(); + } + } + + // Object methods + + /** + * {@inheritDoc} + * + *

This implementation returns {@code true} if {@code other} is a multiset + * of the same size and if, for each element, the two multisets have the same + * count. + */ + @Override public boolean equals( Object object) { + if (object == this) { + return true; + } + if (object instanceof Multiset) { + Multiset that = (Multiset) object; + /* + * We can't simply check whether the entry sets are equal, since that + * approach fails when a TreeMultiset has a comparator that returns 0 + * when passed unequal elements. + */ + + if (this.size() != that.size()) { + return false; + } + for (Entry entry : that.entrySet()) { + if (count(entry.getElement()) != entry.getCount()) { + return false; + } + } + return true; + } + return false; + } + + /** + * {@inheritDoc} + * + *

This implementation returns the hash code of {@link + * Multiset#entrySet()}. + */ + @Override public int hashCode() { + return entrySet().hashCode(); + } + + /** + * {@inheritDoc} + * + *

This implementation returns the result of invoking {@code toString} on + * {@link Multiset#entrySet()}. + */ + @Override public String toString() { + return entrySet().toString(); + } +} diff --git a/impl/src/main/java/org/jboss/weld/extensions/util/collections/AbstractSetMultimap.java b/impl/src/main/java/org/jboss/weld/extensions/util/collections/AbstractSetMultimap.java new file mode 100644 index 0000000..2ad61f4 --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/extensions/util/collections/AbstractSetMultimap.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jboss.weld.extensions.util.collections; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * Basic implementation of the {@link SetMultimap} interface. It's a wrapper + * around {@link AbstractMultimap} that converts the returned collections into + * {@code Sets}. The {@link #createCollection} method must return a {@code Set}. + * + * @author Jared Levy + */ +abstract class AbstractSetMultimap + extends AbstractMultimap implements SetMultimap { + /** + * Creates a new multimap that uses the provided map. + * + * @param map place to store the mapping from each key to its corresponding + * values + */ + protected AbstractSetMultimap(Map> map) { + super(map); + } + + @Override abstract Set createCollection(); + + @Override public Set get(K key) { + return (Set) super.get(key); + } + + @Override public Set> entries() { + return (Set>) super.entries(); + } + + @Override public Set removeAll(Object key) { + return (Set) super.removeAll(key); + } + + /** + * {@inheritDoc} + * + *

Any duplicates in {@code values} will be stored in the multimap once. + */ + @Override public Set replaceValues( + K key, Iterable values) { + return (Set) super.replaceValues(key, values); + } + + /** + * Stores a key-value pair in the multimap. + * + * @param key key to store in the multimap + * @param value value to store in the multimap + * @return {@code true} if the method increased the size of the multimap, or + * {@code false} if the multimap already contained the key-value pair + */ + @Override public boolean put(K key, V value) { + return super.put(key, value); + } + + /** + * Compares the specified object to this multimap for equality. + * + *

Two {@code SetMultimap} instances are equal if, for each key, they + * contain the same values. Equality does not depend on the ordering of keys + * or values. + */ + @Override public boolean equals(Object object) { + return super.equals(object); + } + + private static final long serialVersionUID = 7431625294878419160L; +} diff --git a/impl/src/main/java/org/jboss/weld/extensions/util/collections/Collections2.java b/impl/src/main/java/org/jboss/weld/extensions/util/collections/Collections2.java new file mode 100644 index 0000000..ecf5bf3 --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/extensions/util/collections/Collections2.java @@ -0,0 +1,22 @@ +package org.jboss.weld.extensions.util.collections; + +import java.util.Set; + +public class Collections2 +{ + + private Collections2() {} + + static boolean setEquals(Set thisSet, Object object) { + if (object == thisSet) { + return true; + } + if (object instanceof Set) { + Set thatSet = (Set) object; + return thisSet.size() == thatSet.size() + && thisSet.containsAll(thatSet); + } + return false; + } + +} diff --git a/impl/src/main/java/org/jboss/weld/extensions/util/collections/ImmutableEntry.java b/impl/src/main/java/org/jboss/weld/extensions/util/collections/ImmutableEntry.java new file mode 100644 index 0000000..d482e70 --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/extensions/util/collections/ImmutableEntry.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jboss.weld.extensions.util.collections; + +import java.io.Serializable; + +/** + * @see com.google.common.collect.Maps#immutableEntry(Object, Object) + */ +class ImmutableEntry extends AbstractMapEntry + implements Serializable { + private final K key; + private final V value; + + ImmutableEntry(K key, V value) { + this.key = key; + this.value = value; + } + + @Override public K getKey() { + return key; + } + + @Override public V getValue() { + return value; + } + private static final long serialVersionUID = 0; +} diff --git a/impl/src/main/java/org/jboss/weld/extensions/util/collections/Iterators.java b/impl/src/main/java/org/jboss/weld/extensions/util/collections/Iterators.java new file mode 100644 index 0000000..4dc86ef --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/extensions/util/collections/Iterators.java @@ -0,0 +1,40 @@ +package org.jboss.weld.extensions.util.collections; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +public class Iterators +{ + + private static final Iterator EMPTY_MODIFIABLE_ITERATOR = new Iterator() + { + /* @Override */public boolean hasNext() + { + return false; + } + + /* @Override */public Object next() + { + throw new NoSuchElementException(); + } + + /* @Override */public void remove() + { + throw new IllegalStateException(); + } + }; + + /** + * Returns the empty {@code Iterator} that throws + * {@link IllegalStateException} instead of + * {@link UnsupportedOperationException} on a call to + * {@link Iterator#remove()}. + */ + // Casting to any type is safe since there are no actual elements. + @SuppressWarnings("unchecked") + static Iterator emptyModifiableIterator() + { + return (Iterator) EMPTY_MODIFIABLE_ITERATOR; + } + +} diff --git a/impl/src/main/java/org/jboss/weld/extensions/util/collections/Maps.java b/impl/src/main/java/org/jboss/weld/extensions/util/collections/Maps.java new file mode 100644 index 0000000..555a3cc --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/extensions/util/collections/Maps.java @@ -0,0 +1,63 @@ +package org.jboss.weld.extensions.util.collections; + +import static org.jboss.weld.extensions.util.collections.Preconditions.checkArgument; + +import java.util.HashMap; +import java.util.Map.Entry; + +public class Maps +{ + + private Maps() + { + } + + /** + * Returns an immutable map entry with the specified key and value. The + * {@link Entry#setValue} operation throws an + * {@link UnsupportedOperationException}. + * + *

+ * The returned entry is serializable. + * + * @param key the key to be associated with the returned entry + * @param value the value to be associated with the returned entry + */ + public static Entry immutableEntry(final K key, final V value) + { + return new ImmutableEntry(key, value); + } + + /** + * Returns an appropriate value for the "capacity" (in reality, "minimum + * table size") parameter of a {@link HashMap} constructor, such that the + * resulting table will be between 25% and 50% full when it contains + * {@code expectedSize} entries. + * + * @throws IllegalArgumentException if {@code expectedSize} is negative + */ + static int capacity(int expectedSize) { + checkArgument(expectedSize >= 0); + return Math.max(expectedSize * 2, 16); + } + + /** + * Creates a {@code HashMap} instance with enough capacity to hold the + * specified number of elements without rehashing. + * + * @param expectedSize the expected size + * @return a new, empty {@code HashMap} with enough + * capacity to hold {@code expectedSize} elements without rehashing + * @throws IllegalArgumentException if {@code expectedSize} is negative + */ + public static HashMap newHashMapWithExpectedSize( + int expectedSize) { + /* + * The HashMap is constructed with an initialCapacity that's greater than + * expectedSize. The larger value is necessary because HashMap resizes + * its internal array if the map size exceeds loadFactor * initialCapacity. + */ + return new HashMap(capacity(expectedSize)); + } + +} diff --git a/impl/src/main/java/org/jboss/weld/extensions/util/collections/Multimap.java b/impl/src/main/java/org/jboss/weld/extensions/util/collections/Multimap.java new file mode 100644 index 0000000..5a23175 --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/extensions/util/collections/Multimap.java @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jboss.weld.extensions.util.collections; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * A collection similar to a {@code Map}, but which may associate multiple + * values with a single key. If you call {@link #put} twice, with the same key + * but different values, the multimap contains mappings from the key to both + * values. + * + *

+ * The methods {@link #get}, {@link #keySet}, {@link #keys}, {@link #values}, + * {@link #entries}, and {@link #asMap} return collections that are views of the + * multimap. If the multimap is modifiable, updating it can change the contents + * of those collections, and updating the collections will change the multimap. + * In contrast, {@link #replaceValues} and {@link #removeAll} return collections + * that are independent of subsequent multimap changes. + * + *

+ * Depending on the implementation, a multimap may or may not allow duplicate + * key-value pairs. In other words, the multimap contents after adding the same + * key and value twice varies between implementations. In multimaps allowing + * duplicates, the multimap will contain two mappings, and {@code get} will + * return a collection that includes the value twice. In multimaps not + * supporting duplicates, the multimap will contain a single mapping from the + * key to the value, and {@code get} will return a collection that includes the + * value once. + * + *

+ * All methods that alter the multimap are optional, and the views returned by + * the multimap may or may not be modifiable. When modification isn't supported, + * those methods will throw an {@link UnsupportedOperationException}. + * + * @author Jared Levy + * @param the type of keys maintained by this multimap + * @param the type of mapped values + */ + +public interface Multimap +{ + // Query Operations + + /** Returns the number of key-value pairs in the multimap. */ + int size(); + + /** Returns {@code true} if the multimap contains no key-value pairs. */ + boolean isEmpty(); + + /** + * Returns {@code true} if the multimap contains any values for the specified + * key. + * + * @param key key to search for in multimap + */ + boolean containsKey(Object key); + + /** + * Returns {@code true} if the multimap contains the specified value for any + * key. + * + * @param value value to search for in multimap + */ + boolean containsValue(Object value); + + /** + * Returns {@code true} if the multimap contains the specified key-value + * pair. + * + * @param key key to search for in multimap + * @param value value to search for in multimap + */ + boolean containsEntry(Object key, Object value); + + // Modification Operations + + /** + * Stores a key-value pair in the multimap. + * + *

+ * Some multimap implementations allow duplicate key-value pairs, in which + * case {@code put} always adds a new key-value pair and increases the + * multimap size by 1. Other implementations prohibit duplicates, and storing + * a key-value pair that's already in the multimap has no effect. + * + * @param key key to store in the multimap + * @param value value to store in the multimap + * @return {@code true} if the method increased the size of the multimap, or + * {@code false} if the multimap already contained the key-value pair + * and doesn't allow duplicates + */ + boolean put(K key, V value); + + /** + * Removes a key-value pair from the multimap. + * + * @param key key of entry to remove from the multimap + * @param value value of entry to remove the multimap + * @return {@code true} if the multimap changed + */ + boolean remove(Object key, Object value); + + // Bulk Operations + + /** + * Stores a collection of values with the same key. + * + * @param key key to store in the multimap + * @param values values to store in the multimap + * @return {@code true} if the multimap changed + */ + boolean putAll(K key, Iterable values); + + /** + * Copies all of another multimap's key-value pairs into this multimap. The + * order in which the mappings are added is determined by {@code + * multimap.entries()}. + * + * @param multimap mappings to store in this multimap + * @return {@code true} if the multimap changed + */ + boolean putAll(Multimap multimap); + + /** + * Stores a collection of values with the same key, replacing any existing + * values for that key. + * + * @param key key to store in the multimap + * @param values values to store in the multimap + * @return the collection of replaced values, or an empty collection if no + * values were previously associated with the key. The collection + * may be modifiable, but updating it will have no effect on + * the multimap. + */ + Collection replaceValues(K key, Iterable values); + + /** + * Removes all values associated with a given key. + * + * @param key key of entries to remove from the multimap + * @return the collection of removed values, or an empty collection if no + * values were associated with the provided key. The collection + * may be modifiable, but updating it will have no effect on + * the multimap. + */ + Collection removeAll(Object key); + + /** + * Removes all key-value pairs from the multimap. + */ + void clear(); + + // Views + + /** + * Returns a collection view of all values associated with a key. If no + * mappings in the multimap have the provided key, an empty collection is + * returned. + * + *

+ * Changes to the returned collection will update the underlying multimap, + * and vice versa. + * + * @param key key to search for in multimap + * @return the collection of values that the key maps to + */ + Collection get(K key); + + /** + * Returns the set of all keys, each appearing once in the returned set. + * Changes to the returned set will update the underlying multimap, and vice + * versa. + * + * @return the collection of distinct keys + */ + Set keySet(); + + /** + * Returns a collection, which may contain duplicates, of all keys. The + * number of times of key appears in the returned multiset equals the number + * of mappings the key has in the multimap. Changes to the returned multiset + * will update the underlying multimap, and vice versa. + * + * @return a multiset with keys corresponding to the distinct keys of the + * multimap and frequencies corresponding to the number of values + * that each key maps to + */ + Multiset keys(); + + /** + * Returns a collection of all values in the multimap. Changes to the + * returned collection will update the underlying multimap, and vice versa. + * + * @return collection of values, which may include the same value multiple + * times if it occurs in multiple mappings + */ + Collection values(); + + /** + * Returns a collection of all key-value pairs. Changes to the returned + * collection will update the underlying multimap, and vice versa. The + * entries collection does not support the {@code add} or {@code addAll} + * operations. + * + * @return collection of map entries consisting of key-value pairs + */ + Collection> entries(); + + /** + * Returns a map view that associates each key with the corresponding values + * in the multimap. Changes to the returned map, such as element removal, + * will update the underlying multimap. The map does not support {@code + * setValue()} on its entries, {@code put}, or {@code putAll}. + * + *

+ * The collections returned by {@code asMap().get(Object)} have the same + * behavior as those returned by {@link #get}. + * + * @return a map view from a key to its collection of values + */ + Map> asMap(); + + // Comparison and hashing + + /** + * Compares the specified object with this multimap for equality. Two + * multimaps are equal when their map views, as returned by {@link #asMap}, + * are also equal. + * + *

+ * In general, two multimaps with identical key-value mappings may or may not + * be equal, depending on the implementation. For example, two + * {@link SetMultimap} instances with the same key-value mappings are equal, + * but equality of two ListMultimap instances depends on the ordering of the + * values for each key. + * + *

+ * A non-empty {@link SetMultimap} cannot be equal to a non-empty + * ListMultimap, since their {@link #asMap} views contain unequal + * collections as values. However, any two empty multimaps are equal, because + * they both have empty {@link #asMap} views. + */ + boolean equals(Object obj); + + /** + * Returns the hash code for this multimap. + * + *

+ * The hash code of a multimap is defined as the hash code of the map view, + * as returned by {@link Multimap#asMap}. + */ + int hashCode(); +} diff --git a/impl/src/main/java/org/jboss/weld/extensions/util/collections/Multimaps.java b/impl/src/main/java/org/jboss/weld/extensions/util/collections/Multimaps.java new file mode 100644 index 0000000..0565b65 --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/extensions/util/collections/Multimaps.java @@ -0,0 +1,101 @@ +package org.jboss.weld.extensions.util.collections; + +import static org.jboss.weld.extensions.util.collections.Preconditions.checkNotNull; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +public class Multimaps +{ + + private Multimaps() + { + } + + private static class CustomSetMultimap extends AbstractSetMultimap + { + transient Supplier> factory; + + CustomSetMultimap(Map> map, Supplier> factory) + { + super(map); + this.factory = checkNotNull(factory); + } + + @Override + protected Set createCollection() + { + return factory.get(); + } + + /** @serialData the factory and the backing map */ + private void writeObject(ObjectOutputStream stream) throws IOException + { + stream.defaultWriteObject(); + stream.writeObject(factory); + stream.writeObject(backingMap()); + } + + @SuppressWarnings("unchecked") + // reading data stored by writeObject + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException + { + stream.defaultReadObject(); + factory = (Supplier>) stream.readObject(); + Map> map = (Map>) stream.readObject(); + setMap(map); + } + + private static final long serialVersionUID = 0; + } + + /** + * Creates a new {@code SetMultimap} that uses the provided map and factory. + * It can generate a multimap based on arbitrary {@link Map} and {@link Set} + * classes. + * + *

+ * The {@code factory}-generated and {@code map} classes determine the + * multimap iteration order. They also specify the behavior of the {@code + * equals}, {@code hashCode}, and {@code toString} methods for the multimap + * and its returned views. However, the multimap's {@code get} method returns + * instances of a different class than {@code factory.get()} does. + * + *

+ * The multimap is serializable if {@code map}, {@code factory}, the sets + * generated by {@code factory}, and the multimap contents are all + * serializable. + * + *

+ * The multimap is not threadsafe when any concurrent operations update the + * multimap, even if {@code map} and the instances generated by {@code + * factory} are. Concurrent read operations will work correctly. To allow + * concurrent update operations, wrap the multimap with a call to + * {@link #synchronizedSetMultimap}. + * + *

+ * Call this method only when the simpler methods HashMultimap#create(), + * LinkedHashMultimap#create(), TreeMultimap#create(), and + * TreeMultimap#create(Comparator, Comparator) won't suffice. + * + *

+ * Note: the multimap assumes complete ownership over of {@code map} and the + * sets returned by {@code factory}. Those objects should not be manually + * updated and they should not use soft, weak, or phantom references. + * + * @param map place to store the mapping from each key to its corresponding + * values + * @param factory supplier of new, empty sets that will each hold all values + * for a given key + * @throws IllegalArgumentException if {@code map} is not empty + */ + public static SetMultimap newSetMultimap(Map> map, final Supplier> factory) + { + return new CustomSetMultimap(map, factory); + } + +} diff --git a/impl/src/main/java/org/jboss/weld/extensions/util/collections/Multiset.java b/impl/src/main/java/org/jboss/weld/extensions/util/collections/Multiset.java new file mode 100644 index 0000000..76e659a --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/extensions/util/collections/Multiset.java @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jboss.weld.extensions.util.collections; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +/** + * A collection that supports order-independent equality, like {@link Set}, but + * may have duplicate elements. A multiset is also sometimes called a + * bag. + * + *

Elements of a multiset that are equal to one another (see "Note on + * element equivalence", below) are referred to as occurrences of the + * same single element. The total number of occurrences of an element in a + * multiset is called the count of that element (the terms "frequency" + * and "multiplicity" are equivalent, but not used in this API). Since the count + * of an element is represented as an {@code int}, a multiset may never contain + * more than {@link Integer#MAX_VALUE} occurrences of any one element. + * + *

{@code Multiset} refines the specifications of several methods from + * {@code Collection}. It also defines an additional query operation, {@link + * #count}, which returns the count of an element. There are five new + * bulk-modification operations, for example {@link #add(Object, int)}, to add + * or remove multiple occurrences of an element at once, or to set the count of + * an element to a specific value. These modification operations are optional, + * but implementations which support the standard collection operations {@link + * #add(Object)} or {@link #remove(Object)} are encouraged to implement the + * related methods as well. Finally, two collection views are provided: {@link + * #elementSet} contains the distinct elements of the multiset "with duplicates + * collapsed", and {@link #entrySet} is similar but contains {@link Entry + * Multiset.Entry} instances, each providing both a distinct element and the + * count of that element. + * + *

In addition to these required methods, implementations of {@code + * Multiset} are expected to provide two {@code static} creation methods: + * {@code create()}, returning an empty multiset, and {@code + * create(Iterable)}, returning a multiset containing the + * given initial elements. This is simply a refinement of {@code Collection}'s + * constructor recommendations, reflecting the new developments of Java 5. + * + *

As with other collection types, the modification operations are optional, + * and should throw {@link UnsupportedOperationException} when they are not + * implemented. Most implementations should support either all add operations + * or none of them, all removal operations or none of them, and if and only if + * all of these are supported, the {@code setCount} methods as well. + * + *

A multiset uses {@link Object#equals} to determine whether two instances + * should be considered "the same," unless specified otherwise by the + * implementation. + * + * @author Kevin Bourrillion + */ +public interface Multiset extends Collection { + // Query Operations + + /** + * Returns the number of occurrences of an element in this multiset (the + * count of the element). Note that for an {@link Object#equals}-based + * multiset, this gives the same result as {@link Collections#frequency} + * (which would presumably perform more poorly). + * + * + * @param element the element to count occurrences of + * @return the number of occurrences of the element in this multiset; possibly + * zero but never negative + */ + int count(Object element); + + // Bulk Operations + + /** + * Adds a number of occurrences of an element to this multiset. Note that if + * {@code occurrences == 1}, this method has the identical effect to {@link + * #add(Object)}. This method is functionally equivalent (except in the case + * of overflow) to the call {@code addAll(Collections.nCopies(element, + * occurrences))}, which would presumably perform much more poorly. + * + * @param element the element to add occurrences of; may be {@code null} only + * if explicitly allowed by the implementation + * @param occurrences the number of occurrences of the element to add. May be + * zero, in which case no change will be made. + * @return the count of the element before the operation; possibly zero + * @throws IllegalArgumentException if {@code occurrences} is negative, or if + * this operation would result in more than {@link Integer#MAX_VALUE} + * occurrences of the element + * @throws NullPointerException if {@code element} is null and this + * implementation does not permit null elements. Note that if {@code + * occurrences} is zero, the implementation may opt to return normally. + */ + int add(E element, int occurrences); + + /** + * Removes a number of occurrences of the specified element from this + * multiset. If the multiset contains fewer than this number of occurrences to + * begin with, all occurrences will be removed. Note that if + * {@code occurrences == 1}, this is functionally equivalent to the call + * {@code remove(element)}. + * + * @param element the element to conditionally remove occurrences of + * @param occurrences the number of occurrences of the element to remove. May + * be zero, in which case no change will be made. + * @return the count of the element before the operation; possibly zero + * @throws IllegalArgumentException if {@code occurrences} is negative + */ + int remove(Object element, int occurrences); + + /** + * Adds or removes the necessary occurrences of an element such that the + * element attains the desired count. + * + * @param element the element to add or remove occurrences of; may be null + * only if explicitly allowed by the implementation + * @param count the desired count of the element in this multiset + * @return the count of the element before the operation; possibly zero + * @throws IllegalArgumentException if {@code count} is negative + * @throws NullPointerException if {@code element} is null and this + * implementation does not permit null elements. Note that if {@code + * count} is zero, the implementor may optionally return zero instead. + */ + int setCount(E element, int count); + + /** + * Conditionally sets the count of an element to a new value, as described in + * {@link #setCount(Object, int)}, provided that the element has the expected + * current count. If the current count is not {@code oldCount}, no change is + * made. + * + * @param element the element to conditionally set the count of; may be null + * only if explicitly allowed by the implementation + * @param oldCount the expected present count of the element in this multiset + * @param newCount the desired count of the element in this multiset + * @return {@code true} if the condition for modification was met. This + * implies that the multiset was indeed modified, unless + * {@code oldCount == newCount}. + * @throws IllegalArgumentException if {@code oldCount} or {@code newCount} is + * negative + * @throws NullPointerException if {@code element} is null and the + * implementation does not permit null elements. Note that if {@code + * oldCount} and {@code newCount} are both zero, the implementor may + * optionally return {@code true} instead. + */ + boolean setCount(E element, int oldCount, int newCount); + + // Views + + /** + * Returns the set of distinct elements contained in this multiset. The + * element set is backed by the same data as the multiset, so any change to + * either is immediately reflected in the other. The order of the elements in + * the element set is unspecified. + * + *

If the element set supports any removal operations, these necessarily + * cause all occurrences of the removed element(s) to be removed from + * the multiset. Implementations are not expected to support the add + * operations, although this is possible. + * + *

A common use for the element set is to find the number of distinct + * elements in the multiset: {@code elementSet().size()}. + * + * @return a view of the set of distinct elements in this multiset + */ + Set elementSet(); + + /** + * Returns a view of the contents of this multiset, grouped into {@code + * Multiset.Entry} instances, each providing an element of the multiset and + * the count of that element. This set contains exactly one entry for each + * distinct element in the multiset (thus it always has the same size as the + * {@link #elementSet}). The order of the elements in the element set is + * unspecified. + * + *

The entry set is backed by the same data as the multiset, so any change + * to either is immediately reflected in the other. However, multiset changes + * may or may not be reflected in any {@code Entry} instances already + * retrieved from the entry set (this is implementation-dependent). + * Furthermore, implementations are not required to support modifications to + * the entry set at all, and the {@code Entry} instances themselves don't + * even have methods for modification. See the specific implementation class + * for more details on how its entry set handles modifications. + * + * @return a set of entries representing the data of this multiset + */ + Set> entrySet(); + + /** + * An unmodifiable element-count pair for a multiset. The {@link + * Multiset#entrySet} method returns a view of the multiset whose elements + * are of this class. A multiset implementation may return Entry instances + * that are either live "read-through" views to the Multiset, or immutable + * snapshots. Note that this type is unrelated to the similarly-named type + * {@code Map.Entry}. + */ + interface Entry { + + /** + * Returns the multiset element corresponding to this entry. Multiple calls + * to this method always return the same instance. + * + * @return the element corresponding to this entry + */ + E getElement(); + + /** + * Returns the count of the associated element in the underlying multiset. + * This count may either be an unchanging snapshot of the count at the time + * the entry was retrieved, or a live view of the current count of the + * element in the multiset, depending on the implementation. Note that in + * the former case, this method can never return zero, while in the latter, + * it will return zero if all occurrences of the element were since removed + * from the multiset. + * + * @return the count of the element; never negative + */ + int getCount(); + + /** + * {@inheritDoc} + * + *

Returns {@code true} if the given object is also a multiset entry and + * the two entries represent the same element and count. More formally, two + * entries {@code a} and {@code b} are equal if: + * + *

  ((a.getElement() == null)
+     *      ? (b.getElement() == null) : a.getElement().equals(b.getElement()))
+     *    && (a.getCount() == b.getCount())
+ */ + // TODO: check this wrt TreeMultiset? + boolean equals(Object o); + + /** + * {@inheritDoc} + * + *

The hash code of a multiset entry for element {@code element} and + * count {@code count} is defined as: + * + *

  (element == null ? 0 : element.hashCode()) ^ count
+ */ + int hashCode(); + + /** + * Returns the canonical string representation of this entry, defined as + * follows. If the count for this entry is one, this is simply the string + * representation of the corresponding element. Otherwise, it is the string + * representation of the element, followed by the three characters {@code + * " x "} (space, letter x, space), followed by the count. + */ + String toString(); + } + + // Comparison and hashing + + /** + * Compares the specified object with this multiset for equality. Returns + * {@code true} if the given object is also a multiset and contains equal + * elements with equal counts, regardless of order. + */ + // TODO: caveats about equivalence-relation? + boolean equals(Object object); + + /** + * Returns the hash code for this multiset. This is defined as the sum of + * + *
  (element == null ? 0 : element.hashCode()) ^ count(element)
+ * + * over all distinct elements in the multiset. It follows that a multiset and + * its entry set always have the same hash code. + */ + int hashCode(); + + /** + * {@inheritDoc} + * + *

It is recommended, though not mandatory, that this method return the + * result of invoking {@link #toString} on the {@link #entrySet}, yielding a + * result such as + *

+   *     [a x 3, c, d x 2, e]
+   * 
+ */ + String toString(); + + // Refined Collection Methods + + /** + * {@inheritDoc} + * + *

Elements that occur multiple times in the multiset will appear + * multiple times in this iterator, though not necessarily sequentially. + */ + Iterator iterator(); + + /** + * Determines whether this multiset contains the specified element. + * + *

This method refines {@link Collection#contains} to further specify that + * it may not throw an exception in response to {@code element} being + * null or of the wrong type. + * + * @param element the element to check for + * @return {@code true} if this multiset contains at least one occurrence of + * the element + */ + boolean contains(Object element); + + /** + * Returns {@code true} if this multiset contains at least one occurrence of + * each element in the specified collection. + * + *

This method refines {@link Collection#containsAll} to further specify + * that it may not throw an exception in response to any of {@code + * elements} being null or of the wrong type. + * + *

Note: this method does not take into account the occurrence + * count of an element in the two collections; it may still return {@code + * true} even if {@code elements} contains several occurrences of an element + * and this multiset contains only one. This is no different than any other + * collection type like {@link List}, but it may be unexpected to the user of + * a multiset. + * + * @param elements the collection of elements to be checked for containment in + * this multiset + * @return {@code true} if this multiset contains at least one occurrence of + * each element contained in {@code elements} + * @throws NullPointerException if {@code elements} is null + */ + boolean containsAll(Collection elements); + + /** + * Adds a single occurrence of the specified element to this multiset. + * + *

This method refines {@link Collection#add}, which only ensures + * the presence of the element, to further specify that a successful call must + * always increment the count of the element, and the overall size of the + * collection, by one. + * + * @param element the element to add one occurrence of; may be null only if + * explicitly allowed by the implementation + * @return {@code true} always, since this call is required to modify the + * multiset, unlike other {@link Collection} types + * @throws NullPointerException if {@code element} is null and this + * implementation does not permit null elements + * @throws IllegalArgumentException if {@link Integer#MAX_VALUE} occurrences + * of {@code element} are already contained in this multiset + */ + boolean add(E element); + + /** + * Removes a single occurrence of the specified element from this + * multiset, if present. + * + *

This method refines {@link Collection#remove} to further specify that it + * may not throw an exception in response to {@code element} being null + * or of the wrong type. + * + * @param element the element to remove one occurrence of + * @return {@code true} if an occurrence was found and removed + */ + boolean remove(Object element); + + /** + * {@inheritDoc} + * + *

This method refines {@link Collection#removeAll} to further specify that + * it may not throw an exception in response to any of {@code elements} + * being null or of the wrong type. + */ + boolean removeAll(Collection c); + + /** + * {@inheritDoc} + * + *

This method refines {@link Collection#retainAll} to further specify that + * it may not throw an exception in response to any of {@code elements} + * being null or of the wrong type. + */ + boolean retainAll(Collection c); +} diff --git a/impl/src/main/java/org/jboss/weld/extensions/util/collections/Multisets.java b/impl/src/main/java/org/jboss/weld/extensions/util/collections/Multisets.java new file mode 100644 index 0000000..1ad49c2 --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/extensions/util/collections/Multisets.java @@ -0,0 +1,89 @@ +package org.jboss.weld.extensions.util.collections; + +import static org.jboss.weld.extensions.util.collections.Preconditions.checkArgument; + +public class Multisets +{ + + private Multisets() + { + } + + /** + * Implementation of the {@code equals}, {@code hashCode}, and + * {@code toString} methods of {@link Multiset.Entry}. + */ + abstract static class AbstractEntry implements Multiset.Entry { + /** + * Indicates whether an object equals this entry, following the behavior + * specified in {@link Multiset.Entry#equals}. + */ + @Override public boolean equals(Object object) { + if (object instanceof Multiset.Entry) { + Multiset.Entry that = (Multiset.Entry) object; + return this.getCount() == that.getCount() + && Objects.equal(this.getElement(), that.getElement()); + } + return false; + } + + /** + * Return this entry's hash code, following the behavior specified in + * {@link Multiset.Entry#hashCode}. + */ + @Override public int hashCode() { + E e = getElement(); + return ((e == null) ? 0 : e.hashCode()) ^ getCount(); + } + + /** + * Returns a string representation of this multiset entry. The string + * representation consists of the associated element if the associated count + * is one, and otherwise the associated element followed by the characters + * " x " (space, x and space) followed by the count. Elements and counts are + * converted to strings as by {@code String.valueOf}. + */ + @Override public String toString() { + String text = String.valueOf(getElement()); + int n = getCount(); + return (n == 1) ? text : (text + " x " + n); + } + } + + + static boolean setCountImpl(Multiset self, E element, int oldCount, int newCount) + { + checkNonnegative(oldCount, "oldCount"); + checkNonnegative(newCount, "newCount"); + + if (self.count(element) == oldCount) + { + self.setCount(element, newCount); + return true; + } + else + { + return false; + } + } + + static int setCountImpl(Multiset self, E element, int count) { + checkNonnegative(count, "count"); + + int oldCount = self.count(element); + + int delta = count - oldCount; + if (delta > 0) { + self.add(element, delta); + } else if (delta < 0) { + self.remove(element, -delta); + } + + return oldCount; + } + + static void checkNonnegative(int count, String name) + { + checkArgument(count >= 0, "%s cannot be negative: %s", name, count); + } +} diff --git a/impl/src/main/java/org/jboss/weld/extensions/util/collections/Objects.java b/impl/src/main/java/org/jboss/weld/extensions/util/collections/Objects.java new file mode 100644 index 0000000..88f3e40 --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/extensions/util/collections/Objects.java @@ -0,0 +1,29 @@ +package org.jboss.weld.extensions.util.collections; + +public class Objects +{ + + private Objects() + { + } + + /** + * Determines whether two possibly-null objects are equal. Returns: + * + *

    + *
  • {@code true} if {@code a} and {@code b} are both null. + *
  • {@code true} if {@code a} and {@code b} are both non-null and they are + * equal according to {@link Object#equals(Object)}. + *
  • {@code false} in all other situations. + *
+ * + *

+ * This assumes that any non-null objects passed to this function conform to + * the {@code equals()} contract. + */ + public static boolean equal(Object a, Object b) + { + return a == b || (a != null && a.equals(b)); + } + +} diff --git a/impl/src/main/java/org/jboss/weld/extensions/util/collections/Platform.java b/impl/src/main/java/org/jboss/weld/extensions/util/collections/Platform.java new file mode 100644 index 0000000..0a441f0 --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/extensions/util/collections/Platform.java @@ -0,0 +1,15 @@ +package org.jboss.weld.extensions.util.collections; + +import java.util.List; + +public class Platform +{ + + private Platform() {} + + static List subList(List list, int fromIndex, int toIndex) + { + return list.subList(fromIndex, toIndex); + } + +} diff --git a/impl/src/main/java/org/jboss/weld/extensions/util/collections/Preconditions.java b/impl/src/main/java/org/jboss/weld/extensions/util/collections/Preconditions.java new file mode 100644 index 0000000..26304cc --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/extensions/util/collections/Preconditions.java @@ -0,0 +1,147 @@ +package org.jboss.weld.extensions.util.collections; + +public class Preconditions +{ + private Preconditions() + { + } + + /** + * Ensures the truth of an expression involving one or more parameters to the + * calling method. + * + * @param expression a boolean expression + * @param errorMessageTemplate a template for the exception message should + * the check fail. The message is formed by replacing each {@code + * %s} placeholder in the template with an argument. These are + * matched by position - the first {@code %s} gets {@code + * errorMessageArgs[0]}, etc. Unmatched arguments will be appended + * to the formatted message in square braces. Unmatched + * placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message + * template. Arguments are converted to strings using + * {@link String#valueOf(Object)}. + * @throws IllegalArgumentException if {@code expression} is false + * @throws NullPointerException if the check fails and either {@code + * errorMessageTemplate} or {@code errorMessageArgs} is null + * (don't let this happen) + */ + public static void checkArgument(boolean expression, String errorMessageTemplate, Object... errorMessageArgs) + { + if (!expression) + { + throw new IllegalArgumentException(format(errorMessageTemplate, errorMessageArgs)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the + * calling method. + * + * @param expression a boolean expression + * @throws IllegalArgumentException if {@code expression} is false + */ + public static void checkArgument(boolean expression) + { + if (!expression) + { + throw new IllegalArgumentException(); + } + } + + /** + * Substitutes each {@code %s} in {@code template} with an argument. These + * are matched by position - the first {@code %s} gets {@code args[0]}, etc. + * If there are more arguments than placeholders, the unmatched arguments + * will be appended to the end of the formatted message in square braces. + * + * @param template a non-null string containing 0 or more {@code %s} + * placeholders. + * @param args the arguments to be substituted into the message template. + * Arguments are converted to strings using + * {@link String#valueOf(Object)}. Arguments can be null. + */ + static String format(String template, Object... args) + { + // start substituting the arguments into the '%s' placeholders + StringBuilder builder = new StringBuilder(template.length() + 16 * args.length); + int templateStart = 0; + int i = 0; + while (i < args.length) + { + int placeholderStart = template.indexOf("%s", templateStart); + if (placeholderStart == -1) + { + break; + } + builder.append(template.substring(templateStart, placeholderStart)); + builder.append(args[i++]); + templateStart = placeholderStart + 2; + } + builder.append(template.substring(templateStart)); + + // if we run out of placeholders, append the extra args in square braces + if (i < args.length) + { + builder.append(" ["); + builder.append(args[i++]); + while (i < args.length) + { + builder.append(", "); + builder.append(args[i++]); + } + builder.append("]"); + } + + return builder.toString(); + } + + /** + * Ensures the truth of an expression involving the state of the calling + * instance, but not involving any parameters to the calling method. + * + * @param expression a boolean expression + * @param errorMessage the exception message to use if the check fails; will + * be converted to a string using {@link String#valueOf(Object)} + * @throws IllegalStateException if {@code expression} is false + */ + public static void checkState(boolean expression, Object errorMessage) + { + if (!expression) + { + throw new IllegalStateException(String.valueOf(errorMessage)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling + * instance, but not involving any parameters to the calling method. + * + * @param expression a boolean expression + * @throws IllegalStateException if {@code expression} is false + */ + public static void checkState(boolean expression) + { + if (!expression) + { + throw new IllegalStateException(); + } + } + + /** + * Ensures that an object reference passed as a parameter to the calling + * method is not null. + * + * @param reference an object reference + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + */ + public static T checkNotNull(T reference) + { + if (reference == null) + { + throw new NullPointerException(); + } + return reference; + } +} diff --git a/impl/src/main/java/org/jboss/weld/extensions/util/collections/Serialization.java b/impl/src/main/java/org/jboss/weld/extensions/util/collections/Serialization.java new file mode 100644 index 0000000..5db0812 --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/extensions/util/collections/Serialization.java @@ -0,0 +1,80 @@ +package org.jboss.weld.extensions.util.collections; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collection; +import java.util.Map; + +public class Serialization +{ + + private Serialization() + { + } + + /** + * Reads a count corresponding to a serialized map, multiset, or multimap. It + * returns the size of a map serialized by + * {@link #writeMap(Map, ObjectOutputStream)}, the number of distinct + * elements in a multiset serialized by + * {@link #writeMultiset(Multiset, ObjectOutputStream)}, or the number of + * distinct keys in a multimap serialized by + * {@link #writeMultimap(Multimap, ObjectOutputStream)}. + * + *

+ * The returned count may be used to construct an empty collection of the + * appropriate capacity before calling any of the {@code populate} methods. + */ + public static int readCount(ObjectInputStream stream) throws IOException + { + return stream.readInt(); + } + + /** + * Stores the contents of a multimap in an output stream, as part of + * serialization. It does not support concurrent multimaps whose content may + * change while the method is running. The {@link Multimap#asMap} view + * determines the ordering in which data is written to the stream. + * + *

+ * The serialized output consists of the number of distinct keys, and then + * for each distinct key: the key, the number of values for that key, and the + * key's values. + */ + public static void writeMultimap(Multimap multimap, ObjectOutputStream stream) throws IOException + { + stream.writeInt(multimap.asMap().size()); + for (Map.Entry> entry : multimap.asMap().entrySet()) + { + stream.writeObject(entry.getKey()); + stream.writeInt(entry.getValue().size()); + for (V value : entry.getValue()) + { + stream.writeObject(value); + } + } + } + + /** + * Populates a multimap by reading an input stream, as part of + * deserialization. See {@link #writeMultimap} for the data format. The number + * of distinct keys is determined by a prior call to {@link #readCount}. + */ + public static void populateMultimap( + Multimap multimap, ObjectInputStream stream, int distinctKeys) + throws IOException, ClassNotFoundException { + for (int i = 0; i < distinctKeys; i++) { + @SuppressWarnings("unchecked") // reading data stored by writeMultimap + K key = (K) stream.readObject(); + Collection values = multimap.get(key); + int valueCount = stream.readInt(); + for (int j = 0; j < valueCount; j++) { + @SuppressWarnings("unchecked") // reading data stored by writeMultimap + V value = (V) stream.readObject(); + values.add(value); + } + } + } + +} diff --git a/impl/src/main/java/org/jboss/weld/extensions/util/collections/SetMultimap.java b/impl/src/main/java/org/jboss/weld/extensions/util/collections/SetMultimap.java new file mode 100644 index 0000000..deefe80 --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/extensions/util/collections/SetMultimap.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jboss.weld.extensions.util.collections; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * A {@code Multimap} that cannot hold duplicate key-value pairs. Adding a + * key-value pair that's already in the multimap has no effect. + * + *

The {@link #get}, {@link #removeAll}, and {@link #replaceValues} methods + * each return a {@link Set} of values, while {@link #entries} returns a {@code + * Set} of map entries. Though the method signature doesn't say so explicitly, + * the map returned by {@link #asMap} has {@code Set} values. + * + * @author Jared Levy + */ +public interface SetMultimap extends Multimap { + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this + * method returns a {@link Set}, instead of the {@link java.util.Collection} + * specified in the {@link Multimap} interface. + */ + Set get(K key); + + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this + * method returns a {@link Set}, instead of the {@link java.util.Collection} + * specified in the {@link Multimap} interface. + */ + Set removeAll(Object key); + + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this + * method returns a {@link Set}, instead of the {@link java.util.Collection} + * specified in the {@link Multimap} interface. + * + *

Any duplicates in {@code values} will be stored in the multimap once. + */ + Set replaceValues(K key, Iterable values); + + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this + * method returns a {@link Set}, instead of the {@link java.util.Collection} + * specified in the {@link Multimap} interface. + */ + Set> entries(); + + /** + * {@inheritDoc} + * + *

Though the method signature doesn't say so explicitly, the returned map + * has {@link Set} values. + */ + Map> asMap(); + + /** + * Compares the specified object to this multimap for equality. + * + *

Two {@code SetMultimap} instances are equal if, for each key, they + * contain the same values. Equality does not depend on the ordering of keys + * or values. + */ + boolean equals(Object obj); +} diff --git a/impl/src/main/java/org/jboss/weld/extensions/util/collections/Sets.java b/impl/src/main/java/org/jboss/weld/extensions/util/collections/Sets.java new file mode 100644 index 0000000..c7398ca --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/extensions/util/collections/Sets.java @@ -0,0 +1,40 @@ +package org.jboss.weld.extensions.util.collections; + +import java.util.HashSet; +import java.util.Set; + +public class Sets +{ + + private Sets() + { + } + + /** + * Calculates and returns the hash code of {@code s}. + */ + static int hashCodeImpl(Set s) + { + int hashCode = 0; + for (Object o : s) + { + hashCode += o != null ? o.hashCode() : 0; + } + return hashCode; + } + + /** + * Creates an empty {@code HashSet} instance with enough capacity to hold the + * specified number of elements without rehashing. + * + * @param expectedSize the expected size + * @return a new, empty {@code HashSet} with enough capacity to hold {@code + * expectedSize} elements without rehashing + * @throws IllegalArgumentException if {@code expectedSize} is negative + */ + public static HashSet newHashSetWithExpectedSize(int expectedSize) + { + return new HashSet(Maps.capacity(expectedSize)); + } + +} diff --git a/impl/src/main/java/org/jboss/weld/extensions/util/collections/Supplier.java b/impl/src/main/java/org/jboss/weld/extensions/util/collections/Supplier.java new file mode 100644 index 0000000..9b7ee57 --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/extensions/util/collections/Supplier.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jboss.weld.extensions.util.collections; + + +/** + * A class that can supply objects of a single type. Semantically, this could + * be a factory, generator, builder, closure, or something else entirely. No + * guarantees are implied by this interface. + * + * @author Harry Heymann + */ +public interface Supplier { + /** + * Retrieves an instance of the appropriate type. The returned object may or + * may not be a new instance, depending on the implementation. + * + * @return an instance of the appropriate type + */ + public T get(); +}