/* * JBoss, Home of Professional Open Source * Copyright 2010, Red Hat Middleware LLC, and individual contributors * by the @authors tag. See the copyright.txt in the distribution for a * full listing of individual contributors. * * 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.solder.util.collections; import java.util.AbstractCollection; import java.util.Collection; import java.util.ConcurrentModificationException; import java.util.Iterator; import static org.jboss.solder.util.collections.Preconditions.checkNotNull; /** * 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. * <p/> * <p/> * 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. * <p/> * <p/> * 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. */ class WrappedCollection<K, V> extends AbstractCollection<V> { /** * */ private final AbstractMultimap<K, V> abstractMultimap; final K key; Collection<V> delegate; final WrappedCollection<K, V> ancestor; final Collection<V> ancestorDelegate; WrappedCollection(AbstractMultimap<K, V> abstractMultimap, K key, Collection<V> delegate, WrappedCollection<K, V> ancestor) { this.abstractMultimap = abstractMultimap; this.key = key; this.delegate = delegate; this.ancestor = ancestor; this.ancestorDelegate = (ancestor == null) ? null : ancestor.getDelegate(); } public AbstractMultimap<K, V> getParent() { return this.abstractMultimap; } /** * If the delegate collection is empty, but the multimap has values for * the key, replace the delegate with the new collection for the key. * <p/> * <p/> * 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<V> newDelegate = abstractMultimap.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()) { abstractMultimap.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. * <p/> * <p/> * Subcollection add the ancestor's delegate instead. */ void addToMap() { if (ancestor != null) { ancestor.addToMap(); } else { abstractMultimap.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<V> getDelegate() { return delegate; } @Override public Iterator<V> iterator() { refreshIfEmpty(); return new WrappedIterator(this); } @Override public boolean add(V value) { refreshIfEmpty(); boolean wasEmpty = delegate.isEmpty(); boolean changed = delegate.add(value); if (changed) { abstractMultimap.totalSize++; if (wasEmpty) { addToMap(); } } return changed; } WrappedCollection<K, V> getAncestor() { return ancestor; } // The following methods are provided for better performance. @Override public boolean addAll(Collection<? extends V> collection) { if (collection.isEmpty()) { return false; } int oldSize = size(); // calls refreshIfEmpty boolean changed = delegate.addAll(collection); if (changed) { int newSize = delegate.size(); abstractMultimap.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(); abstractMultimap.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) { abstractMultimap.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(); abstractMultimap.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(); abstractMultimap.totalSize += (newSize - oldSize); removeIfEmpty(); } return changed; } }