org.apache.commons.collections.DefaultMapBag Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.commons.collections;
import java.util.ArrayList;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections.set.UnmodifiableSet;
/**
* A skeletal implementation of the {@link Bag}
* interface to minimize the effort required for target implementations.
* Subclasses need only to call setMap(Map)
in their constructor
* (or invoke the Map constructor) specifying a map instance that will be used
* to store the contents of the bag.
*
* The map will be used to map bag elements to a number; the number represents
* the number of occurrences of that element in the bag.
*
* @deprecated Moved to bag subpackage as AbstractMapBag. Due to be removed in v4.0.
* @since Commons Collections 2.0
* @version $Revision: 646777 $ $Date: 2008-04-10 14:33:15 +0200 (Thu, 10 Apr 2008) $
*
* @author Chuck Burdick
* @author Michael A. Smith
* @author Stephen Colebourne
* @author Janek Bogucki
*/
public abstract class DefaultMapBag implements Bag {
private Map _map = null;
private int _total = 0;
private int _mods = 0;
/**
* No-argument constructor.
* Subclasses should invoke setMap(Map)
in
* their constructors.
*/
public DefaultMapBag() {
}
/**
* Constructor that assigns the specified Map as the backing store.
* The map must be empty.
*
* @param map the map to assign
*/
protected DefaultMapBag(Map map) {
setMap(map);
}
/**
* Adds a new element to the bag by incrementing its count in the
* underlying map.
*
* @param object the object to add
* @return true
if the object was not already in the uniqueSet
*/
public boolean add(Object object) {
return add(object, 1);
}
/**
* Adds a new element to the bag by incrementing its count in the map.
*
* @param object the object to search for
* @param nCopies the number of copies to add
* @return true
if the object was not already in the uniqueSet
*/
public boolean add(Object object, int nCopies) {
_mods++;
if (nCopies > 0) {
int count = (nCopies + getCount(object));
_map.put(object, new Integer(count));
_total += nCopies;
return (count == nCopies);
} else {
return false;
}
}
/**
* Invokes {@link #add(Object)} for each element in the given collection.
*
* @param coll the collection to add
* @return true
if this call changed the bag
*/
public boolean addAll(Collection coll) {
boolean changed = false;
Iterator i = coll.iterator();
while (i.hasNext()) {
boolean added = add(i.next());
changed = changed || added;
}
return changed;
}
/**
* Clears the bag by clearing the underlying map.
*/
public void clear() {
_mods++;
_map.clear();
_total = 0;
}
/**
* Determines if the bag contains the given element by checking if the
* underlying map contains the element as a key.
*
* @param object the object to search for
* @return true if the bag contains the given element
*/
public boolean contains(Object object) {
return _map.containsKey(object);
}
/**
* Determines if the bag contains the given elements.
*
* @param coll the collection to check against
* @return true
if the Bag contains all the collection
*/
public boolean containsAll(Collection coll) {
return containsAll(new HashBag(coll));
}
/**
* Returns true
if the bag contains all elements in
* the given collection, respecting cardinality.
*
* @param other the bag to check against
* @return true
if the Bag contains all the collection
*/
public boolean containsAll(Bag other) {
boolean result = true;
Iterator i = other.uniqueSet().iterator();
while (i.hasNext()) {
Object current = i.next();
boolean contains = getCount(current) >= other.getCount(current);
result = result && contains;
}
return result;
}
/**
* Returns true if the given object is not null, has the precise type
* of this bag, and contains the same number of occurrences of all the
* same elements.
*
* @param object the object to test for equality
* @return true if that object equals this bag
*/
public boolean equals(Object object) {
if (object == this) {
return true;
}
if (object instanceof Bag == false) {
return false;
}
Bag other = (Bag) object;
if (other.size() != size()) {
return false;
}
for (Iterator it = _map.keySet().iterator(); it.hasNext();) {
Object element = it.next();
if (other.getCount(element) != getCount(element)) {
return false;
}
}
return true;
}
/**
* Returns the hash code of the underlying map.
*
* @return the hash code of the underlying map
*/
public int hashCode() {
return _map.hashCode();
}
/**
* Returns true if the underlying map is empty.
*
* @return true if there are no elements in this bag
*/
public boolean isEmpty() {
return _map.isEmpty();
}
public Iterator iterator() {
return new BagIterator(this, extractList().iterator());
}
static class BagIterator implements Iterator {
private DefaultMapBag _parent = null;
private Iterator _support = null;
private Object _current = null;
private int _mods = 0;
public BagIterator(DefaultMapBag parent, Iterator support) {
_parent = parent;
_support = support;
_current = null;
_mods = parent.modCount();
}
public boolean hasNext() {
return _support.hasNext();
}
public Object next() {
if (_parent.modCount() != _mods) {
throw new ConcurrentModificationException();
}
_current = _support.next();
return _current;
}
public void remove() {
if (_parent.modCount() != _mods) {
throw new ConcurrentModificationException();
}
_support.remove();
_parent.remove(_current, 1);
_mods++;
}
}
public boolean remove(Object object) {
return remove(object, getCount(object));
}
public boolean remove(Object object, int nCopies) {
_mods++;
boolean result = false;
int count = getCount(object);
if (nCopies <= 0) {
result = false;
} else if (count > nCopies) {
_map.put(object, new Integer(count - nCopies));
result = true;
_total -= nCopies;
} else { // count > 0 && count <= i
// need to remove all
result = (_map.remove(object) != null);
_total -= count;
}
return result;
}
public boolean removeAll(Collection coll) {
boolean result = false;
if (coll != null) {
Iterator i = coll.iterator();
while (i.hasNext()) {
boolean changed = remove(i.next(), 1);
result = result || changed;
}
}
return result;
}
/**
* Remove any members of the bag that are not in the given
* bag, respecting cardinality.
*
* @param coll the collection to retain
* @return true if this call changed the collection
*/
public boolean retainAll(Collection coll) {
return retainAll(new HashBag(coll));
}
/**
* Remove any members of the bag that are not in the given
* bag, respecting cardinality.
* @see #retainAll(Collection)
*
* @param other the bag to retain
* @return true
if this call changed the collection
*/
public boolean retainAll(Bag other) {
boolean result = false;
Bag excess = new HashBag();
Iterator i = uniqueSet().iterator();
while (i.hasNext()) {
Object current = i.next();
int myCount = getCount(current);
int otherCount = other.getCount(current);
if (1 <= otherCount && otherCount <= myCount) {
excess.add(current, myCount - otherCount);
} else {
excess.add(current, myCount);
}
}
if (!excess.isEmpty()) {
result = removeAll(excess);
}
return result;
}
/**
* Returns an array of all of this bag's elements.
*
* @return an array of all of this bag's elements
*/
public Object[] toArray() {
return extractList().toArray();
}
/**
* Returns an array of all of this bag's elements.
*
* @param array the array to populate
* @return an array of all of this bag's elements
*/
public Object[] toArray(Object[] array) {
return extractList().toArray(array);
}
/**
* Returns the number of occurrence of the given element in this bag
* by looking up its count in the underlying map.
*
* @param object the object to search for
* @return the number of occurrences of the object, zero if not found
*/
public int getCount(Object object) {
int result = 0;
Integer count = MapUtils.getInteger(_map, object);
if (count != null) {
result = count.intValue();
}
return result;
}
/**
* Returns an unmodifiable view of the underlying map's key set.
*
* @return the set of unique elements in this bag
*/
public Set uniqueSet() {
return UnmodifiableSet.decorate(_map.keySet());
}
/**
* Returns the number of elements in this bag.
*
* @return the number of elements in this bag
*/
public int size() {
return _total;
}
/**
* Actually walks the bag to make sure the count is correct and
* resets the running total
*
* @return the current total size
*/
protected int calcTotalSize() {
_total = extractList().size();
return _total;
}
/**
* Utility method for implementations to set the map that backs
* this bag. Not intended for interactive use outside of
* subclasses.
*/
protected void setMap(Map map) {
if (map == null || map.isEmpty() == false) {
throw new IllegalArgumentException("The map must be non-null and empty");
}
_map = map;
}
/**
* Utility method for implementations to access the map that backs
* this bag. Not intended for interactive use outside of
* subclasses.
*/
protected Map getMap() {
return _map;
}
/**
* Create a list for use in iteration, etc.
*/
private List extractList() {
List result = new ArrayList();
Iterator i = uniqueSet().iterator();
while (i.hasNext()) {
Object current = i.next();
for (int index = getCount(current); index > 0; index--) {
result.add(current);
}
}
return result;
}
/**
* Return number of modifications for iterator.
*
* @return the modification count
*/
private int modCount() {
return _mods;
}
/**
* Implement a toString() method suitable for debugging.
*
* @return a debugging toString
*/
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("[");
Iterator i = uniqueSet().iterator();
while (i.hasNext()) {
Object current = i.next();
int count = getCount(current);
buf.append(count);
buf.append(":");
buf.append(current);
if (i.hasNext()) {
buf.append(",");
}
}
buf.append("]");
return buf.toString();
}
}