com.twelvemonkeys.util.LinkedMap Maven / Gradle / Ivy
/*
* 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.
*
* Resembles {@code LinkedHashMap} from JDK 1.4+, but is backed by a generic
* {@code Map}, rather than implementing a particular algoritm.
*
* This linked list defines the iteration ordering, which is normally the order
* in which keys were inserted into the map (insertion-order).
* Note that insertion order is not affected if a key is re-inserted
* 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).
*
* 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
* (access-order).
* 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.
* No other methods generate entry accesses.
* In particular, operations on collection-views do not affect the order of
* iteration of the backing map.
*
* 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 Harald Kuhr
* @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 extends AbstractDecoratedMap implements Serializable {
transient LinkedEntry mHead;
protected final boolean mAccessOrder;
/**
* 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);
mAccessOrder = 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> 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> pBacking, Map extends K, ? extends V> pContents, boolean pAccessOrder) {
super(pBacking, pContents);
mAccessOrder = pAccessOrder;
}
protected void init() {
mHead = new LinkedEntry(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";
}
};
mHead.mPrevious = mHead.mNext = mHead;
}
public boolean containsValue(Object pValue) {
// Overridden to take advantage of faster iterator
if (pValue == null) {
for (LinkedEntry e = mHead.mNext; e != mHead; e = e.mNext) {
if (e.mValue == null) {
return true;
}
}
} else {
for (LinkedEntry e = mHead.mNext; e != mHead; e = e.mNext) {
if (pValue.equals(e.mValue)) {
return true;
}
}
}
return false;
}
protected Iterator newKeyIterator() {
return new KeyIterator();
}
protected Iterator newValueIterator() {
return new ValueIterator();
}
protected Iterator> newEntryIterator() {
return new EntryIterator();
}
private abstract class LinkedMapIterator implements Iterator {
LinkedEntry mNextEntry = mHead.mNext;
LinkedEntry 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 = mModCount;
public boolean hasNext() {
return mNextEntry != mHead;
}
public void remove() {
if (mLastReturned == null) {
throw new IllegalStateException();
}
if (mModCount != mExpectedModCount) {
throw new ConcurrentModificationException();
}
LinkedMap.this.remove(mLastReturned.mKey);
mLastReturned = null;
mExpectedModCount = mModCount;
}
LinkedEntry nextEntry() {
if (mModCount != mExpectedModCount) {
throw new ConcurrentModificationException();
}
if (mNextEntry == mHead) {
throw new NoSuchElementException();
}
LinkedEntry e = mLastReturned = mNextEntry;
mNextEntry = e.mNext;
return e;
}
}
private class KeyIterator extends LinkedMap.LinkedMapIterator {
public K next() {
return nextEntry().mKey;
}
}
private class ValueIterator extends LinkedMap.LinkedMapIterator {
public V next() {
return nextEntry().mValue;
}
}
private class EntryIterator extends LinkedMap.LinkedMapIterator> {
public Entry next() {
return nextEntry();
}
}
public V get(Object pKey) {
LinkedEntry entry = (LinkedEntry) mEntries.get(pKey);
if (entry != null) {
entry.recordAccess(this);
return entry.mValue;
}
return null;
}
public V remove(Object pKey) {
LinkedEntry entry = (LinkedEntry) mEntries.remove(pKey);
if (entry != null) {
entry.remove();
mModCount++;
return entry.mValue;
}
return null;
}
public V put(K pKey, V pValue) {
LinkedEntry entry = (LinkedEntry) mEntries.get(pKey);
V oldValue;
if (entry == null) {
oldValue = null;
// Remove eldest entry if instructed, else grow capacity if appropriate
LinkedEntry eldest = mHead.mNext;
if (removeEldestEntry(eldest)) {
removeEntry(eldest);
}
entry = createEntry(pKey, pValue);
entry.addBefore(mHead);
mEntries.put(pKey, entry);
}
else {
oldValue = entry.mValue;
entry.mValue = pValue;
entry.recordAccess(this);
}
mModCount++;
return oldValue;
}
/**
* Creates a new {@code LinkedEntry}.
*
* @param pKey the key
* @param pValue the value
* @return a new LinkedEntry
*/
/*protected*/ LinkedEntry createEntry(K pKey, V pValue) {
return new LinkedEntry(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.
*
* 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.
*
* private static final int MAX_ENTRIES = 100;
*
* protected boolean removeEldestEntry(Map.Entry eldest) {
* return size() > MAX_ENTRIES;
* }
*
*
* 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 is permitted for this method to modify
* the map directly, but if it does so, it must 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.
*
*
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 pEldest) {
return false;
}
/**
* Linked list implementation of {@code Map.Entry}.
*/
protected static class LinkedEntry extends BasicEntry implements Serializable {
LinkedEntry mPrevious;
LinkedEntry mNext;
LinkedEntry(K pKey, V pValue, LinkedEntry pNext) {
super(pKey, pValue);
mNext = 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 pExisting) {
mNext = pExisting;
mPrevious = pExisting.mPrevious;
mPrevious.mNext = this;
mNext.mPrevious = this;
}
/**
* Removes this entry from the linked list.
*/
void remove() {
mPrevious.mNext = mNext;
mNext.mPrevious = mPrevious;
}
/**
* 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 pMap) {
LinkedMap linkedMap = (LinkedMap) pMap;
if (linkedMap.mAccessOrder) {
linkedMap.mModCount++;
remove();
addBefore(linkedMap.mHead);
}
}
/**
* Removes this entry from the linked list.
*
* @param pMap the map to record remoal from
*/
protected void recordRemoval(Map pMap) {
// TODO: Is this REALLY correct?
remove();
}
}
}