/* * Copyright (c) 2008, Harald Kuhr * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name "TwelveMonkeys" nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR * 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. */ package com.twelvemonkeys.util; import java.util.*; import java.io.Serializable; /** * Generic map and linked list implementation of the {@code Map} interface, * with predictable iteration order. * <p> * Resembles {@code LinkedHashMap} from JDK 1.4+, but is backed by a generic * {@code Map}, rather than implementing a particular algoritm. * <p> * This linked list defines the iteration ordering, which is normally the order * in which keys were inserted into the map (<em>insertion-order</em>). * Note that insertion order is not affected if a key is <em>re-inserted</em> * into the map (a key {@code k} is reinserted into a map {@code m} if * {@code m.put(k, v)} is invoked when {@code m.containsKey(k)} would return * {@code true} immediately prior to the invocation). * <p> * A special {@link #LinkedMap(boolean) constructor} is provided to create a * linked hash map whose order of iteration is the order in which its entries * were last accessed, from least-recently accessed to most-recently * (<em>access-order</em>). * This kind of map is well-suited to building LRU caches. * Invoking the {@code put} or {@code get} method results in an access to the * corresponding entry (assuming it exists after the invocation completes). * The {@code putAll} method generates one entry access for each mapping in * the specified map, in the order that key-value mappings are provided by the * specified map's entry set iterator. * <em>No other methods generate entry accesses.</em> * In particular, operations on collection-views do not affect the order of * iteration of the backing map. * <p> * The {@link #removeEldestEntry(Map.Entry)} method may be overridden to impose * a policy for removing stale mappings automatically when new mappings are * added to the map. * * @author inspired by LinkedHashMap from JDK 1.4+, by Josh Bloch * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/LinkedMap.java#1 $ * * @see LinkedHashMap * @see LRUMap */ public class LinkedMap<K, V> extends AbstractDecoratedMap<K, V> implements Serializable { transient LinkedEntry<K, V> head; protected final boolean accessOrder; /** * Creates a {@code LinkedMap} backed by a {@code HashMap}, with default * (insertion) order. */ public LinkedMap() { this(null, false); } /** * Creates a {@code LinkedMap} backed by a {@code HashMap}, with the * given order. * * @param pAccessOrder if {@code true}, ordering will be "least recently * accessed item" to "latest accessed item", otherwise "first inserted item" * to "latest inserted item". */ public LinkedMap(boolean pAccessOrder) { this(null, pAccessOrder); } /** * Creates a {@code LinkedMap} backed by a {@code HashMap}, with key/value * pairs copied from {@code pContents} and default (insertion) order. * * @param pContents the map whose mappings are to be placed in this map. * May be {@code null}. */ public LinkedMap(Map<? extends K, ? extends V> pContents) { this(pContents, false); } /** * Creates a {@code LinkedMap} backed by a {@code HashMap}, with key/value * pairs copied from {@code pContents} and the given order. * * @param pContents the map whose mappings are to be placed in this map. * May be {@code null}. * @param pAccessOrder if {@code true}, ordering will be "least recently * accessed item" to "latest accessed item", otherwise "first inserted item" * to "latest inserted item". */ public LinkedMap(Map<? extends K, ? extends V> pContents, boolean pAccessOrder) { super(pContents); accessOrder = pAccessOrder; } /** * Creates a {@code LinkedMap} backed by the given map, with key/value * pairs copied from {@code pContents} and default (insertion) order. * * @param pBacking the backing map of this map. Must be either empty, or * the same map as {@code pContents}. * @param pContents the map whose mappings are to be placed in this map. * May be {@code null}. */ public LinkedMap(Map<K, Entry<K, V>> pBacking, Map<? extends K, ? extends V> pContents) { this(pBacking, pContents, false); } /** * Creates a {@code LinkedMap} backed by the given map, with key/value * pairs copied from {@code pContents} and the given order. * * @param pBacking the backing map of this map. Must be either empty, or * the same map as {@code pContents}. * @param pContents the map whose mappings are to be placed in this map. * May be {@code null}. * @param pAccessOrder if {@code true}, ordering will be "least recently * accessed item" to "latest accessed item", otherwise "first inserted item" * to "latest inserted item". */ public LinkedMap(Map<K, Entry<K, V>> pBacking, Map<? extends K, ? extends V> pContents, boolean pAccessOrder) { super(pBacking, pContents); accessOrder = pAccessOrder; } protected void init() { head = new LinkedEntry<K, V>(null, null, null) { void addBefore(LinkedEntry pExisting) { throw new Error(); } void remove() { throw new Error(); } public void recordAccess(Map pMap) { throw new Error(); } public void recordRemoval(Map pMap) { throw new Error(); } public void recordRemoval() { throw new Error(); } public V getValue() { throw new Error(); } public V setValue(V pValue) { throw new Error(); } public K getKey() { throw new Error(); } public String toString() { return "head"; } }; head.previous = head.next = head; } public boolean containsValue(Object pValue) { // Overridden to take advantage of faster iterator if (pValue == null) { for (LinkedEntry e = head.next; e != head; e = e.next) { if (e.mValue == null) { return true; } } } else { for (LinkedEntry e = head.next; e != head; e = e.next) { if (pValue.equals(e.mValue)) { return true; } } } return false; } protected Iterator<K> newKeyIterator() { return new KeyIterator(); } protected Iterator<V> newValueIterator() { return new ValueIterator(); } protected Iterator<Entry<K, V>> newEntryIterator() { return new EntryIterator(); } private abstract class LinkedMapIterator<E> implements Iterator<E> { LinkedEntry<K, V> mNextEntry = head.next; LinkedEntry<K, V> mLastReturned = null; /** * The modCount value that the iterator believes that the backing * List should have. If this expectation is violated, the iterator * has detected concurrent modification. */ int mExpectedModCount = modCount; public boolean hasNext() { return mNextEntry != head; } public void remove() { if (mLastReturned == null) { throw new IllegalStateException(); } if (modCount != mExpectedModCount) { throw new ConcurrentModificationException(); } LinkedMap.this.remove(mLastReturned.mKey); mLastReturned = null; mExpectedModCount = modCount; } LinkedEntry<K, V> nextEntry() { if (modCount != mExpectedModCount) { throw new ConcurrentModificationException(); } if (mNextEntry == head) { throw new NoSuchElementException(); } LinkedEntry<K, V> e = mLastReturned = mNextEntry; mNextEntry = e.next; return e; } } private class KeyIterator extends LinkedMap<K, V>.LinkedMapIterator<K> { public K next() { return nextEntry().mKey; } } private class ValueIterator extends LinkedMap<K, V>.LinkedMapIterator<V> { public V next() { return nextEntry().mValue; } } private class EntryIterator extends LinkedMap<K, V>.LinkedMapIterator<Entry<K, V>> { public Entry<K, V> next() { return nextEntry(); } } public V get(Object pKey) { LinkedEntry<K, V> entry = (LinkedEntry<K, V>) entries.get(pKey); if (entry != null) { entry.recordAccess(this); return entry.mValue; } return null; } public V remove(Object pKey) { LinkedEntry<K, V> entry = (LinkedEntry<K, V>) entries.remove(pKey); if (entry != null) { entry.remove(); modCount++; return entry.mValue; } return null; } public V put(K pKey, V pValue) { LinkedEntry<K, V> entry = (LinkedEntry<K, V>) entries.get(pKey); V oldValue; if (entry == null) { oldValue = null; // Remove eldest entry if instructed, else grow capacity if appropriate LinkedEntry<K, V> eldest = head.next; if (removeEldestEntry(eldest)) { removeEntry(eldest); } entry = createEntry(pKey, pValue); entry.addBefore(head); entries.put(pKey, entry); } else { oldValue = entry.mValue; entry.mValue = pValue; entry.recordAccess(this); } modCount++; return oldValue; } /** * Creates a new {@code LinkedEntry}. * * @param pKey the key * @param pValue the value * @return a new LinkedEntry */ /*protected*/ LinkedEntry<K, V> createEntry(K pKey, V pValue) { return new LinkedEntry<K, V>(pKey, pValue, null); } /** * @todo * * @return a copy of this map, with the same order and same key/value pairs. */ public Object clone() throws CloneNotSupportedException { LinkedMap map; map = (LinkedMap) super.clone(); // TODO: The rest of the work is PROBABLY handled by // AbstractDecoratedMap, but need to verify that. return map; } /** * Returns {@code true} if this map should remove its eldest entry. * This method is invoked by {@code put} and {@code putAll} after * inserting a new entry into the map. It provides the implementer * with the opportunity to remove the eldest entry each time a new one * is added. This is useful if the map represents a cache: it allows * the map to reduce memory consumption by deleting stale entries. * * <p>Sample use: this override will allow the map to grow up to 100 * entries and then delete the eldest entry each time a new entry is * added, maintaining a steady state of 100 entries. * <pre> * private static final int MAX_ENTRIES = 100; * * protected boolean removeEldestEntry(Map.Entry eldest) { * return size() > MAX_ENTRIES; * } * </pre> * * <p>This method typically does not modify the map in any way, * instead allowing the map to modify itself as directed by its * return value. It <i>is</i> permitted for this method to modify * the map directly, but if it does so, it <i>must</i> return * {@code false} (indicating that the map should not attempt any * further modification). The effects of returning {@code true} * after modifying the map from within this method are unspecified. * * <p>This implementation merely returns {@code false} (so that this * map acts like a normal map - the eldest element is never removed). * * @param pEldest The least recently inserted entry in the map, or if * this is an access-ordered map, the least recently accessed * entry. This is the entry that will be removed it this * method returns {@code true}. If the map was empty prior * to the {@code put} or {@code putAll} invocation resulting * in this invocation, this will be the entry that was just * inserted; in other words, if the map contains a single * entry, the eldest entry is also the newest. * @return {@code true} if the eldest entry should be removed * from the map; {@code false} if it should be retained. */ protected boolean removeEldestEntry(Entry<K, V> pEldest) { return false; } /** * Linked list implementation of {@code Map.Entry}. */ protected static class LinkedEntry<K, V> extends BasicEntry<K, V> implements Serializable { LinkedEntry<K, V> previous; LinkedEntry<K, V> next; LinkedEntry(K pKey, V pValue, LinkedEntry<K, V> pNext) { super(pKey, pValue); next = pNext; } /** * Adds this entry before the given entry (which must be an existing * entry) in the list. * * @param pExisting the entry to add before */ void addBefore(LinkedEntry<K, V> pExisting) { next = pExisting; previous = pExisting.previous; previous.next = this; next.previous = this; } /** * Removes this entry from the linked list. */ void remove() { previous.next = next; next.previous = previous; } /** * If the entry is part of an access ordered list, moves the entry to * the end of the list. * * @param pMap the map to record access for */ protected void recordAccess(Map<K, V> pMap) { LinkedMap<K, V> linkedMap = (LinkedMap<K, V>) pMap; if (linkedMap.accessOrder) { linkedMap.modCount++; remove(); addBefore(linkedMap.head); } } /** * Removes this entry from the linked list. * * @param pMap the map to record removal from */ protected void recordRemoval(Map<K, V> pMap) { // TODO: Is this REALLY correct? remove(); } } }