diff --git a/data/test/HashedMap.emptyCollection.version3.obj b/data/test/HashedMap.emptyCollection.version3.obj index bda164e44..f89ef0ca6 100644 Binary files a/data/test/HashedMap.emptyCollection.version3.obj and b/data/test/HashedMap.emptyCollection.version3.obj differ diff --git a/data/test/HashedMap.fullCollection.version3.obj b/data/test/HashedMap.fullCollection.version3.obj index f542b20fd..7ec98a6ed 100644 Binary files a/data/test/HashedMap.fullCollection.version3.obj and b/data/test/HashedMap.fullCollection.version3.obj differ diff --git a/data/test/IdentityMap.emptyCollection.version3.obj b/data/test/IdentityMap.emptyCollection.version3.obj index fdeb41c8c..52731116a 100644 Binary files a/data/test/IdentityMap.emptyCollection.version3.obj and b/data/test/IdentityMap.emptyCollection.version3.obj differ diff --git a/data/test/IdentityMap.fullCollection.version3.obj b/data/test/IdentityMap.fullCollection.version3.obj index 981b87192..c24840231 100644 Binary files a/data/test/IdentityMap.fullCollection.version3.obj and b/data/test/IdentityMap.fullCollection.version3.obj differ diff --git a/data/test/LRUMap.emptyCollection.version3.obj b/data/test/LRUMap.emptyCollection.version3.obj new file mode 100644 index 000000000..59b17b460 Binary files /dev/null and b/data/test/LRUMap.emptyCollection.version3.obj differ diff --git a/data/test/LRUMap.fullCollection.version3.obj b/data/test/LRUMap.fullCollection.version3.obj new file mode 100644 index 000000000..f311f04e5 Binary files /dev/null and b/data/test/LRUMap.fullCollection.version3.obj differ diff --git a/data/test/LinkedMap.emptyCollection.version3.obj b/data/test/LinkedMap.emptyCollection.version3.obj index 8da49d7b4..cab0aeedc 100644 Binary files a/data/test/LinkedMap.emptyCollection.version3.obj and b/data/test/LinkedMap.emptyCollection.version3.obj differ diff --git a/data/test/LinkedMap.fullCollection.version3.obj b/data/test/LinkedMap.fullCollection.version3.obj index adc6a4517..939b729d6 100644 Binary files a/data/test/LinkedMap.fullCollection.version3.obj and b/data/test/LinkedMap.fullCollection.version3.obj differ diff --git a/src/java/org/apache/commons/collections/map/HashedMap.java b/src/java/org/apache/commons/collections/map/HashedMap.java index 56e150120..732db7134 100644 --- a/src/java/org/apache/commons/collections/map/HashedMap.java +++ b/src/java/org/apache/commons/collections/map/HashedMap.java @@ -1,5 +1,5 @@ /* - * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/map/HashedMap.java,v 1.7 2003/12/06 14:02:11 scolebourne Exp $ + * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/map/HashedMap.java,v 1.8 2003/12/07 01:23:54 scolebourne Exp $ * ==================================================================== * * The Apache Software License, Version 1.1 @@ -87,7 +87,7 @@ import org.apache.commons.collections.MapIterator; * methods exposed. * * @since Commons Collections 3.0 - * @version $Revision: 1.7 $ $Date: 2003/12/06 14:02:11 $ + * @version $Revision: 1.8 $ $Date: 2003/12/07 01:23:54 $ * * @author java util HashMap * @author Stephen Colebourne @@ -113,7 +113,7 @@ public class HashedMap implements IterableMap, Serializable, Cloneable { protected static final Object NULL = new Object(); /** Load factor, normally 0.75 */ - protected final float loadFactor; + protected transient float loadFactor; /** The size of the map */ protected transient int size; /** Map entries */ @@ -157,14 +157,14 @@ public class HashedMap implements IterableMap, Serializable, Cloneable { * @param initialCapacity the initial capacity * @param loadFactor the load factor * @throws IllegalArgumentException if the initial capacity is less than one - * @throws IllegalArgumentException if the load factor is less than one + * @throws IllegalArgumentException if the load factor is less than zero */ public HashedMap(int initialCapacity, float loadFactor) { super(); if (initialCapacity < 1) { throw new IllegalArgumentException("Initial capacity must be greater than 0"); } - if (loadFactor <= 0 || Float.isNaN(loadFactor)) { + if (loadFactor <= 0.0f || Float.isNaN(loadFactor)) { throw new IllegalArgumentException("Load factor must be greater than 0"); } this.loadFactor = loadFactor; @@ -296,14 +296,13 @@ public class HashedMap implements IterableMap, Serializable, Cloneable { while (entry != null) { if (entry.hashCode == hashCode && isEqualKey(key, entry.key)) { Object oldValue = entry.getValue(); - entry.setValue(value); + updateEntry(entry, value); return oldValue; } entry = entry.next; } - modCount++; - add(hashCode, index, key, value); + addMapping(index, hashCode, key, value); return null; } @@ -335,18 +334,13 @@ public class HashedMap implements IterableMap, Serializable, Cloneable { key = convertKey(key); int hashCode = hash(key); int index = hashIndex(hashCode, data.length); - HashEntry entry = data[index]; + HashEntry entry = data[index]; HashEntry previous = null; while (entry != null) { if (entry.hashCode == hashCode && isEqualKey(key, entry.key)) { - modCount++; - if (previous == null) { - data[index] = entry.next; - } else { - previous.next = entry.next; - } - size--; - return destroyEntry(entry); + Object oldValue = entry.getValue(); + removeMapping(entry, index, previous); + return oldValue; } previous = entry; entry = entry.next; @@ -368,25 +362,6 @@ public class HashedMap implements IterableMap, Serializable, Cloneable { } //----------------------------------------------------------------------- - /** - * Gets the entry mapped to the key specified. - * - * @param key the key - * @return the entry, null if no match - */ - protected HashEntry getEntry(Object key) { - key = convertKey(key); - int hashCode = hash(key); - HashEntry entry = data[hashIndex(hashCode, data.length)]; // no local for hash index - while (entry != null) { - if (entry.hashCode == hashCode && isEqualKey(key, entry.key)) { - return entry; - } - entry = entry.next; - } - return null; - } - /** * Converts input keys to another object for storage in the map. * This implementation masks nulls. @@ -459,9 +434,91 @@ public class HashedMap implements IterableMap, Serializable, Cloneable { return hashCode & (dataSize - 1); } + //----------------------------------------------------------------------- /** - * Creates an entry to store the data. - * This implementation creates a HashEntry instance. + * Gets the entry mapped to the key specified. + *
+ * This method exists for subclasses that may need to perform a multi-step + * process accessing the entry. The public methods in this class don't use this + * method to gain a small performance boost. + * + * @param key the key + * @return the entry, null if no match + */ + protected HashEntry getEntry(Object key) { + key = convertKey(key); + int hashCode = hash(key); + HashEntry entry = data[hashIndex(hashCode, data.length)]; // no local for hash index + while (entry != null) { + if (entry.hashCode == hashCode && isEqualKey(key, entry.key)) { + return entry; + } + entry = entry.next; + } + return null; + } + + //----------------------------------------------------------------------- + /** + * Updates an existing key-value mapping to change the value. + *
+ * This implementation calls setValue()
on the entry.
+ * Subclasses could override to handle changes to the map.
+ *
+ * @param entry the entry to update
+ * @param newValue the new value to store
+ * @return value the previous value
+ */
+ protected void updateEntry(HashEntry entry, Object newValue) {
+ entry.setValue(newValue);
+ }
+
+ /**
+ * Reuses an existing key-value mapping, storing completely new data.
+ *
+ * This implementation sets all the data fields on the entry. + * Subclasses could populate additional entry fields. + * + * @param entry the entry to update, not null + * @param hashIndex the index in the data array + * @param hashCode the hash code of the key to add + * @param key the key to add + * @param value the value to add + */ + protected void reuseEntry(HashEntry entry, int hashIndex, int hashCode, Object key, Object value) { + entry.next = data[hashIndex]; + entry.hashCode = hashCode; + entry.key = key; + entry.value = value; + } + + //----------------------------------------------------------------------- + /** + * Adds a new key-value mapping into this map. + *
+ * This implementation calls createEntry()
, addEntry()
+ * and checkCapacity
.
+ * It also handles changes to modCount
and size
.
+ * Subclasses could override to fully control adds to the map.
+ *
+ * @param hashIndex the index into the data array to store at
+ * @param hashCode the hash code of the key to add
+ * @param key the key to add
+ * @param value the value to add
+ * @return the value previously mapped to this key, null if none
+ */
+ protected void addMapping(int hashIndex, int hashCode, Object key, Object value) {
+ modCount++;
+ HashEntry entry = createEntry(data[hashIndex], hashCode, key, value);
+ addEntry(entry, hashIndex);
+ size++;
+ checkCapacity();
+ }
+
+ /**
+ * Creates an entry to store the key-value data.
+ *
+ * This implementation creates a new HashEntry instance. * Subclasses can override this to return a different storage class, * or implement caching. * @@ -476,29 +533,81 @@ public class HashedMap implements IterableMap, Serializable, Cloneable { } /** - * Kills an entry ready for the garbage collector. - * This implementation prepares the HashEntry for garbage collection. - * Subclasses can override this to implement caching (override clear as well). - * - * @param entry the entry to destroy - * @return the value from the entry - */ - protected Object destroyEntry(HashEntry entry) { - entry.next = null; - return entry.value; - } - - /** - * Adds a new key-value mapping into this map. - * Subclasses could override to fix the size of the map. + * Adds an entry into this map. + *
+ * This implementation adds the entry to the data storage table. + * Subclasses could override to handle changes to the map. * + * @param hashIndex the index into the data array to store at + * @param hashCode the hash code of the key to add * @param key the key to add * @param value the value to add * @return the value previously mapped to this key, null if none */ - protected void add(int hashCode, int hashIndex, Object key, Object value) { - data[hashIndex] = createEntry(data[hashIndex], hashCode, key, value); - if (size++ >= threshold) { + protected void addEntry(HashEntry entry, int hashIndex) { + data[hashIndex] = entry; + } + + //----------------------------------------------------------------------- + /** + * Removes a mapping from the map. + *
+ * This implementation calls removeEntry()
and destroyEntry()
.
+ * It also handles changes to modCount
and size
.
+ * Subclasses could override to fully control removals from the map.
+ *
+ * @param entry the entry to remove
+ * @param hashIndex the index into the data structure
+ * @param previous the previous entry in the chain
+ */
+ protected void removeMapping(HashEntry entry, int hashIndex, HashEntry previous) {
+ modCount++;
+ removeEntry(entry, hashIndex, previous);
+ size--;
+ destroyEntry(entry);
+ }
+
+ /**
+ * Removes an entry from the chain stored in a particular index.
+ *
+ * This implementation removes the entry from the data storage table. + * The size is not updated. + * Subclasses could override to handle changes to the map. + * + * @param entry the entry to remove + * @param hashIndex the index into the data structure + * @param previous the previous entry in the chain + */ + protected void removeEntry(HashEntry entry, int hashIndex, HashEntry previous) { + if (previous == null) { + data[hashIndex] = entry.next; + } else { + previous.next = entry.next; + } + } + + /** + * Kills an entry ready for the garbage collector. + *
+ * This implementation prepares the HashEntry for garbage collection. + * Subclasses can override this to implement caching (override clear as well). + * + * @param entry the entry to destroy + */ + protected void destroyEntry(HashEntry entry) { + entry.next = null; + entry.key = null; + entry.value = null; + } + + //----------------------------------------------------------------------- + /** + * Checks the capacity of the map and enlarges it if necessary. + *
+ * This implementation uses the threshold to check if the map needs enlarging + */ + protected void checkCapacity() { + if (size >= threshold) { ensureCapacity(data.length * 2); } } @@ -874,17 +983,21 @@ public class HashedMap implements IterableMap, Serializable, Cloneable { this.key = key; this.value = value; } + public Object getKey() { return (key == NULL ? null : key); } + public Object getValue() { return value; } + public Object setValue(Object value) { Object old = this.value; this.value = value; return old; } + public boolean equals(Object obj) { if (obj == this) { return true; @@ -897,10 +1010,12 @@ public class HashedMap implements IterableMap, Serializable, Cloneable { (getKey() == null ? other.getKey() == null : getKey().equals(other.getKey())) && (getValue() == null ? other.getValue() == null : getValue().equals(other.getValue())); } + public int hashCode() { return (getKey() == null ? 0 : getKey().hashCode()) ^ (getValue() == null ? 0 : getValue().hashCode()); } + public String toString() { return new StringBuffer().append(getKey()).append('=').append(getValue()).toString(); } @@ -980,13 +1095,49 @@ public class HashedMap implements IterableMap, Serializable, Cloneable { } //----------------------------------------------------------------------- + /** + * Write the data of subclasses. + *
+ * Serialization is not one of the JDK's nicest topics. Normal serialization will + * not load subclass fields until this object is setup. In other words the readObject + * on this class is performed before that on the subclass. Unfortunately, data setup in + * the subclass may affect the ability of methods such as put() to work properly. + *
+ * The solution adopted here is to have a method called by this class as part of the + * serialization and deserialization process. Override this method if the subclass + * stores additional data needed for put() to work. + * A sub-subclass must call super.doWriteObject(). + */ + protected void doWriteObject(ObjectOutputStream out) throws IOException { + // do nothing + } + + /** + * Read the data of subclasses. + *
+ * Serialization is not one of the JDK's nicest topics. Normal serialization will + * not load subclass fields until this object is setup. In other words the readObject + * on this class is performed before that on the subclass. Unfortunately, data setup in + * the subclass may affect the ability of methods such as put() to work properly. + *
+ * The solution adopted here is to have a method called by this class as part of the
+ * serialization and deserialization process. Override this method if the subclass
+ * stores additional data needed for put() to work.
+ * A sub-subclass must call super.doReadObject().
+ */
+ protected void doReadObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ // do nothing
+ }
+
/**
* Write the map out using a custom routine.
*/
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
+ out.writeFloat(loadFactor);
out.writeInt(data.length);
out.writeInt(size);
+ doWriteObject(out);
for (MapIterator it = mapIterator(); it.hasNext();) {
out.writeObject(it.next());
out.writeObject(it.getValue());
@@ -998,16 +1149,20 @@ public class HashedMap implements IterableMap, Serializable, Cloneable {
*/
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
+ loadFactor = in.readFloat();
int capacity = in.readInt();
int size = in.readInt();
- data = new HashEntry[capacity];
+ doReadObject(in);
init();
+ data = new HashEntry[capacity];
for (int i = 0; i < size; i++) {
Object key = in.readObject();
Object value = in.readObject();
put(key, value);
}
+ threshold = calculateThreshold(data.length, loadFactor);
}
+
//-----------------------------------------------------------------------
/**
* Clones the map without cloning the keys or values.
diff --git a/src/java/org/apache/commons/collections/map/LRUMap.java b/src/java/org/apache/commons/collections/map/LRUMap.java
new file mode 100644
index 000000000..30cd56f82
--- /dev/null
+++ b/src/java/org/apache/commons/collections/map/LRUMap.java
@@ -0,0 +1,327 @@
+/*
+ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/map/LRUMap.java,v 1.1 2003/12/07 01:23:54 scolebourne Exp $
+ * ====================================================================
+ *
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 2001-2003 The Apache Software Foundation. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ * any, must include the following acknowledgement:
+ * "This product includes software developed by the
+ * Apache Software Foundation (http://www.apache.org/)."
+ * Alternately, this acknowledgement may appear in the software itself,
+ * if and wherever such third-party acknowledgements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "Commons", and "Apache Software
+ * Foundation" must not be used to endorse or promote products derived
+ * from this software without prior written permission. For written
+ * permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ * nor may "Apache" appear in their names without prior written
+ * permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * Map
implementation with a fixed maximum size which removes
+ * the least recently used entry if an entry is added when full.
+ *
+ * The least recently used algorithm works on the get and put operations only. + * Iteration of any kind, including setting the value by iteration, does not + * change the order. Queries such as containsKey and containsValue or access + * via views also do not change the order. + *
+ * The map implements OrderedMap
and entries may be queried using
+ * the bidirectional OrderedMapIterator
. The order returned is
+ * most recently used to least recently used. Iterators from map views can
+ * also be cast to OrderedIterator
if required.
+ *
+ * All the available iterators can be reset back to the start by casting to
+ * ResettableIterator
and calling reset()
.
+ *
+ * NOTE: The order of the map has changed from the previous version located + * in the main collections package. The map is now ordered most recently used + * to least recently used. + * + * @since Commons Collections 3.0 + * @version $Revision: 1.1 $ $Date: 2003/12/07 01:23:54 $ + * + * @author James Strachan + * @author Morgan Delagrange + * @author Stephen Colebourne + */ +public class LRUMap extends LinkedMap implements OrderedMap { + + /** Serialisation version */ + static final long serialVersionUID = -2848625157350244215L; + /** Default maximum size */ + protected static final int DEFAULT_MAX_SIZE = 100; + + /** Maximum size */ + private transient int maxSize; + + /** + * Constructs a new empty map with a maximum size of 100. + */ + public LRUMap() { + this(DEFAULT_MAX_SIZE, DEFAULT_LOAD_FACTOR); + } + + /** + * Constructs a new, empty map with the specified maximum size. + * + * @param maxSize the maximum size of the map + * @throws IllegalArgumentException if the maximum size is less than one + */ + public LRUMap(int maxSize) { + this(maxSize, DEFAULT_LOAD_FACTOR); + } + + /** + * Constructs a new, empty map with the specified initial capacity and + * load factor. + * + * @param maxSize the maximum size of the map, -1 for no limit, + * @param loadFactor the load factor + * @throws IllegalArgumentException if the maximum size is less than one + * @throws IllegalArgumentException if the load factor is less than zero + */ + public LRUMap(int maxSize, float loadFactor) { + super((maxSize < 1 ? DEFAULT_CAPACITY : maxSize), loadFactor); + if (maxSize < 1) { + throw new IllegalArgumentException("LRUMap max size must be greater than 0"); + } + this.maxSize = maxSize; + } + + /** + * Constructor copying elements from another map. + *
+ * The maximum size is set from the map's size. + * + * @param map the map to copy + * @throws NullPointerException if the map is null + * @throws IllegalArgumentException if the map is empty + */ + public LRUMap(Map map) { + this(map.size(), DEFAULT_LOAD_FACTOR); + putAll(map); + } + + //----------------------------------------------------------------------- + /** + * Gets the value mapped to the key specified. + *
+ * This operation changes the position of the key in the map to the + * most recently used position (first). + * + * @param key the key + * @return the mapped value, null if no match + */ + public Object get(Object key) { + LinkEntry entry = (LinkEntry) getEntry(key); + if (entry == null) { + return null; + } + moveFirst(entry); + return entry.getValue(); + } + + //----------------------------------------------------------------------- + /** + * Updates an existing key-value mapping. + * This implementation moves the updated entry to the top of the list. + * + * @param entry the entry to update + * @param newValue the new value to store + * @return value the previous value + */ + protected void moveFirst(LinkEntry entry) { + if (entry.before != header) { + modCount++; + // remove + entry.after.before = entry.before; + entry.before.after = entry.after; + // add first + entry.before = header; + entry.after = header.after; + header.after.before = entry; + header.after = entry; + } + } + + /** + * Updates an existing key-value mapping. + * This implementation moves the updated entry to the top of the list. + * + * @param entry the entry to update + * @param newValue the new value to store + * @return value the previous value + */ + protected void updateEntry(HashEntry entry, Object newValue) { + moveFirst((LinkEntry) entry); // handles modCount + entry.setValue(newValue); + } + + /** + * Adds a new key-value mapping into this map. + * This implementation checks the LRU size and determines whether to + * discard an entry or not. + * + * @param hashIndex the index into the data array to store at + * @param hashCode the hash code of the key to add + * @param key the key to add + * @param value the value to add + * @return the value previously mapped to this key, null if none + */ + protected void addMapping(int hashIndex, int hashCode, Object key, Object value) { + if (size >= maxSize && removeLRU(header.before)) { + LinkEntry entry = header.before; + // remove from current location + int removeIndex = hashIndex(entry.hashCode, data.length); + HashEntry loop = data[removeIndex]; + HashEntry previous = null; + while (loop != entry) { + previous = loop; + loop = loop.next; + } + modCount++; + removeEntry(entry, removeIndex, previous); + reuseEntry(entry, hashIndex, hashCode, key, value); + addEntry(entry, hashIndex); + + } else { + super.addMapping(hashIndex, hashCode, key, value); + } + } + + /** + * Adds a new entry into this map using access order. + *
+ * This implementation adds the entry to the data storage table and + * to the start of the linked list. + * + * @param entry the entry to add + * @param hashIndex the index into the data array to store at + */ + protected void addEntry(HashEntry entry, int hashIndex) { + LinkEntry link = (LinkEntry) entry; + link.before = header; + link.after = header.after; + header.after.before = link; + header.after = link; + data[hashIndex] = entry; + } + + /** + * Subclass method to control removal of the least recently used entry from the map. + *
+ * This method exists for subclasses to override. A subclass may wish to + * provide cleanup of resources when an entry is removed. For example: + *
+ * protected boolean removeLRU(LinkEntry entry) { + * releaseResources(entry.getValue()); // release resources held by entry + * return true; // actually delete entry + * } + *+ *
+ * Alternatively, a subclass may choose to not remove the entry or selectively + * keep certain LRU entries. For example: + *
+ * protected boolean removeLRU(LinkEntry entry) { + * if (entry.getKey().toString().startsWith("System.")) { + * return false; // entry not removed from LRUMap + * } else { + * return true; // actually delete entry + * } + * } + *+ * Note that the effect of not removing an LRU is for the Map to exceed the maximum size. + * + * @param entry the entry to be removed + */ + protected boolean removeLRU(LinkEntry entry) { + return true; + } + + //----------------------------------------------------------------------- + /** + * Returns true if this map is full and no new mappings can be added. + * + * @return
true
if the map is full
+ */
+ public boolean isFull() {
+ return (size >= maxSize);
+ }
+
+ /**
+ * Gets the maximum size of the map (the bound).
+ *
+ * @return the maximum number of elements the map can hold
+ */
+ public int maxSize() {
+ return maxSize;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Write the data of subclasses.
+ * A sub-subclass must call super.doWriteObject().
+ */
+ protected void doWriteObject(ObjectOutputStream out) throws IOException {
+ out.writeInt(maxSize);
+ super.doWriteObject(out);
+ }
+
+ /**
+ * Read the data of subclasses.
+ * A sub-subclass must call super.doReadObject().
+ */
+ protected void doReadObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ maxSize = in.readInt();
+ super.doReadObject(in);
+ }
+
+}
diff --git a/src/java/org/apache/commons/collections/map/LinkedMap.java b/src/java/org/apache/commons/collections/map/LinkedMap.java
index d7fc852c1..e7ac66d52 100644
--- a/src/java/org/apache/commons/collections/map/LinkedMap.java
+++ b/src/java/org/apache/commons/collections/map/LinkedMap.java
@@ -1,5 +1,5 @@
/*
- * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/map/LinkedMap.java,v 1.2 2003/12/06 14:02:11 scolebourne Exp $
+ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/map/LinkedMap.java,v 1.3 2003/12/07 01:23:54 scolebourne Exp $
* ====================================================================
*
* The Apache Software License, Version 1.1
@@ -71,7 +71,8 @@ import org.apache.commons.collections.ResettableIterator;
/**
* A Map
implementation that maintains the order of the entries.
- * The order maintained is by insertion.
+ * In this implementation order is maintained is by original insertion, but
+ * subclasses may work differently.
* * This implementation improves on the JDK1.4 LinkedHashMap by adding the * {@link org.apache.commons.collections.iterators.MapIterator MapIterator} @@ -84,9 +85,12 @@ import org.apache.commons.collections.ResettableIterator; *
* All the available iterators can be reset back to the start by casting to
* ResettableIterator
and calling reset()
.
+ *
+ * The implementation is also designed to be subclassed, with lots of useful + * methods exposed. * * @since Commons Collections 3.0 - * @version $Revision: 1.2 $ $Date: 2003/12/06 14:02:11 $ + * @version $Revision: 1.3 $ $Date: 2003/12/07 01:23:54 $ * * @author java util LinkedHashMap * @author Stephen Colebourne @@ -97,7 +101,7 @@ public class LinkedMap extends HashedMap implements OrderedMap { static final long serialVersionUID = -1954063410665686469L; /** Header in the linked list */ - private transient LinkedEntry header; + protected transient LinkEntry header; /** * Constructs a new empty map with default size and load factor. @@ -123,7 +127,7 @@ public class LinkedMap extends HashedMap implements OrderedMap { * @param initialCapacity the initial capacity * @param loadFactor the load factor * @throws IllegalArgumentException if the initial capacity is less than one - * @throws IllegalArgumentException if the load factor is less than one + * @throws IllegalArgumentException if the load factor is less than zero */ public LinkedMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); @@ -143,7 +147,7 @@ public class LinkedMap extends HashedMap implements OrderedMap { * Initialise this subclass during construction. */ protected void init() { - header = new LinkedEntry(null, -1, null, null); + header = new LinkEntry(null, -1, null, null); header.before = header.after = header; } @@ -157,13 +161,13 @@ public class LinkedMap extends HashedMap implements OrderedMap { public boolean containsValue(Object value) { // override uses faster iterator if (value == null) { - for (LinkedEntry entry = header.after; entry != header; entry = entry.after) { + for (LinkEntry entry = header.after; entry != header; entry = entry.after) { if (entry.getValue() == null) { return true; } } } else { - for (LinkedEntry entry = header.after; entry != header; entry = entry.after) { + for (LinkEntry entry = header.after; entry != header; entry = entry.after) { if (isEqualValue(value, entry.getValue())) { return true; } @@ -214,7 +218,7 @@ public class LinkedMap extends HashedMap implements OrderedMap { * @return the next key */ public Object nextKey(Object key) { - LinkedEntry entry = (LinkedEntry) getEntry(key); + LinkEntry entry = (LinkEntry) getEntry(key); return (entry == null || entry.after == header ? null : entry.after.getKey()); } @@ -225,14 +229,33 @@ public class LinkedMap extends HashedMap implements OrderedMap { * @return the previous key */ public Object previousKey(Object key) { - LinkedEntry entry = (LinkedEntry) getEntry(key); + LinkEntry entry = (LinkEntry) getEntry(key); return (entry == null || entry.before == header ? null : entry.before.getKey()); } //----------------------------------------------------------------------- + /** + * Adds an entry into this map, maintaining insertion order. + *
+ * This implementation adds the entry to the data storage table and + * to the end of the linked list. + * + * @param entry the entry to add + * @param hashIndex the index into the data array to store at + */ + protected void addEntry(HashEntry entry, int hashIndex) { + LinkEntry link = (LinkEntry) entry; + link.after = header; + link.before = header.before; + header.before.after = link; + header.before = link; + data[hashIndex] = entry; + } + /** * Creates an entry to store the data. - * This implementation creates a LinkEntry instance in the linked list. + *
+ * This implementation creates a new LinkEntry instance. * * @param next the next entry in sequence * @param hashCode the hash code to use @@ -241,30 +264,26 @@ public class LinkedMap extends HashedMap implements OrderedMap { * @return the newly created entry */ protected HashEntry createEntry(HashEntry next, int hashCode, Object key, Object value) { - LinkedEntry entry = new LinkedEntry(next, hashCode, key, value); - entry.after = header; - entry.before = header.before; - header.before.after = entry; - header.before = entry; - return entry; + return new LinkEntry(next, hashCode, key, value); } /** - * Kills an entry ready for the garbage collector. - * This implementation manages the linked list and prepares the - * LinkEntry for garbage collection. + * Removes an entry from the map and the linked list. + *
+ * This implementation removes the entry from the linked list chain, then
+ * calls the superclass implementation.
*
- * @param entry the entry to destroy
- * @return the value from the entry
+ * @param entry the entry to remove
+ * @param hashIndex the index into the data structure
+ * @param previous the previous entry in the chain
*/
- protected Object destroyEntry(HashEntry entry) {
- LinkedEntry link = (LinkedEntry) entry;
+ protected void removeEntry(HashEntry entry, int hashIndex, HashEntry previous) {
+ LinkEntry link = (LinkEntry) entry;
link.before.after = link.after;
link.after.before = link.before;
- link.next = null;
link.after = null;
link.before = null;
- return entry.value;
+ super.removeEntry(entry, hashIndex, previous);
}
//-----------------------------------------------------------------------
@@ -283,7 +302,7 @@ public class LinkedMap extends HashedMap implements OrderedMap {
if (size == 0) {
return IteratorUtils.EMPTY_ORDERED_MAP_ITERATOR;
}
- return new LinkedMapIterator(this);
+ return new LinkMapIterator(this);
}
/**
@@ -301,15 +320,15 @@ public class LinkedMap extends HashedMap implements OrderedMap {
if (size == 0) {
return IteratorUtils.EMPTY_ORDERED_MAP_ITERATOR;
}
- return new LinkedMapIterator(this);
+ return new LinkMapIterator(this);
}
/**
* MapIterator
*/
- static class LinkedMapIterator extends LinkedIterator implements OrderedMapIterator {
+ static class LinkMapIterator extends LinkIterator implements OrderedMapIterator {
- LinkedMapIterator(LinkedMap map) {
+ LinkMapIterator(LinkedMap map) {
super(map);
}
@@ -363,7 +382,7 @@ public class LinkedMap extends HashedMap implements OrderedMap {
/**
* EntrySetIterator and MapEntry
*/
- static class EntrySetIterator extends LinkedIterator {
+ static class EntrySetIterator extends LinkIterator {
EntrySetIterator(LinkedMap map) {
super(map);
@@ -427,7 +446,7 @@ public class LinkedMap extends HashedMap implements OrderedMap {
/**
* ValuesIterator
*/
- static class ValuesIterator extends LinkedIterator {
+ static class ValuesIterator extends LinkIterator {
ValuesIterator(LinkedMap map) {
super(map);
@@ -446,12 +465,12 @@ public class LinkedMap extends HashedMap implements OrderedMap {
/**
* LinkEntry
*/
- protected static class LinkedEntry extends HashEntry {
+ protected static class LinkEntry extends HashEntry {
- LinkedEntry before;
- LinkedEntry after;
+ protected LinkEntry before;
+ protected LinkEntry after;
- protected LinkedEntry(HashEntry next, int hashCode, Object key, Object value) {
+ protected LinkEntry(HashEntry next, int hashCode, Object key, Object value) {
super(next, hashCode, key, value);
}
}
@@ -459,15 +478,15 @@ public class LinkedMap extends HashedMap implements OrderedMap {
/**
* Base Iterator
*/
- protected static abstract class LinkedIterator
+ protected static abstract class LinkIterator
implements OrderedIterator, ResettableIterator {
- private final LinkedMap map;
- private LinkedEntry current;
- private LinkedEntry next;
- private int expectedModCount;
+ protected final LinkedMap map;
+ protected LinkEntry current;
+ protected LinkEntry next;
+ protected int expectedModCount;
- protected LinkedIterator(LinkedMap map) {
+ protected LinkIterator(LinkedMap map) {
super();
this.map = map;
this.next = map.header.after;
@@ -482,7 +501,7 @@ public class LinkedMap extends HashedMap implements OrderedMap {
return (next.before != map.header);
}
- protected LinkedEntry nextEntry() {
+ protected LinkEntry nextEntry() {
if (map.modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
@@ -494,11 +513,11 @@ public class LinkedMap extends HashedMap implements OrderedMap {
return current;
}
- protected LinkedEntry previousEntry() {
+ protected LinkEntry previousEntry() {
if (map.modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
- LinkedEntry previous = next.before;
+ LinkEntry previous = next.before;
if (previous == map.header) {
throw new NoSuchElementException(HashedMap.NO_PREVIOUS_ENTRY);
}
@@ -507,7 +526,7 @@ public class LinkedMap extends HashedMap implements OrderedMap {
return current;
}
- protected LinkedEntry currentEntry() {
+ protected LinkEntry currentEntry() {
return current;
}
diff --git a/src/test/org/apache/commons/collections/map/TestAll.java b/src/test/org/apache/commons/collections/map/TestAll.java
index 7f117fa01..670dd86aa 100644
--- a/src/test/org/apache/commons/collections/map/TestAll.java
+++ b/src/test/org/apache/commons/collections/map/TestAll.java
@@ -1,5 +1,5 @@
/*
- * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/test/org/apache/commons/collections/map/TestAll.java,v 1.9 2003/12/03 19:04:41 scolebourne Exp $
+ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/test/org/apache/commons/collections/map/TestAll.java,v 1.10 2003/12/07 01:23:54 scolebourne Exp $
* ====================================================================
*
* The Apache Software License, Version 1.1
@@ -65,7 +65,7 @@ import junit.framework.TestSuite;
* Entry point for tests.
*
* @since Commons Collections 3.0
- * @version $Revision: 1.9 $ $Date: 2003/12/03 19:04:41 $
+ * @version $Revision: 1.10 $ $Date: 2003/12/07 01:23:54 $
*
* @author Stephen Colebourne
*/
@@ -87,6 +87,7 @@ public class TestAll extends TestCase {
suite.addTest(TestHashedMap.suite());
suite.addTest(TestIdentityMap.suite());
suite.addTest(TestLinkedMap.suite());
+ suite.addTest(TestLRUMap.suite());
suite.addTest(TestReferenceMap.suite());
suite.addTest(TestStaticBucketMap.suite());
diff --git a/src/test/org/apache/commons/collections/map/TestLRUMap.java b/src/test/org/apache/commons/collections/map/TestLRUMap.java
new file mode 100644
index 000000000..b88d6b019
--- /dev/null
+++ b/src/test/org/apache/commons/collections/map/TestLRUMap.java
@@ -0,0 +1,346 @@
+/*
+ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/test/org/apache/commons/collections/map/TestLRUMap.java,v 1.1 2003/12/07 01:23:54 scolebourne Exp $
+ * ====================================================================
+ *
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 2001-2003 The Apache Software Foundation. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ * any, must include the following acknowledgement:
+ * "This product includes software developed by the
+ * Apache Software Foundation (http://www.apache.org/)."
+ * Alternately, this acknowledgement may appear in the software itself,
+ * if and wherever such third-party acknowledgements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "Commons", and "Apache Software
+ * Foundation" must not be used to endorse or promote products derived
+ * from this software without prior written permission. For written
+ * permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ * nor may "Apache" appear in their names without prior written
+ * permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ *