Update licence and javadoc

git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/collections/trunk@131083 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Stephen Colebourne 2003-05-16 14:24:55 +00:00
parent edc53fb910
commit c16a5e8ea5
3 changed files with 560 additions and 557 deletions

View File

@ -1,9 +1,10 @@
/* /*
* $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/DefaultMapBag.java,v 1.7 2003/01/13 23:54:38 rwaldhoff Exp $ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/DefaultMapBag.java,v 1.8 2003/05/16 14:24:55 scolebourne Exp $
* ==================================================================== * ====================================================================
*
* The Apache Software License, Version 1.1 * The Apache Software License, Version 1.1
* *
* Copyright (c) 1999-2003 The Apache Software Foundation. All rights * Copyright (c) 2002-2003 The Apache Software Foundation. All rights
* reserved. * reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -54,7 +55,6 @@
* <http://www.apache.org/>. * <http://www.apache.org/>.
* *
*/ */
package org.apache.commons.collections; package org.apache.commons.collections;
import java.util.ArrayList; import java.util.ArrayList;
@ -72,383 +72,412 @@ import java.util.Set;
* Subclasses need only to call <code>setMap(Map)</code> in their constructor * Subclasses need only to call <code>setMap(Map)</code> in their constructor
* (or invoke the {@link #DefaultMapBag(java.util.Map) Map-constructor}) * (or invoke the {@link #DefaultMapBag(java.util.Map) Map-constructor})
* specifying a map instance that will be used to store the contents of * specifying a map instance that will be used to store the contents of
* the bag.<P> * the bag.
* * <p>
* The map will be used to map bag elements to a number; the number represents * 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.<P> * the number of occurrences of that element in the bag.
* *
* @since Commons Collections 2.0 * @since Commons Collections 2.0
* @version $Revision: 1.7 $ $Date: 2003/01/13 23:54:38 $ * @version $Revision: 1.8 $ $Date: 2003/05/16 14:24:55 $
*
* @author Chuck Burdick * @author Chuck Burdick
* @author <a href="mailto:mas@apache.org">Michael A. Smith</a> * @author <a href="mailto:mas@apache.org">Michael A. Smith</a>
**/ * @author Stephen Colebourne
*/
public abstract class DefaultMapBag implements Bag { public abstract class DefaultMapBag implements Bag {
private Map _map = null; private Map _map = null;
private int _total = 0; private int _total = 0;
private int _mods = 0; private int _mods = 0;
/**
* No-argument constructor.
* Subclasses should invoke <code>setMap(Map)</code> in
* their constructors.
*/
public DefaultMapBag() {
}
/**
* @since Commons Collections 2.2
*/
public DefaultMapBag(Map map) {
setMap(map);
}
/**
* Adds a new element to the bag by incrementing its count in the
* underlying map.
*
* @see Bag#add(Object)
*/
public boolean add(Object o) {
return add(o, 1);
}
/**
* Adds a new element to the bag by incrementing its count in the map.
*
* @see Bag#add(Object, int)
*/
public boolean add(Object o, int i) {
_mods++;
if (i > 0) {
int count = (i + getCount(o));
_map.put(o, new Integer(count));
_total += i;
return (count == i);
} else {
return false;
}
}
/**
* Invokes {@link #add(Object)} for each element in the given collection.
*
* @see Bag#addAll(Collection)
*/
public boolean addAll(Collection c) {
boolean changed = false;
Iterator i = c.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.
*
* @return true if the bag contains the given element
*/
public boolean contains(Object o) {
return _map.containsKey(o);
}
public boolean containsAll(Collection c) {
return containsAll(new HashBag(c));
}
/**
* Returns <code>true</code> if the bag contains all elements in
* the given collection, respecting cardinality.
* @see #containsAll(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) >= ((Bag)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 o the object to test for equality
* @return true if that object equals this bag
*/
public boolean equals(Object o) {
return (o == this ||
(o != null && o.getClass().equals(this.getClass()) &&
((DefaultMapBag)o)._map.equals(this._map)));
}
/**
* 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());
}
private 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 o) {
return remove(o, getCount(o));
}
public boolean remove (Object o, int i) {
_mods++;
boolean result = false;
int count = getCount(o);
if (i <= 0) {
result = false;
} else if (count > i) {
_map.put(o, new Integer(count - i));
result = true;
_total -= i;
} else { // count > 0 && count <= i
// need to remove all
result = (_map.remove(o) != null);
_total -= count;
}
return result;
}
public boolean removeAll(Collection c) {
boolean result = false;
if (c != null) {
Iterator i = c.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.
*
* @return true if this call changed the collection
*/
public boolean retainAll(Collection c) {
return retainAll(new HashBag(c));
}
/**
* Remove any members of the bag that are not in the given
* bag, respecting cardinality.
* @see #retainAll(Collection)
* @return <code>true</code> 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 a the array to populate
* @return an array of all of this bag's elements
*/
public Object[] toArray(Object[] a) {
return extractList().toArray(a);
}
/**
* Returns the number of occurrence of the given element in this bag
* by looking up its count in the underlying map.
*
* @see Bag#getCount(Object)
*/
public int getCount(Object o) {
int result = 0;
Integer count = MapUtils.getInteger(_map, o);
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 Collections.unmodifiableSet(_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
**/
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 m) {
_map = m;
}
/**
* 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
**/
private int modCount() {
return _mods;
}
/** /**
* Implement a toString() method suitable for debugging * No-argument constructor.
**/ * Subclasses should invoke <code>setMap(Map)</code> 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 <code>true</code> if the object was not already in the <code>uniqueSet</code>
*/
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 <code>true</code> if the object was not already in the <code>uniqueSet</code>
*/
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 <code>true</code> 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 <code>true</code> if the Bag contains all the collection
*/
public boolean containsAll(Collection coll) {
return containsAll(new HashBag(coll));
}
/**
* Returns <code>true</code> if the bag contains all elements in
* the given collection, respecting cardinality.
*
* @param other the bag to check against
* @return <code>true</code> 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) >= ((Bag) 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;
}
return (object != null &&
object.getClass().equals(this.getClass()) &&
((DefaultMapBag) object)._map.equals(this._map));
}
/**
* 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());
}
private 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 <code>true</code> 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 Collections.unmodifiableSet(_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() { public String toString() {
StringBuffer buf = new StringBuffer(); StringBuffer buf = new StringBuffer();
buf.append("["); buf.append("[");
Iterator i = uniqueSet().iterator(); Iterator i = uniqueSet().iterator();
while(i.hasNext()) { while (i.hasNext()) {
Object current = i.next(); Object current = i.next();
int count = getCount(current); int count = getCount(current);
buf.append(count); buf.append(count);
buf.append(":"); buf.append(":");
buf.append(current); buf.append(current);
if(i.hasNext()) { if (i.hasNext()) {
buf.append(","); buf.append(",");
} }
} }
buf.append("]"); buf.append("]");
return buf.toString(); return buf.toString();
} }
} }

View File

@ -1,13 +1,10 @@
/* /*
* $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/DefaultMapEntry.java,v 1.8 2002/08/15 20:04:31 pjack Exp $ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/DefaultMapEntry.java,v 1.9 2003/05/16 14:24:54 scolebourne Exp $
* $Revision: 1.8 $
* $Date: 2002/08/15 20:04:31 $
*
* ==================================================================== * ====================================================================
* *
* The Apache Software License, Version 1.1 * The Apache Software License, Version 1.1
* *
* Copyright (c) 1999-2002 The Apache Software Foundation. All rights * Copyright (c) 2001-2003 The Apache Software Foundation. All rights
* reserved. * reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -23,11 +20,11 @@
* distribution. * distribution.
* *
* 3. The end-user documentation included with the redistribution, if * 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement: * any, must include the following acknowledgment:
* "This product includes software developed by the * "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)." * Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself, * Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear. * if and wherever such third-party acknowledgments normally appear.
* *
* 4. The names "The Jakarta Project", "Commons", and "Apache Software * 4. The names "The Jakarta Project", "Commons", and "Apache Software
* Foundation" must not be used to endorse or promote products derived * Foundation" must not be used to endorse or promote products derived
@ -36,7 +33,7 @@
* *
* 5. Products derived from this software may not be called "Apache" * 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written * nor may "Apache" appear in their names without prior written
* permission of the Apache Group. * permission of the Apache Software Foundation.
* *
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
@ -62,31 +59,35 @@ package org.apache.commons.collections;
import java.util.Map; import java.util.Map;
/** A default implementation of {@link java.util.Map.Entry} /**
* * A default implementation of {@link java.util.Map.Entry}
* @since 1.0 *
* @author <a href="mailto:jstrachan@apache.org">James Strachan</a> * @since Commons Collections 1.0
* @author <a href="mailto:mas@apache.org">Michael A. Smith</a> * @version $Revision: 1.9 $ $Date: 2003/05/16 14:24:54 $
*/ *
* @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
* @author <a href="mailto:mas@apache.org">Michael A. Smith</a>
*/
public class DefaultMapEntry implements Map.Entry { public class DefaultMapEntry implements Map.Entry {
/** The key */
private Object key; private Object key;
/** The value */
private Object value; private Object value;
/** /**
* Constructs a new <Code>DefaultMapEntry</Code> with a null key * Constructs a new <Code>DefaultMapEntry</Code> with a null key
* and null value. * and null value.
*/ */
public DefaultMapEntry() { public DefaultMapEntry() {
} }
/** /**
* Constructs a new <Code>DefaultMapEntry</Code> with the given * Constructs a new <Code>DefaultMapEntry</Code> with the given
* key and given value. * key and given value.
* *
* @param key the key for the entry, may be null * @param key the key for the entry, may be null
* @param value the value for the entyr, may be null * @param value the value for the entyr, may be null
*/ */
public DefaultMapEntry(Object key, Object value) { public DefaultMapEntry(Object key, Object value) {
this.key = key; this.key = key;
@ -94,9 +95,9 @@ public class DefaultMapEntry implements Map.Entry {
} }
/** /**
* Implemented per API documentation of * Implemented per API documentation of
* {@link java.util.Map.Entry#equals(Object)} * {@link java.util.Map.Entry#equals(Object)}
**/ */
public boolean equals(Object o) { public boolean equals(Object o) {
if( o == null ) return false; if( o == null ) return false;
if( o == this ) return true; if( o == this ) return true;
@ -112,9 +113,9 @@ public class DefaultMapEntry implements Map.Entry {
/** /**
* Implemented per API documentation of * Implemented per API documentation of
* {@link java.util.Map.Entry#hashCode()} * {@link java.util.Map.Entry#hashCode()}
**/ */
public int hashCode() { public int hashCode() {
return ( ( getKey() == null ? 0 : getKey().hashCode() ) ^ return ( ( getKey() == null ? 0 : getKey().hashCode() ) ^
( getValue() == null ? 0 : getValue().hashCode() ) ); ( getValue() == null ? 0 : getValue().hashCode() ) );
@ -126,19 +127,18 @@ public class DefaultMapEntry implements Map.Entry {
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
/** /**
* Returns the key. * Returns the key.
* *
* @return the key * @return the key
*/ */
public Object getKey() { public Object getKey() {
return key; return key;
} }
/** /**
* Returns the value. * Returns the value.
* *
* @return the value * @return the value
*/ */
public Object getValue() { public Object getValue() {
return value; return value;
@ -148,20 +148,21 @@ public class DefaultMapEntry implements Map.Entry {
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
/** /**
* Sets the key. This method does not modify any map. * Sets the key. This method does not modify any map.
* *
* @param key the new key * @param key the new key
*/ */
public void setKey(Object key) { public void setKey(Object key) {
this.key = key; this.key = key;
} }
/** Note that this method only sets the local reference inside this object and /**
* does not modify the original Map. * Note that this method only sets the local reference inside this object and
* * does not modify the original Map.
* @return the old value of the value *
* @param value the new value * @return the old value of the value
*/ * @param value the new value
*/
public Object setValue(Object value) { public Object setValue(Object value) {
Object answer = this.value; Object answer = this.value;
this.value = value; this.value = value;

View File

@ -1,13 +1,10 @@
/* /*
* $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/DoubleOrderedMap.java,v 1.3 2002/10/12 22:15:18 scolebourne Exp $ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/DoubleOrderedMap.java,v 1.4 2003/05/16 14:24:54 scolebourne Exp $
* $Revision: 1.3 $
* $Date: 2002/10/12 22:15:18 $
*
* ==================================================================== * ====================================================================
* *
* The Apache Software License, Version 1.1 * The Apache Software License, Version 1.1
* *
* Copyright (c) 1999-2002 The Apache Software Foundation. All rights * Copyright (c) 2002-2003 The Apache Software Foundation. All rights
* reserved. * reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -23,11 +20,11 @@
* distribution. * distribution.
* *
* 3. The end-user documentation included with the redistribution, if * 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement: * any, must include the following acknowledgment:
* "This product includes software developed by the * "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)." * Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself, * Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear. * if and wherever such third-party acknowledgments normally appear.
* *
* 4. The names "The Jakarta Project", "Commons", and "Apache Software * 4. The names "The Jakarta Project", "Commons", and "Apache Software
* Foundation" must not be used to endorse or promote products derived * Foundation" must not be used to endorse or promote products derived
@ -36,7 +33,7 @@
* *
* 5. Products derived from this software may not be called "Apache" * 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written * nor may "Apache" appear in their names without prior written
* permission of the Apache Group. * permission of the Apache Software Foundation.
* *
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
@ -58,11 +55,8 @@
* <http://www.apache.org/>. * <http://www.apache.org/>.
* *
*/ */
package org.apache.commons.collections; package org.apache.commons.collections;
import java.util.AbstractCollection; import java.util.AbstractCollection;
import java.util.AbstractMap; import java.util.AbstractMap;
import java.util.AbstractSet; import java.util.AbstractSet;
@ -73,105 +67,99 @@ import java.util.Map;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Set; import java.util.Set;
/** /**
* Red-Black tree-based implementation of Map. This class guarantees * Red-Black tree-based implementation of Map. This class guarantees
* that the map will be in both ascending key order and ascending * that the map will be in both ascending key order and ascending
* value order, sorted according to the natural order for the key's * value order, sorted according to the natural order for the key's
* and value's classes.<p> * and value's classes.<p>
* *
* This Map is intended for applications that need to be able to look * This Map is intended for applications that need to be able to look
* up a key-value pairing by either key or value, and need to do so * up a key-value pairing by either key or value, and need to do so
* with equal efficiency.<p> * with equal efficiency.<p>
* *
* While that goal could be accomplished by taking a pair of TreeMaps * While that goal could be accomplished by taking a pair of TreeMaps
* and redirecting requests to the appropriate TreeMap (e.g., * and redirecting requests to the appropriate TreeMap (e.g.,
* containsKey would be directed to the TreeMap that maps values to * containsKey would be directed to the TreeMap that maps values to
* keys, containsValue would be directed to the TreeMap that maps keys * keys, containsValue would be directed to the TreeMap that maps keys
* to values), there are problems with that implementation, * to values), there are problems with that implementation,
* particularly when trying to keep the two TreeMaps synchronized with * particularly when trying to keep the two TreeMaps synchronized with
* each other. And if the data contained in the TreeMaps is large, the * each other. And if the data contained in the TreeMaps is large, the
* cost of redundant storage becomes significant.<p> * cost of redundant storage becomes significant.<p>
* *
* This solution keeps the data properly synchronized and minimizes * This solution keeps the data properly synchronized and minimizes
* the data storage. The red-black algorithm is based on TreeMap's, * the data storage. The red-black algorithm is based on TreeMap's,
* but has been modified to simultaneously map a tree node by key and * but has been modified to simultaneously map a tree node by key and
* by value. This doubles the cost of put operations (but so does * by value. This doubles the cost of put operations (but so does
* using two TreeMaps), and nearly doubles the cost of remove * using two TreeMaps), and nearly doubles the cost of remove
* operations (there is a savings in that the lookup of the node to be * operations (there is a savings in that the lookup of the node to be
* removed only has to be performed once). And since only one node * removed only has to be performed once). And since only one node
* contains the key and value, storage is significantly less than that * contains the key and value, storage is significantly less than that
* required by two TreeMaps.<p> * required by two TreeMaps.<p>
* *
* There are some limitations placed on data kept in this Map. The * There are some limitations placed on data kept in this Map. The
* biggest one is this:<p> * biggest one is this:<p>
* *
* When performing a put operation, neither the key nor the value may * When performing a put operation, neither the key nor the value may
* already exist in the Map. In the java.util Map implementations * already exist in the Map. In the java.util Map implementations
* (HashMap, TreeMap), you can perform a put with an already mapped * (HashMap, TreeMap), you can perform a put with an already mapped
* key, and neither cares about duplicate values at all ... but this * key, and neither cares about duplicate values at all ... but this
* implementation's put method with throw an IllegalArgumentException * implementation's put method with throw an IllegalArgumentException
* if either the key or the value is already in the Map.<p> * if either the key or the value is already in the Map.<p>
* *
* Obviously, that same restriction (and consequence of failing to * Obviously, that same restriction (and consequence of failing to
* heed that restriction) applies to the putAll method.<p> * heed that restriction) applies to the putAll method.<p>
* *
* The Map.Entry instances returned by the appropriate methods will * The Map.Entry instances returned by the appropriate methods will
* not allow setValue() and will throw an * not allow setValue() and will throw an
* UnsupportedOperationException on attempts to call that method.<p> * UnsupportedOperationException on attempts to call that method.<p>
* *
* New methods are added to take advantage of the fact that values are * New methods are added to take advantage of the fact that values are
* kept sorted independently of their keys:<p> * kept sorted independently of their keys:<p>
* *
* Object getKeyForValue(Object value) is the opposite of get; it * Object getKeyForValue(Object value) is the opposite of get; it
* takes a value and returns its key, if any.<p> * takes a value and returns its key, if any.<p>
* *
* Object removeValue(Object value) finds and removes the specified * Object removeValue(Object value) finds and removes the specified
* value and returns the now un-used key.<p> * value and returns the now un-used key.<p>
* *
* Set entrySetByValue() returns the Map.Entry's in a Set whose * Set entrySetByValue() returns the Map.Entry's in a Set whose
* iterator will iterate over the Map.Entry's in ascending order by * iterator will iterate over the Map.Entry's in ascending order by
* their corresponding values.<p> * their corresponding values.<p>
* *
* Set keySetByValue() returns the keys in a Set whose iterator will * Set keySetByValue() returns the keys in a Set whose iterator will
* iterate over the keys in ascending order by their corresponding * iterate over the keys in ascending order by their corresponding
* values.<p> * values.<p>
* *
* Collection valuesByValue() returns the values in a Collection whose * Collection valuesByValue() returns the values in a Collection whose
* iterator will iterate over the values in ascending order.<p> * iterator will iterate over the values in ascending order.<p>
* *
* @since 2.0 * @since Commons Collections 2.0
* @author Marc Johnson (marcj at users dot sourceforge dot net) * @version $Revision: 1.4 $ $Date: 2003/05/16 14:24:54 $
*/ *
* @author Marc Johnson (marcj at users dot sourceforge dot net)
// final for performance */
public final class DoubleOrderedMap extends AbstractMap { public final class DoubleOrderedMap extends AbstractMap {
// final for performance
private Node[] rootNode = new Node[]{ null, private static final int KEY = 0;
null }; private static final int VALUE = 1;
private int nodeCount = 0; private static final int SUM_OF_INDICES = KEY + VALUE;
private int modifications = 0; private static final int FIRST_INDEX = 0;
private Set[] setOfKeys = new Set[]{ null, private static final int NUMBER_OF_INDICES = 2;
null }; private static final String[] dataName = new String[] { "key", "value" };
private Set[] setOfEntries = new Set[]{ null,
null };
private Collection[] collectionOfValues = new Collection[]{
null,
null }; private Node[] rootNode = new Node[] { null, null };
private static final int KEY = 0; private int nodeCount = 0;
private static final int VALUE = 1; private int modifications = 0;
private static final int SUM_OF_INDICES = KEY + VALUE; private Set[] setOfKeys = new Set[] { null, null };
private static final int FIRST_INDEX = 0; private Set[] setOfEntries = new Set[] { null, null };
private static final int NUMBER_OF_INDICES = 2; private Collection[] collectionOfValues = new Collection[] { null, null };
private static final String[] dataName = new String[]{ "key",
"value"
};
/** /**
* Construct a new DoubleOrderedMap * Construct a new DoubleOrderedMap
*/ */
public DoubleOrderedMap() {} public DoubleOrderedMap() {
}
/** /**
* Constructs a new DoubleOrderedMap from an existing Map, with keys and * Constructs a new DoubleOrderedMap from an existing Map, with keys and
@ -179,14 +167,14 @@ null };
* *
* @param map the map whose mappings are to be placed in this map. * @param map the map whose mappings are to be placed in this map.
* *
* @exception ClassCastException if the keys in the map are not * @throws ClassCastException if the keys in the map are not
* Comparable, or are not mutually * Comparable, or are not mutually
* comparable; also if the values in * comparable; also if the values in
* the map are not Comparable, or * the map are not Comparable, or
* are not mutually Comparable * are not mutually Comparable
* @exception NullPointerException if any key or value in the map * @throws NullPointerException if any key or value in the map
* is null * is null
* @exception IllegalArgumentException if there are duplicate keys * @throws IllegalArgumentException if there are duplicate keys
* or duplicate values in the * or duplicate values in the
* map * map
*/ */
@ -205,9 +193,9 @@ null };
* @return the key to which this map maps the specified value, or * @return the key to which this map maps the specified value, or
* null if the map contains no mapping for this value. * null if the map contains no mapping for this value.
* *
* @exception ClassCastException if the value is of an * @throws ClassCastException if the value is of an
* inappropriate type for this map. * inappropriate type for this map.
* @exception NullPointerException if the value is null * @throws NullPointerException if the value is null
*/ */
public Object getKeyForValue(final Object value) public Object getKeyForValue(final Object value)
throws ClassCastException, NullPointerException { throws ClassCastException, NullPointerException {
@ -847,8 +835,7 @@ null };
* @param insertedNode the node to be inserted * @param insertedNode the node to be inserted
* @param index KEY or VALUE * @param index KEY or VALUE
*/ */
private void doRedBlackInsert(final Node insertedNode, final int index) private void doRedBlackInsert(final Node insertedNode, final int index) {
{
Node currentNode = insertedNode; Node currentNode = insertedNode;
@ -904,8 +891,7 @@ null };
makeRed(getGrandParent(currentNode, index), index); makeRed(getGrandParent(currentNode, index), index);
if (getGrandParent(currentNode, index) != null) { if (getGrandParent(currentNode, index) != null) {
rotateLeft(getGrandParent(currentNode, index), rotateLeft(getGrandParent(currentNode, index), index);
index);
} }
} }
} }
@ -943,11 +929,9 @@ index);
rootNode[index] = replacement; rootNode[index] = replacement;
} else if (deletedNode } else if (deletedNode
== deletedNode.getParent(index).getLeft(index)) { == deletedNode.getParent(index).getLeft(index)) {
deletedNode.getParent(index).setLeft(replacement, deletedNode.getParent(index).setLeft(replacement, index);
index);
} else { } else {
deletedNode.getParent(index).setRight(replacement, deletedNode.getParent(index).setRight(replacement, index);
index);
} }
deletedNode.setLeft(null, index); deletedNode.setLeft(null, index);
@ -975,8 +959,7 @@ index);
if (deletedNode if (deletedNode
== deletedNode.getParent(index) == deletedNode.getParent(index)
.getLeft(index)) { .getLeft(index)) {
deletedNode.getParent(index).setLeft(null, deletedNode.getParent(index).setLeft(null, index);
index);
} else { } else {
deletedNode.getParent(index).setRight(null, deletedNode.getParent(index).setRight(null,
index); index);
@ -1016,9 +999,7 @@ index);
makeRed(getParent(currentNode, index), index); makeRed(getParent(currentNode, index), index);
rotateLeft(getParent(currentNode, index), index); rotateLeft(getParent(currentNode, index), index);
siblingNode = getRightChild(getParent(currentNode, siblingNode = getRightChild(getParent(currentNode, index), index);
index),
index);
} }
if (isBlack(getLeftChild(siblingNode, index), index) if (isBlack(getLeftChild(siblingNode, index), index)
@ -1034,8 +1015,7 @@ index),
rotateRight(siblingNode, index); rotateRight(siblingNode, index);
siblingNode = siblingNode =
getRightChild(getParent(currentNode, index), getRightChild(getParent(currentNode, index), index);
index);
} }
copyColor(getParent(currentNode, index), siblingNode, copyColor(getParent(currentNode, index), siblingNode,
@ -1047,23 +1027,18 @@ index),
currentNode = rootNode[index]; currentNode = rootNode[index];
} }
} else { } else {
Node siblingNode = getLeftChild(getParent(currentNode, Node siblingNode = getLeftChild(getParent(currentNode, index), index);
index),
index);
if (isRed(siblingNode, index)) { if (isRed(siblingNode, index)) {
makeBlack(siblingNode, index); makeBlack(siblingNode, index);
makeRed(getParent(currentNode, index), index); makeRed(getParent(currentNode, index), index);
rotateRight(getParent(currentNode, index), index); rotateRight(getParent(currentNode, index), index);
siblingNode = getLeftChild(getParent(currentNode, siblingNode = getLeftChild(getParent(currentNode, index), index);
index),
index);
} }
if (isBlack(getRightChild(siblingNode, index), index) if (isBlack(getRightChild(siblingNode, index), index)
&& isBlack(getLeftChild(siblingNode, index), index)) && isBlack(getLeftChild(siblingNode, index), index)) {
{
makeRed(siblingNode, index); makeRed(siblingNode, index);
currentNode = getParent(currentNode, index); currentNode = getParent(currentNode, index);
@ -1074,8 +1049,7 @@ index),
rotateLeft(siblingNode, index); rotateLeft(siblingNode, index);
siblingNode = siblingNode =
getLeftChild(getParent(currentNode, index), getLeftChild(getParent(currentNode, index), index);
index);
} }
copyColor(getParent(currentNode, index), siblingNode, copyColor(getParent(currentNode, index), siblingNode,
@ -1203,8 +1177,8 @@ index),
* @param index KEY or VALUE (used to put the right word in the * @param index KEY or VALUE (used to put the right word in the
* exception message) * exception message)
* *
* @exception NullPointerException if o is null * @throws NullPointerException if o is null
* @exception ClassCastException if o is not Comparable * @throws ClassCastException if o is not Comparable
*/ */
private static void checkNonNullComparable(final Object o, private static void checkNonNullComparable(final Object o,
final int index) { final int index) {
@ -1225,8 +1199,8 @@ index),
* *
* @param key the key to be checked * @param key the key to be checked
* *
* @exception NullPointerException if key is null * @throws NullPointerException if key is null
* @exception ClassCastException if key is not Comparable * @throws ClassCastException if key is not Comparable
*/ */
private static void checkKey(final Object key) { private static void checkKey(final Object key) {
checkNonNullComparable(key, KEY); checkNonNullComparable(key, KEY);
@ -1237,8 +1211,8 @@ index),
* *
* @param value the value to be checked * @param value the value to be checked
* *
* @exception NullPointerException if value is null * @throws NullPointerException if value is null
* @exception ClassCastException if value is not Comparable * @throws ClassCastException if value is not Comparable
*/ */
private static void checkValue(final Object value) { private static void checkValue(final Object value) {
checkNonNullComparable(value, VALUE); checkNonNullComparable(value, VALUE);
@ -1251,8 +1225,8 @@ index),
* @param key the key to be checked * @param key the key to be checked
* @param value the value to be checked * @param value the value to be checked
* *
* @exception NullPointerException if key or value is null * @throws NullPointerException if key or value is null
* @exception ClassCastException if key or value is not Comparable * @throws ClassCastException if key or value is not Comparable
*/ */
private static void checkKeyAndValue(final Object key, private static void checkKeyAndValue(final Object key,
final Object value) { final Object value) {
@ -1294,7 +1268,7 @@ index),
* *
* @param newNode the node to be inserted * @param newNode the node to be inserted
* *
* @exception IllegalArgumentException if the node already exists * @throws IllegalArgumentException if the node already exists
* in the value mapping * in the value mapping
*/ */
private void insertValue(final Node newNode) private void insertValue(final Node newNode)
@ -1355,9 +1329,9 @@ index),
* @return true if this map contains a mapping for the specified * @return true if this map contains a mapping for the specified
* key. * key.
* *
* @exception ClassCastException if the key is of an inappropriate * @throws ClassCastException if the key is of an inappropriate
* type for this map. * type for this map.
* @exception NullPointerException if the key is null * @throws NullPointerException if the key is null
*/ */
public boolean containsKey(final Object key) public boolean containsKey(final Object key)
throws ClassCastException, NullPointerException { throws ClassCastException, NullPointerException {
@ -1392,9 +1366,9 @@ index),
* @return the value to which this map maps the specified key, or * @return the value to which this map maps the specified key, or
* null if the map contains no mapping for this key. * null if the map contains no mapping for this key.
* *
* @exception ClassCastException if the key is of an inappropriate * @throws ClassCastException if the key is of an inappropriate
* type for this map. * type for this map.
* @exception NullPointerException if the key is null * @throws NullPointerException if the key is null
*/ */
public Object get(final Object key) public Object get(final Object key)
throws ClassCastException, NullPointerException { throws ClassCastException, NullPointerException {
@ -1411,12 +1385,12 @@ index),
* *
* @return null * @return null
* *
* @exception ClassCastException if the class of the specified key * @throws ClassCastException if the class of the specified key
* or value prevents it from being * or value prevents it from being
* stored in this map. * stored in this map.
* @exception NullPointerException if the specified key or value * @throws NullPointerException if the specified key or value
* is null * is null
* @exception IllegalArgumentException if the key duplicates an * @throws IllegalArgumentException if the key duplicates an
* existing key, or if the * existing key, or if the
* value duplicates an * value duplicates an
* existing value * existing value
@ -1680,8 +1654,7 @@ index),
Node node = lookup((Comparable) entry.getKey(), Node node = lookup((Comparable) entry.getKey(),
KEY); KEY);
if ((node != null) && node.getData(VALUE).equals(value)) if ((node != null) && node.getData(VALUE).equals(value)) {
{
doRedBlackDelete(node); doRedBlackDelete(node);
return true; return true;
@ -1743,9 +1716,9 @@ index),
/** /**
* @return the next element in the iteration. * @return the next element in the iteration.
* *
* @exception NoSuchElementException if iteration has no more * @throws NoSuchElementException if iteration has no more
* elements. * elements.
* @exception ConcurrentModificationException if the * @throws ConcurrentModificationException if the
* DoubleOrderedMap is * DoubleOrderedMap is
* modified behind * modified behind
* the iterator's * the iterator's
@ -1777,12 +1750,12 @@ index),
* the iteration is in progress in any way other than by * the iteration is in progress in any way other than by
* calling this method. * calling this method.
* *
* @exception IllegalStateException if the next method has not * @throws IllegalStateException if the next method has not
* yet been called, or the * yet been called, or the
* remove method has already * remove method has already
* been called after the last * been called after the last
* call to the next method. * call to the next method.
* @exception ConcurrentModificationException if the * @throws ConcurrentModificationException if the
* DoubleOrderedMap is * DoubleOrderedMap is
* modified behind * modified behind
* the iterator's * the iterator's
@ -2000,7 +1973,7 @@ index),
* *
* @return does not return * @return does not return
* *
* @exception UnsupportedOperationException * @throws UnsupportedOperationException
*/ */
public Object setValue(Object ignored) public Object setValue(Object ignored)
throws UnsupportedOperationException { throws UnsupportedOperationException {