Java/Collections Data Structure/Customized Map
Версия от 18:01, 31 мая 2010; (обсуждение)
Содержание
- 1 A fixed size map implementation.
- 2 A hash map that uses primitive ints for the key rather than objects.
- 3 A hashtable-based Map implementation with soft keys
- 4 A java.util.Map interface which can only hold a single object
- 5 A Map collection with real-time behavior
- 6 A Map that accepts int or Integer keys only
- 7 A Map where keys are compared by object identity, rather than equals()
- 8 A Map with multiple values for a key
- 9 A memory-efficient hash map.
- 10 A multi valued Map
- 11 An IdentityMap that uses reference-equality instead of object-equality
- 12 An implementation of the java.util.Map interface which can only hold a single object.
- 13 An integer hashmap
- 14 Array Map
- 15 A simple hashmap from keys to integers
- 16 Cache Map
- 17 CaseBlindHashMap - a HashMap extension, using Strings as key values.
- 18 Case Insensitive Map
- 19 Combines multiple values to form a single composite key. MultiKey can often be used as an alternative to nested maps.
- 20 Complex Key HashMap
- 21 Concurrent Skip List Map
- 22 Copy On Write Map
- 23 Expiring Map
- 24 Hash map using String values as keys mapped to primitive int values.
- 25 HashNMap stores multiple values by a single key value. Values can be retrieved using a direct query or by creating an enumeration over the stored elements.
- 26 Integer Map
- 27 Int HashMap
- 28 Int HashMap from jodd.org
- 29 Int Int Map
- 30 IntMap provides a simple hashmap from keys to integers
- 31 Int Object HashMap
- 32 Int Object HashMap (from CERN)
- 33 Int Object Map
- 34 List Map
- 35 List ordered map
- 36 Lookup table that stores a list of strings
- 37 Map implementation Optimized for Strings keys
- 38 Map using Locale objects as keys
- 39 Map with keys iterated in insertion order
- 40 Most Recently Used Map
- 41 Multi Map
- 42 MultiMap is a Java version of the C++ STL class std::multimap
- 43 Object Int Map
- 44 Ordered Map
- 45 Sequenced HashMap
- 46 String Map
- 47 Type-safe Map, from char array to String value
- 48 Utility methods for operating on memory-efficient maps.
A fixed size map implementation.
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.Serializable;
import java.util.AbstractList;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* A fixed size map implementation. Holds an array of keys and array of values which correspond by
* index. Null key entries are available for use. This means that null is not a valid key.
*
* @author Jonathan Locke
* @param <K>
* Key type
* @param <V>
* Value type
*/
public class MiniMap<K, V> implements Map<K, V>, Serializable
{
private static final long serialVersionUID = 1L;
/** The array of keys. Keys that are null are not used. */
private final K[] keys;
/** The array of values which correspond by index with the keys array. */
private final V[] values;
/** The number of valid entries */
private int size;
/** The last search index. This makes putting and getting more efficient. */
private int lastSearchIndex;
/**
* Constructor
*
* @param maxEntries
* The maximum number of entries this map can hold
*/
@SuppressWarnings("unchecked")
public MiniMap(final int maxEntries)
{
keys = (K[])new Object[maxEntries];
values = (V[])new Object[maxEntries];
}
/**
* Constructor
*
* @param map
* The map
* @param maxEntries
* The maximum number of entries this map can hold
*/
public MiniMap(final Map<? extends K, ? extends V> map, final int maxEntries)
{
this(maxEntries);
putAll(map);
}
/**
* @return True if this MicroMap is full
*/
public boolean isFull()
{
return size == keys.length;
}
/**
* @see java.util.Map#size()
*/
public int size()
{
return size;
}
/**
* @see java.util.Map#isEmpty()
*/
public boolean isEmpty()
{
return size == 0;
}
/**
* @see java.util.Map#containsKey(java.lang.Object)
*/
public boolean containsKey(final Object key)
{
return findKey(0, key) != -1;
}
/**
* @see java.util.Map#containsValue(java.lang.Object)
*/
public boolean containsValue(final Object value)
{
return findValue(0, value) != -1;
}
/**
* @see java.util.Map#get(java.lang.Object)
*/
public V get(final Object key)
{
// Search for key
final int index = findKey(key);
if (index != -1)
{
// Return value
return values[index];
}
// Failed to find key
return null;
}
/**
* @see java.util.Map#put(java.lang.Object, java.lang.Object)
*/
public V put(final K key, final V value)
{
// Search for key
final int index = findKey(key);
if (index != -1)
{
// Replace existing value
final V oldValue = values[index];
values[index] = value;
return oldValue;
}
// Is there room for a new entry?
if (size < keys.length)
{
// Store at first null index and continue searching after null index
// next time
final int nullIndex = nextNullKey(lastSearchIndex);
lastSearchIndex = nextIndex(nullIndex);
keys[nullIndex] = key;
values[nullIndex] = value;
size++;
return null;
}
else
{
throw new IllegalStateException("Map full");
}
}
/**
* @see java.util.Map#remove(java.lang.Object)
*/
public V remove(final Object key)
{
// Search for key
final int index = findKey(key);
if (index != -1)
{
// Store value
final V oldValue = values[index];
keys[index] = null;
values[index] = null;
size--;
return oldValue;
}
return null;
}
/**
* @see java.util.Map#putAll(java.util.Map)
*/
public void putAll(Map<? extends K, ? extends V> map)
{
for (final Iterator<? extends Entry<? extends K, ? extends V>> iterator = map.entrySet()
.iterator(); iterator.hasNext();)
{
final Map.Entry<? extends K, ? extends V> e = iterator.next();
put(e.getKey(), e.getValue());
}
}
/**
* @see java.util.Map#clear()
*/
public void clear()
{
for (int i = 0; i < keys.length; i++)
{
keys[i] = null;
values[i] = null;
}
size = 0;
}
/**
* @see java.util.Map#keySet()
*/
public Set<K> keySet()
{
return new AbstractSet<K>()
{
@Override
public Iterator<K> iterator()
{
return new Iterator<K>()
{
public boolean hasNext()
{
return i < size - 1;
}
public K next()
{
// Just in case... (WICKET-428)
if (!hasNext())
{
throw new NoSuchElementException();
}
// Find next key
i = nextKey(nextIndex(i));
// Get key
return keys[i];
}
public void remove()
{
keys[i] = null;
values[i] = null;
size--;
}
int i = -1;
};
}
@Override
public int size()
{
return size;
}
};
}
/**
* @see java.util.Map#values()
*/
public Collection<V> values()
{
return new AbstractList<V>()
{
@Override
public V get(final int index)
{
if (index > size - 1)
{
throw new IndexOutOfBoundsException();
}
int keyIndex = nextKey(0);
for (int i = 0; i < index; i++)
{
keyIndex = nextKey(keyIndex + 1);
}
return values[keyIndex];
}
@Override
public int size()
{
return size;
}
};
}
/**
* @see java.util.Map#entrySet()
*/
public Set<Entry<K, V>> entrySet()
{
return new AbstractSet<Entry<K, V>>()
{
@Override
public Iterator<Entry<K, V>> iterator()
{
return new Iterator<Entry<K, V>>()
{
public boolean hasNext()
{
return index < size;
}
public Entry<K, V> next()
{
if (!hasNext())
{
throw new NoSuchElementException();
}
keyIndex = nextKey(nextIndex(keyIndex));
index++;
return new Map.Entry<K, V>()
{
public K getKey()
{
return keys[keyIndex];
}
public V getValue()
{
return values[keyIndex];
}
public V setValue(final V value)
{
final V oldValue = values[keyIndex];
values[keyIndex] = value;
return oldValue;
}
};
}
public void remove()
{
keys[keyIndex] = null;
values[keyIndex] = null;
}
int keyIndex = -1;
int index = 0;
};
}
@Override
public int size()
{
return size;
}
};
}
/**
* Computes the next index in the key or value array (both are the same length)
*
* @param index
* The index
* @return The next index, taking into account wraparound
*/
private int nextIndex(final int index)
{
return (index + 1) % keys.length;
}
/**
* Finds the index of the next non-null key. If the map is empty, -1 will be returned.
*
* @param start
* Index to start at
* @return Index of next non-null key
*/
private int nextKey(final int start)
{
int i = start;
do
{
if (keys[i] != null)
{
return i;
}
i = nextIndex(i);
}
while (i != start);
return -1;
}
/**
* Finds the index of the next null key. If no null key can be found, the map is full and -1
* will be returned.
*
* @param start
* Index to start at
* @return Index of next null key
*/
private int nextNullKey(final int start)
{
int i = start;
do
{
if (keys[i] == null)
{
return i;
}
i = nextIndex(i);
}
while (i != start);
return -1;
}
/**
* Finds a key by starting at lastSearchIndex and searching from there. If the key is found,
* lastSearchIndex is advanced so the next key search can find the next key in the array, which
* is the most likely to be retrieved.
*
* @param key
* Key to find in map
* @return Index of matching key or -1 if not found
*/
private int findKey(final Object key)
{
if (size > 0)
{
// Find key starting at search index
final int index = findKey(lastSearchIndex, key);
// Found match?
if (index != -1)
{
// Start search at the next index next time
lastSearchIndex = nextIndex(index);
// Return index of key
return index;
}
}
return -1;
}
/**
* Searches for a key from a given starting index.
*
* @param key
* The key to find in this map
* @param start
* Index to start at
* @return Index of matching key or -1 if not found
*/
private int findKey(final int start, final Object key)
{
int i = start;
do
{
if (key.equals(keys[i]))
{
return i;
}
i = nextIndex(i);
}
while (i != start);
return -1;
}
/**
* Searches for a value from a given starting index.
*
* @param start
* Index to start at
* @param value
* The value to find in this map
* @return Index of matching value or -1 if not found
*/
private int findValue(final int start, final Object value)
{
int i = start;
do
{
if (value.equals(values[i]))
{
return i;
}
i = nextIndex(i);
}
while (i != start);
return -1;
}
}
A hash map that uses primitive ints for the key rather than objects.
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Note: originally released under the GNU LGPL v2.1,
* but rereleased by the original author under the ASF license (above).
*/
/**
* <p>A hash map that uses primitive ints for the key rather than objects.</p>
*
* <p>Note that this class is for internal optimization purposes only, and may
* not be supported in future releases of Apache Commons Lang. Utilities of
* this sort may be included in future releases of Apache Commons Collections.</p>
*
* @author Justin Couch
* @author Alex Chaffee (alex@apache.org)
* @author Stephen Colebourne
* @since 2.0
* @version $Revision: 561230 $
* @see java.util.HashMap
*/
class IntHashMap {
/**
* The hash table data.
*/
private transient Entry table[];
/**
* The total number of entries in the hash table.
*/
private transient int count;
/**
* The table is rehashed when its size exceeds this threshold. (The
* value of this field is (int)(capacity * loadFactor).)
*
* @serial
*/
private int threshold;
/**
* The load factor for the hashtable.
*
* @serial
*/
private float loadFactor;
/**
* <p>Innerclass that acts as a datastructure to create a new entry in the
* table.</p>
*/
private static class Entry {
int hash;
int key;
Object value;
Entry next;
/**
* <p>Create a new entry with the given values.</p>
*
* @param hash The code used to hash the object with
* @param key The key used to enter this in the table
* @param value The value for this key
* @param next A reference to the next entry in the table
*/
protected Entry(int hash, int key, Object value, Entry next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
}
/**
* <p>Constructs a new, empty hashtable with a default capacity and load
* factor, which is <code>20</code> and <code>0.75</code> respectively.</p>
*/
public IntHashMap() {
this(20, 0.75f);
}
/**
* <p>Constructs a new, empty hashtable with the specified initial capacity
* and default load factor, which is <code>0.75</code>.</p>
*
* @param initialCapacity the initial capacity of the hashtable.
* @throws IllegalArgumentException if the initial capacity is less
* than zero.
*/
public IntHashMap(int initialCapacity) {
this(initialCapacity, 0.75f);
}
/**
* <p>Constructs a new, empty hashtable with the specified initial
* capacity and the specified load factor.</p>
*
* @param initialCapacity the initial capacity of the hashtable.
* @param loadFactor the load factor of the hashtable.
* @throws IllegalArgumentException if the initial capacity is less
* than zero, or if the load factor is nonpositive.
*/
public IntHashMap(int initialCapacity, float loadFactor) {
super();
if (initialCapacity < 0) {
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}
if (loadFactor <= 0) {
throw new IllegalArgumentException("Illegal Load: " + loadFactor);
}
if (initialCapacity == 0) {
initialCapacity = 1;
}
this.loadFactor = loadFactor;
table = new Entry[initialCapacity];
threshold = (int) (initialCapacity * loadFactor);
}
/**
* <p>Returns the number of keys in this hashtable.</p>
*
* @return the number of keys in this hashtable.
*/
public int size() {
return count;
}
/**
* <p>Tests if this hashtable maps no keys to values.</p>
*
* @return <code>true</code> if this hashtable maps no keys to values;
* <code>false</code> otherwise.
*/
public boolean isEmpty() {
return count == 0;
}
/**
* <p>Tests if some key maps into the specified value in this hashtable.
* This operation is more expensive than the <code>containsKey</code>
* method.</p>
*
* <p>Note that this method is identical in functionality to containsValue,
* (which is part of the Map interface in the collections framework).</p>
*
* @param value a value to search for.
* @return <code>true</code> if and only if some key maps to the
* <code>value</code> argument in this hashtable as
* determined by the <tt>equals</tt> method;
* <code>false</code> otherwise.
* @throws NullPointerException if the value is <code>null</code>.
* @see #containsKey(int)
* @see #containsValue(Object)
* @see java.util.Map
*/
public boolean contains(Object value) {
if (value == null) {
throw new NullPointerException();
}
Entry tab[] = table;
for (int i = tab.length; i-- > 0;) {
for (Entry e = tab[i]; e != null; e = e.next) {
if (e.value.equals(value)) {
return true;
}
}
}
return false;
}
/**
* <p>Returns <code>true</code> if this HashMap maps one or more keys
* to this value.</p>
*
* <p>Note that this method is identical in functionality to contains
* (which predates the Map interface).</p>
*
* @param value value whose presence in this HashMap is to be tested.
* @return boolean <code>true</code> if the value is contained
* @see java.util.Map
* @since JDK1.2
*/
public boolean containsValue(Object value) {
return contains(value);
}
/**
* <p>Tests if the specified object is a key in this hashtable.</p>
*
* @param key possible key.
* @return <code>true</code> if and only if the specified object is a
* key in this hashtable, as determined by the <tt>equals</tt>
* method; <code>false</code> otherwise.
* @see #contains(Object)
*/
public boolean containsKey(int key) {
Entry tab[] = table;
int hash = key;
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index]; e != null; e = e.next) {
if (e.hash == hash) {
return true;
}
}
return false;
}
/**
* <p>Returns the value to which the specified key is mapped in this map.</p>
*
* @param key a key in the hashtable.
* @return the value to which the key is mapped in this hashtable;
* <code>null</code> if the key is not mapped to any value in
* this hashtable.
* @see #put(int, Object)
*/
public Object get(int key) {
Entry tab[] = table;
int hash = key;
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index]; e != null; e = e.next) {
if (e.hash == hash) {
return e.value;
}
}
return null;
}
/**
* <p>Increases the capacity of and internally reorganizes this
* hashtable, in order to accommodate and access its entries more
* efficiently.</p>
*
* <p>This method is called automatically when the number of keys
* in the hashtable exceeds this hashtable"s capacity and load
* factor.</p>
*/
protected void rehash() {
int oldCapacity = table.length;
Entry oldMap[] = table;
int newCapacity = oldCapacity * 2 + 1;
Entry newMap[] = new Entry[newCapacity];
threshold = (int) (newCapacity * loadFactor);
table = newMap;
for (int i = oldCapacity; i-- > 0;) {
for (Entry old = oldMap[i]; old != null;) {
Entry e = old;
old = old.next;
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = newMap[index];
newMap[index] = e;
}
}
}
/**
* <p>Maps the specified <code>key</code> to the specified
* <code>value</code> in this hashtable. The key cannot be
* <code>null</code>. </p>
*
* <p>The value can be retrieved by calling the <code>get</code> method
* with a key that is equal to the original key.</p>
*
* @param key the hashtable key.
* @param value the value.
* @return the previous value of the specified key in this hashtable,
* or <code>null</code> if it did not have one.
* @throws NullPointerException if the key is <code>null</code>.
* @see #get(int)
*/
public Object put(int key, Object value) {
// Makes sure the key is not already in the hashtable.
Entry tab[] = table;
int hash = key;
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index]; e != null; e = e.next) {
if (e.hash == hash) {
Object old = e.value;
e.value = value;
return old;
}
}
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
Entry e = new Entry(hash, key, value, tab[index]);
tab[index] = e;
count++;
return null;
}
/**
* <p>Removes the key (and its corresponding value) from this
* hashtable.</p>
*
* <p>This method does nothing if the key is not present in the
* hashtable.</p>
*
* @param key the key that needs to be removed.
* @return the value to which the key had been mapped in this hashtable,
* or <code>null</code> if the key did not have a mapping.
*/
public Object remove(int key) {
Entry tab[] = table;
int hash = key;
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index], prev = null; e != null; prev = e, e = e.next) {
if (e.hash == hash) {
if (prev != null) {
prev.next = e.next;
} else {
tab[index] = e.next;
}
count--;
Object oldValue = e.value;
e.value = null;
return oldValue;
}
}
return null;
}
/**
* <p>Clears this hashtable so that it contains no keys.</p>
*/
public synchronized void clear() {
Entry tab[] = table;
for (int index = tab.length; --index >= 0;) {
tab[index] = null;
}
count = 0;
}
}
A hashtable-based Map implementation with soft keys
/*
* Copyright 2006 (C) TJDO.
* All rights reserved.
*
* This software is distributed under the terms of the TJDO License version 1.0.
* See the terms of the TJDO License in the documentation provided with this software.
*
* $Id: SoftHashMap.java,v 1.2 2007/10/03 01:23:43 jackknifebarber Exp $
*/
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* A hashtable-based <tt>Map</tt> implementation with <em>soft keys</em>.
* An entry in a <tt>SoftHashMap</tt> will automatically be removed when
* its key is no longer in ordinary use.
* More precisely, the presence of a mapping for a given key will not prevent
* the key from being discarded by the garbage collector, that is, made
* finalizable, finalized, and then reclaimed.
* When a key has been discarded its entry is effectively removed from the map,
* so this class behaves somewhat differently than other <tt>Map</tt>
* implementations.
* <p>
* Both null values and the null key are supported. This class has performance
* characteristics similar to those of the <tt>HashMap</tt> class, and has the
* same efficiency parameters of <em>initial capacity</em> and <em>load
* factor</em>.
* <p>
* Like most collection classes, this class is not synchronized.
* A synchronized <tt>SoftHashMap</tt> may be constructed using the
* <tt>Collections.synchronizedMap</tt> method.
* <p>
* This class is intended primarily for use with key objects whose
* <tt>equals</tt> methods test for object identity using the <tt>==</tt>
* operator.
* Once such a key is discarded it can never be recreated, so it is impossible
* to do a lookup of that key in a <tt>SoftHashMap</tt> at some later time and
* be surprised that its entry has been removed.
* This class will work perfectly well with key objects whose <tt>equals</tt>
* methods are not based upon object identity, such as <tt>String</tt>
* instances.
* With such recreatable key objects, however, the automatic removal of
* <tt>SoftHashMap</tt> entries whose keys have been discarded may prove to be
* confusing.
* <p>
* The behavior of the <tt>SoftHashMap</tt> class depends in part upon the
* actions of the garbage collector, so several familiar (though not required)
* <tt>Map</tt> invariants do not hold for this class.
* Because the garbage collector may discard keys at any time, a
* <tt>SoftHashMap</tt> may behave as though an unknown thread is silently
* removing entries.s
* In particular, even if you synchronize on a <tt>SoftHashMap</tt> instance and
* invoke none of its mutator methods, it is possible for the <tt>size</tt>
* method to return smaller values over time, for the <tt>isEmpty</tt> method to
* return <tt>false</tt> and then <tt>true</tt>, for the <tt>containsKey</tt>
* method to return <tt>true</tt> and later <tt>false</tt> for a given key, for
* the <tt>get</tt> method to return a value for a given key but later return
* <tt>null</tt>, for the <tt>put</tt> method to return <tt>null</tt> and the
* <tt>remove</tt> method to return <tt>false</tt> for a key that previously
* appeared to be in the map, and for successive examinations of the key set,
* the value set, and the entry set to yield successively smaller numbers of
* elements.
* <p>
* Each key object in a <tt>SoftHashMap</tt> is stored indirectly as the
* referent of a soft reference.
* Therefore a key will automatically be removed only after the soft references
* to it, both inside and outside of the map, have been cleared by the garbage
* collector.
* <p>
* <strong>Implementation note:</strong> The value objects in a
* <tt>SoftHashMap</tt> are held by ordinary strong references.
* Thus care should be taken to ensure that value objects do not strongly refer
* to their own keys, either directly or indirectly, since that will prevent the
* keys from being discarded.
* Note that a value object may refer indirectly to its key via the
* <tt>SoftHashMap</tt> itself; that is, a value object may strongly refer to
* some other key object whose associated value object, in turn, strongly refers
* to the key of the first value object.
* One way to deal with this is to wrap values themselves within
* <tt>SoftReferences</tt> before inserting, as in: <tt>m.put(key, new
* SoftReference(value))</tt>, and then unwrapping upon each <tt>get</tt>.
* <p>
* The iterators returned by all of this class"s "collection view methods" are
* <i>fail-fast</i>: if the map is structurally modified at any time after the
* iterator is created, in any way except through the iterator"s own
* <tt>remove</tt> or <tt>add</tt> methods, the iterator will throw a
* <tt>ConcurrentModificationException</tt>.
* Thus, in the face of concurrent modification, the iterator fails quickly and
* cleanly, rather than risking arbitrary, non-deterministic behavior at an
* undetermined time in the future.
* <p>
* Note that the fail-fast behavior of an iterator cannot be guaranteed as it
* is, generally speaking, impossible to make any hard guarantees in the
* presence of unsynchronized concurrent modification.
* Fail-fast iterators throw <tt>ConcurrentModificationException</tt> on a
* best-effort basis.
* Therefore, it would be wrong to write a program that depended on this
* exception for its correctness: <i>the fail-fast behavior of iterators
* should be used only to detect bugs.</i>
*
* @author
* (borrowing liberally from java.util.WeakHashMap)
* @version $Revision: 1.2 $
*/
public class SoftHashMap extends AbstractMap implements Map
{
/**
* The default initial capacity -- MUST be a power of two.
*/
private static final int DEFAULT_INITIAL_CAPACITY = 16;
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
private static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The load fast used when none specified in constructor.
*/
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* The table, resized as necessary. Length MUST Always be a power of two.
*/
private Entry[] table;
/**
* The number of key-value mappings contained in this soft hash map.
*/
private int size;
/**
* The next size value at which to resize (capacity * load factor).
*/
private int threshold;
/**
* The load factor for the hash table.
*/
private final float loadFactor;
/**
* Reference queue for cleared SoftEntries.
*/
private final ReferenceQueue queue = new ReferenceQueue();
/**
* The number of times this map has been structurally modified.
* Structural modifications are those that change the number of mappings or
* otherwise modify its internal structure (e.g., rehash).
* This field is used to make iterators on Collection-views of the map
* fail-fast.
* (See ConcurrentModificationException).
*/
private volatile int modCount;
/**
* Constructs a new, empty <tt>SoftHashMap</tt> with the given initial
* capacity and the given load factor.
*
* @param initialCapacity
* The initial capacity of the <tt>SoftHashMap</tt>
* @param loadFactor
* The load factor of the <tt>SoftHashMap</tt>
* @throws IllegalArgumentException
* If the initial capacity is negative, or if the load factor is
* nonpositive.
*/
public SoftHashMap(int initialCapacity, float loadFactor)
{
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Initial Capacity: " + initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load factor: " + loadFactor);
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
table = new Entry[capacity];
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
}
/**
* Constructs a new, empty <tt>SoftHashMap</tt> with the given initial
* capacity and the default load factor, which is <tt>0.75</tt>.
*
* @param initialCapacity
* The initial capacity of the <tt>SoftHashMap</tt>
* @throws IllegalArgumentException
* If the initial capacity is negative.
*/
public SoftHashMap(int initialCapacity)
{
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* Constructs a new, empty <tt>SoftHashMap</tt> with the default initial
* capacity (16) and the default load factor (0.75).
*/
public SoftHashMap()
{
loadFactor = DEFAULT_LOAD_FACTOR;
threshold = DEFAULT_INITIAL_CAPACITY;
table = new Entry[DEFAULT_INITIAL_CAPACITY];
}
/**
* Constructs a new <tt>SoftHashMap</tt> with the same mappings as the
* specified <tt>Map</tt>.
* The <tt>SoftHashMap</tt> is created with default load factor, which is
* <tt>0.75</tt> and an initial capacity sufficient to hold the mappings
* in the specified <tt>Map</tt>.
*
* @param t
* the map whose mappings are to be placed in this map.
* @throws NullPointerException
* if the specified map is null.
*/
public SoftHashMap(Map t)
{
this(Math.max((int)(t.size() / DEFAULT_LOAD_FACTOR) + 1, 16), DEFAULT_LOAD_FACTOR);
putAll(t);
}
// internal utilities
/**
* Value representing null keys inside tables.
*/
private static final Object NULL_KEY = new Object();
/**
* Use NULL_KEY for key if it is null.
*/
private static Object maskNull(Object key)
{
return key == null ? NULL_KEY : key;
}
/**
* Return internal representation of null key back to caller as null
*/
private static Object unmaskNull(Object key)
{
return key == NULL_KEY ? null : key;
}
/**
* Returns a hash value for the specified object.
* In addition to the object"s own hashCode, this method applies a
* "supplemental hash function," which defends against poor quality hash
* functions.
* This is critical because HashMap uses power-of two length hash tables.
* <p>
* The shift distances in this function were chosen as the result of an
* automated search over the entire four-dimensional search space.
*/
static int hash(Object x)
{
int h = x.hashCode();
h += ~(h << 9);
h ^= (h >>> 14);
h += (h << 4);
h ^= (h >>> 10);
return h;
}
/**
* Check for equality of non-null reference x and possibly-null y.
* By default uses Object.equals.
*/
static boolean eq(Object x, Object y)
{
return x == y || x.equals(y);
}
/**
* Return index for hash code h.
*/
static int indexFor(int h, int length)
{
return h & (length - 1);
}
/**
* Expunge stale entries from the table.
*/
private void expungeStaleEntries()
{
Object r;
while ((r = queue.poll()) != null)
{
Entry e = (Entry)r;
int h = e.hash;
int i = indexFor(h, table.length);
Entry prev = table[i];
Entry p = prev;
while (p != null)
{
Entry next = p.next;
if (p == e)
{
if (prev == e)
table[i] = next;
else
prev.next = next;
e.next = null; // Help GC
e.value = null; // " "
size--;
break;
}
prev = p;
p = next;
}
}
}
/**
* Return the table after first expunging stale entries
*/
private Entry[] getTable()
{
expungeStaleEntries();
return table;
}
/**
* Returns the number of key-value mappings in this map.
* This result is a snapshot, and may not reflect unprocessed entries that
* will be removed before next attempted access because they are no longer
* referenced.
*/
public int size()
{
if (size == 0)
return 0;
expungeStaleEntries();
return size;
}
/**
* Returns <tt>true</tt> if this map contains no key-value mappings.
* This result is a snapshot, and may not reflect unprocessed entries that
* will be removed before next attempted access because they are no longer
* referenced.
*/
public boolean isEmpty()
{
return size() == 0;
}
/**
* Returns the value to which the specified key is mapped in this soft hash
* map, or <tt>null</tt> if the map contains no mapping for this key.
* A return value of <tt>null</tt> does not <i>necessarily</i> indicate that
* the map contains no mapping for the key; it is also possible that the map
* explicitly maps the key to <tt>null</tt>.
* The <tt>containsKey</tt> method may be used to distinguish these two
* cases.
*
* @param key
* the key whose associated value is to be returned.
* @return
* the value to which this map maps the specified key, or <tt>null</tt>
* if the map contains no mapping for this key.
*
* @see #put(Object, Object)
*/
public Object get(Object key)
{
Object k = maskNull(key);
int h = hash(k);
Entry[] tab = getTable();
int index = indexFor(h, tab.length);
Entry e = tab[index];
while (e != null)
{
if (e.hash == h && eq(k, e.get()))
return e.value;
e = e.next;
}
return null;
}
/**
* Returns <tt>true</tt> if this map contains a mapping for the specified
* key.
*
* @param key
* The key whose presence in this map is to be tested
* @return
* <tt>true</tt> if there is a mapping for <tt>key</tt>;
* <tt>false</tt> otherwise
*/
public boolean containsKey(Object key)
{
return getEntry(key) != null;
}
/**
* Returns the entry associated with the specified key in the map.
* Returns null if the map contains no mapping for this key.
*/
Entry getEntry(Object key)
{
Object k = maskNull(key);
int h = hash(k);
Entry[] tab = getTable();
int index = indexFor(h, tab.length);
Entry e = tab[index];
while (e != null && !(e.hash == h && eq(k, e.get())))
e = e.next;
return e;
}
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for this key, the old value is
* replaced.
*
* @param key
* key with which the specified value is to be associated.
* @param value
* value to be associated with the specified key.
* @return
* previous value associated with specified key, or <tt>null</tt> if
* there was no mapping for key. A <tt>null</tt> return can also
* indicate that the map previously associated <tt>null</tt> with the
* specified key.
*/
public Object put(Object key, Object value)
{
Object k = maskNull(key);
int h = hash(k);
Entry[] tab = getTable();
int i = indexFor(h, tab.length);
for (Entry e = tab[i]; e != null; e = e.next)
{
if (h == e.hash && eq(k, e.get()))
{
Object oldValue = e.value;
if (value != oldValue)
e.value = value;
return oldValue;
}
}
modCount++;
tab[i] = new Entry(k, value, queue, h, tab[i]);
if (++size >= threshold)
resize(tab.length * 2);
return null;
}
/**
* Rehashes the contents of this map into a new array with a
* larger capacity.
* This method is called automatically when the number of keys in this map
* reaches its threshold.
* <p>
* If current capacity is MAXIMUM_CAPACITY, this method does not resize the
* map, but but sets threshold to Integer.MAX_VALUE.
* This has the effect of preventing future calls.
*
* @param newCapacity
* the new capacity, MUST be a power of two; must be greater than
* current capacity unless current capacity is MAXIMUM_CAPACITY (in
* which case value is irrelevant).
*/
void resize(int newCapacity)
{
Entry[] oldTable = getTable();
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY)
{
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(oldTable, newTable);
table = newTable;
/*
* If ignoring null elements and processing ref queue caused massive
* shrinkage, then restore old table. This should be rare, but avoids
* unbounded expansion of garbage-filled tables.
*/
if (size >= threshold / 2)
threshold = (int)(newCapacity * loadFactor);
else
{
expungeStaleEntries();
transfer(newTable, oldTable);
table = oldTable;
}
}
/** Transfer all entries from src to dest tables. */
private void transfer(Entry[] src, Entry[] dest)
{
for (int j = 0; j < src.length; ++j)
{
Entry e = src[j];
src[j] = null;
while (e != null)
{
Entry next = e.next;
Object key = e.get();
if (key == null)
{
e.next = null; // Help GC
e.value = null; // " "
size--;
}
else
{
int i = indexFor(e.hash, dest.length);
e.next = dest[i];
dest[i] = e;
}
e = next;
}
}
}
/**
* Copies all of the mappings from the specified map to this map.
* These mappings will replace any mappings that this map had for any of the
* keys currently in the specified map.
*
* @param m
* mappings to be stored in this map.
* @throws NullPointerException
* if the specified map is null.
*/
public void putAll(Map m)
{
int numKeysToBeAdded = m.size();
if (numKeysToBeAdded == 0)
return;
/*
* Expand the map if the map if the number of mappings to be added
* is greater than or equal to threshold. This is conservative; the
* obvious condition is (m.size() + size) >= threshold, but this
* condition could result in a map with twice the appropriate capacity,
* if the keys to be added overlap with the keys already in this map.
* By using the conservative calculation, we subject ourself
* to at most one extra resize.
*/
if (numKeysToBeAdded > threshold)
{
int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);
if (targetCapacity > MAXIMUM_CAPACITY)
targetCapacity = MAXIMUM_CAPACITY;
int newCapacity = table.length;
while (newCapacity < targetCapacity)
newCapacity <<= 1;
if (newCapacity > table.length)
resize(newCapacity);
}
for (Iterator i = m.entrySet().iterator(); i.hasNext();)
{
Map.Entry e = (Map.Entry)i.next();
put(e.getKey(), e.getValue());
}
}
/**
* Removes the mapping for this key from this map if present.
*
* @param key
* key whose mapping is to be removed from the map.
* @return
* previous value associated with specified key, or <tt>null</tt> if
* there was no mapping for key. A <tt>null</tt> return can also
* indicate that the map previously associated <tt>null</tt> with the
* specified key.
*/
public Object remove(Object key)
{
Object k = maskNull(key);
int h = hash(k);
Entry[] tab = getTable();
int i = indexFor(h, tab.length);
Entry prev = tab[i];
Entry e = prev;
while (e != null)
{
Entry next = e.next;
if (h == e.hash && eq(k, e.get()))
{
modCount++;
size--;
if (prev == e)
tab[i] = next;
else
prev.next = next;
return e.value;
}
prev = e;
e = next;
}
return null;
}
/** Special version of remove needed by Entry set. */
Entry removeMapping(Object o)
{
if (!(o instanceof Map.Entry))
return null;
Entry[] tab = getTable();
Map.Entry entry = (Map.Entry)o;
Object k = maskNull(entry.getKey());
int h = hash(k);
int i = indexFor(h, tab.length);
Entry prev = tab[i];
Entry e = prev;
while (e != null)
{
Entry next = e.next;
if (h == e.hash && e.equals(entry))
{
modCount++;
size--;
if (prev == e)
tab[i] = next;
else
prev.next = next;
return e;
}
prev = e;
e = next;
}
return null;
}
/**
* Removes all mappings from this map.
*/
public void clear()
{
/*
* Clear out ref queue. We don"t need to expunge entries since table
* is getting cleared.
*/
while (queue.poll() != null)
;
modCount++;
Entry tab[] = table;
for (int i = 0; i < tab.length; ++i)
tab[i] = null;
size = 0;
/*
* Allocation of array may have caused GC, which may have caused
* additional entries to go stale. Removing these entries from the
* reference queue will make them eligible for reclamation.
*/
while (queue.poll() != null)
;
}
/**
* Returns <tt>true</tt> if this map maps one or more keys to the specified
* value.
*
* @param value
* value whose presence in this map is to be tested.
* @return
* <tt>true</tt> if this map maps one or more keys to the specified
* value.
*/
public boolean containsValue(Object value)
{
if (value == null)
return containsNullValue();
Entry tab[] = getTable();
for (int i = tab.length; i-- > 0;)
{
for (Entry e = tab[i]; e != null; e = e.next)
{
if (value.equals(e.value))
return true;
}
}
return false;
}
/**
* Special-case code for containsValue with null argument.
*/
private boolean containsNullValue()
{
Entry tab[] = getTable();
for (int i = tab.length; i-- > 0;)
{
for (Entry e = tab[i]; e != null; e = e.next)
{
if (e.value == null)
return true;
}
}
return false;
}
/**
* The entries in this hash table extend SoftReference, using its main ref
* field as the key.
*/
private static class Entry extends SoftReference implements Map.Entry
{
private Object value;
private final int hash;
private Entry next;
/**
* Create new entry.
*/
Entry(Object key, Object value, ReferenceQueue queue, int hash, Entry next)
{
super(key, queue);
this.value = value;
this.hash = hash;
this.next = next;
}
public Object getKey()
{
return unmaskNull(get());
}
public Object getValue()
{
return value;
}
public Object setValue(Object newValue)
{
Object oldValue = value;
value = newValue;
return oldValue;
}
public boolean equals(Object o)
{
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2)))
{
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
public int hashCode()
{
Object k = getKey();
Object v = getValue();
return ((k==null ? 0 : k.hashCode()) ^
(v==null ? 0 : v.hashCode()));
}
public String toString()
{
return getKey() + "=" + getValue();
}
}
private abstract class HashIterator implements Iterator
{
int index;
Entry entry = null;
Entry lastReturned = null;
int expectedModCount = modCount;
/**
* Strong reference needed to avoid disappearance of key
* between hasNext and next
*/
Object nextKey = null;
/**
* Strong reference needed to avoid disappearance of key
* between nextEntry() and any use of the entry
*/
Object currentKey = null;
HashIterator()
{
index = (size() != 0 ? table.length : 0);
}
public boolean hasNext()
{
Entry[] t = table;
while (nextKey == null)
{
Entry e = entry;
int i = index;
while (e == null && i > 0)
e = t[--i];
entry = e;
index = i;
if (e == null)
{
currentKey = null;
return false;
}
nextKey = e.get(); // hold on to key in strong ref
if (nextKey == null)
entry = entry.next;
}
return true;
}
/** The common parts of next() across different types of iterators */
protected Entry nextEntry()
{
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (nextKey == null && !hasNext())
throw new NoSuchElementException();
lastReturned = entry;
entry = entry.next;
currentKey = nextKey;
nextKey = null;
return lastReturned;
}
public void remove()
{
if (lastReturned == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
SoftHashMap.this.remove(currentKey);
expectedModCount = modCount;
lastReturned = null;
currentKey = null;
}
}
private class ValueIterator extends HashIterator
{
public Object next()
{
return nextEntry().value;
}
}
private class KeyIterator extends HashIterator
{
public Object next()
{
return nextEntry().getKey();
}
}
private class EntryIterator extends HashIterator
{
public Object next()
{
return nextEntry();
}
}
// Views
private transient Set keySet = null;
private transient Collection values = null;
private transient Set entrySet = null;
/**
* Returns a set view of the keys contained in this map.
* The set is backed by the map, so changes to the map are reflected in the
* set, and vice-versa.
* The set supports element removal, which removes the corresponding mapping
* from this map, via the <tt>Iterator.remove</tt>, <tt>Set.remove</tt>,
* <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations.
* It does not support the <tt>add</tt> or <tt>addAll</tt> operations.
*
* @return
* a set view of the keys contained in this map.
*/
public Set keySet()
{
Set ks = keySet;
return (ks != null ? ks : (keySet = new KeySet()));
}
private class KeySet extends AbstractSet
{
public Iterator iterator()
{
return new KeyIterator();
}
public int size()
{
return SoftHashMap.this.size();
}
public boolean contains(Object o)
{
return containsKey(o);
}
public boolean remove(Object o)
{
if (containsKey(o))
{
SoftHashMap.this.remove(o);
return true;
}
else
return false;
}
public void clear()
{
SoftHashMap.this.clear();
}
public Object[] toArray()
{
Collection c = new ArrayList(size());
for (Iterator i = iterator(); i.hasNext(); )
c.add(i.next());
return c.toArray();
}
public Object[] toArray(Object a[])
{
Collection c = new ArrayList(size());
for (Iterator i = iterator(); i.hasNext(); )
c.add(i.next());
return c.toArray(a);
}
}
/**
* Returns a collection view of the values contained in this map.
* The collection is backed by the map, so changes to the map are reflected
* in the collection, and vice-versa.
* The collection supports element removal, which removes the corresponding
* mapping from this map, via the <tt>Iterator.remove</tt>,
* <tt>Collection.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt>, and
* <tt>clear</tt> operations.
* It does not support the <tt>add</tt> or <tt>addAll</tt> operations.
*
* @return
* a collection view of the values contained in this map.
*/
public Collection values()
{
Collection vs = values;
return (vs != null ? vs : (values = new Values()));
}
private class Values extends AbstractCollection
{
public Iterator iterator()
{
return new ValueIterator();
}
public int size()
{
return SoftHashMap.this.size();
}
public boolean contains(Object o)
{
return containsValue(o);
}
public void clear()
{
SoftHashMap.this.clear();
}
public Object[] toArray()
{
Collection c = new ArrayList(size());
for (Iterator i = iterator(); i.hasNext(); )
c.add(i.next());
return c.toArray();
}
public Object[] toArray(Object a[])
{
Collection c = new ArrayList(size());
for (Iterator i = iterator(); i.hasNext(); )
c.add(i.next());
return c.toArray(a);
}
}
/**
* Returns a collection view of the mappings contained in this map.
* Each element in the returned collection is a <tt>Map.Entry</tt>.
* The collection is backed by the map, so changes to the map are reflected
* in the collection, and vice-versa.
* The collection supports element removal, which removes the corresponding
* mapping from the map, via the <tt>Iterator.remove</tt>,
* <tt>Collection.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt>, and
* <tt>clear</tt> operations.
* It does not support the <tt>add</tt> or <tt>addAll</tt> operations.
*
* @return
* a collection view of the mappings contained in this map.
*/
public Set entrySet()
{
Set es = entrySet;
return (es != null ? es : (entrySet = new EntrySet()));
}
private class EntrySet extends AbstractSet
{
public Iterator iterator()
{
return new EntryIterator();
}
public boolean contains(Object o)
{
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
Entry candidate = getEntry(e.getKey());
return candidate != null && candidate.equals(e);
}
public boolean remove(Object o)
{
return removeMapping(o) != null;
}
public int size()
{
return SoftHashMap.this.size();
}
public void clear()
{
SoftHashMap.this.clear();
}
public Object[] toArray()
{
Collection c = new ArrayList(size());
for (Iterator i = iterator(); i.hasNext();)
c.add(new SimpleEntry((Map.Entry)i.next()));
return c.toArray();
}
public Object[] toArray(Object a[])
{
Collection c = new ArrayList(size());
for (Iterator i = iterator(); i.hasNext();)
c.add(new SimpleEntry((Map.Entry)i.next()));
return c.toArray(a);
}
}
static class SimpleEntry implements Map.Entry
{
private Object key;
private Object value;
public SimpleEntry(Map.Entry e)
{
this.key = e.getKey();
this.value = e.getValue();
}
public Object getKey()
{
return key;
}
public Object getValue()
{
return value;
}
public Object setValue(Object value)
{
Object oldValue = this.value;
this.value = value;
return oldValue;
}
public boolean equals(Object o)
{
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
return eq(key, e.getKey()) && eq(value, e.getValue());
}
public int hashCode()
{
return ((key == null) ? 0 : key.hashCode()) ^
((value == null) ? 0 : value.hashCode());
}
public String toString()
{
return key + "=" + value;
}
private static boolean eq(Object o1, Object o2)
{
return o1 == null ? o2 == null : o1.equals(o2);
}
}
}
A java.util.Map interface which can only hold a single object
/*
* $Id: MicroMap.java 458489 2006-01-04 09:28:14Z ivaynberg $ $Revision:
* 1.4 $ $Date: 2006-01-04 10:28:14 +0100 (Wed, 04 Jan 2006) $
*
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
import java.io.Serializable;
import java.util.AbstractList;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* An implementation of the java.util.Map interface which can only hold a single
* object. This is particularly useful to control memory usage in Wicket because
* many containers hold only a single component.
*
* @author Jonathan Locke
*/
public final class MicroMap implements Map, Serializable
{
private static final long serialVersionUID = 1L;
/** The maximum number of entries this map supports. */
public static final int MAX_ENTRIES = 1;
/** The one and only key in this tiny map */
private Object key;
/** The value for the only key in this tiny map */
private Object value;
/**
* Constructor
*/
public MicroMap()
{
}
/**
* Constructs map with a single key and value pair.
*
* @param key
* The key
* @param value
* The value
*/
public MicroMap(final Object key, final Object value)
{
put(key, value);
}
/**
* @return True if this MicroMap is full
*/
public boolean isFull()
{
return size() == MAX_ENTRIES;
}
/**
* @see java.util.Map#size()
*/
public int size()
{
return (key != null) ? 1 : 0;
}
/**
* @see java.util.Map#isEmpty()
*/
public boolean isEmpty()
{
return size() == 0;
}
/**
* @see java.util.Map#containsKey(java.lang.Object)
*/
public boolean containsKey(final Object key)
{
return key.equals(this.key);
}
/**
* @see java.util.Map#containsValue(java.lang.Object)
*/
public boolean containsValue(final Object value)
{
return value.equals(this.value);
}
/**
* @see java.util.Map#get(java.lang.Object)
*/
public Object get(final Object key)
{
if (key.equals(this.key))
{
return value;
}
return null;
}
/**
* @see java.util.Map#put(java.lang.Object, java.lang.Object)
*/
public Object put(final Object key, final Object value)
{
// Replace?
if (key.equals(this.key))
{
final Object oldValue = this.value;
this.value = value;
return oldValue;
}
else
{
// Is there room for a new entry?
if (size() < MAX_ENTRIES)
{
// Store
this.key = key;
this.value = value;
return null;
}
else
{
throw new IllegalStateException("Map full");
}
}
}
/**
* @see java.util.Map#remove(java.lang.Object)
*/
public Object remove(final Object key)
{
if (key.equals(this.key))
{
final Object oldValue = this.value;
this.key = null;
this.value = null;
return oldValue;
}
return null;
}
/**
* @see java.util.Map#putAll(java.util.Map)
*/
public void putAll(final Map map)
{
if (map.size() <= MAX_ENTRIES)
{
final Map.Entry e = (Map.Entry)map.entrySet().iterator().next();
put(e.getKey(), e.getValue());
}
else
{
throw new IllegalStateException("Map full. Cannot add " + map.size() + " entries");
}
}
/**
* @see java.util.Map#clear()
*/
public void clear()
{
key = null;
value = null;
}
/**
* @see java.util.Map#keySet()
*/
public Set keySet()
{
return new AbstractSet()
{
public Iterator iterator()
{
return new Iterator()
{
public boolean hasNext()
{
return index < MicroMap.this.size();
}
public Object next()
{
index++;
return key;
}
public void remove()
{
MicroMap.this.clear();
}
int index;
};
}
public int size()
{
return MicroMap.this.size();
}
};
}
/**
* @see java.util.Map#values()
*/
public Collection values()
{
return new AbstractList()
{
public Object get(final int index)
{
return value;
}
public int size()
{
return MicroMap.this.size();
}
};
}
/**
* @see java.util.Map#entrySet()
*/
public Set entrySet()
{
return new AbstractSet()
{
public Iterator iterator()
{
return new Iterator()
{
public boolean hasNext()
{
return index < MicroMap.this.size();
}
public Object next()
{
index++;
return new Map.Entry()
{
public Object getKey()
{
return key;
}
public Object getValue()
{
return value;
}
public Object setValue(final Object value)
{
final Object oldValue = MicroMap.this.value;
MicroMap.this.value = value;
return oldValue;
}
};
}
public void remove()
{
clear();
}
int index = 0;
};
}
public int size()
{
return MicroMap.this.size();
}
};
}
}
A Map collection with real-time behavior
/*
* J.A.D.E. Java(TM) Addition to Default Environment.
* Latest release available at http://jade.dautelle.ru/
* This class is public domain (not copyrighted).
*
* This class was added from J.A.D.E. directly to avoid adding the jade.jar
* which was causing a conflict with parsing XML menus in FreeHep for some reason
*/
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* <p> This class represents a <code>Map</code> collection with real-time
* behavior. Unless the map"s size exceeds its current capacity,
* no dynamic memory allocation is ever performed and response time is
* <b>extremely fast</b> and <b>consistent</b>.</p>
*
* <p> Our
* @version 6.0, January 18 2004
*/
public class FastMap implements Map, Cloneable, Serializable {
/**
* Holds the map"s hash table.
*/
private transient EntryImpl[] _entries;
/**
* Holds the map"s current capacity.
*/
private transient int _capacity;
/**
* Holds the hash code mask.
*/
private transient int _mask;
/**
* Holds the first pool entry (linked list).
*/
private transient EntryImpl _poolFirst;
/**
* Holds the first map entry (linked list).
*/
private transient EntryImpl _mapFirst;
/**
* Holds the last map entry (linked list).
*/
private transient EntryImpl _mapLast;
/**
* Holds the current size.
*/
private transient int _size;
/**
* Creates a {@link FastMap} with a capacity of <code>16</code> entries.
*/
public FastMap() {
initialize(16);
}
/**
* Creates a {@link FastMap}, copy of the specified <code>Map</code>.
* If the specified map is not an instance of {@link FastMap}, the
* newly created map has a capacity set to the specified map"s size.
* The copy has the same order as the original, regardless of the original
* map"s implementation:<pre>
* TreeMap dictionary = ...;
* FastMap dictionaryLookup = new FastMap(dictionary);
* </pre>
*
* @param map the map whose mappings are to be placed in this map.
*/
public FastMap(Map map) {
int capacity = (map instanceof FastMap) ?
((FastMap)map).capacity() : map.size();
initialize(capacity);
putAll(map);
}
/**
* Creates a {@link FastMap} with the specified capacity. Unless the
* capacity is exceeded, operations on this map do not allocate entries.
* For optimum performance, the capacity should be of the same order
* of magnitude or larger than the expected map"s size.
*
* @param capacity the number of buckets in the hash table; it also
* defines the number of pre-allocated entries.
*/
public FastMap(int capacity) {
initialize(capacity);
}
/**
* Returns the number of key-value mappings in this {@link FastMap}.
*
* @return this map"s size.
*/
public int size() {
return _size;
}
/**
* Returns the capacity of this {@link FastMap}. The capacity defines
* the number of buckets in the hash table, as well as the maximum number
* of entries the map may contain without allocating memory.
*
* @return this map"s capacity.
*/
public int capacity() {
return _capacity;
}
/**
* Indicates if this {@link FastMap} contains no key-value mappings.
*
* @return <code>true</code> if this map contains no key-value mappings;
* <code>false</code> otherwise.
*/
public boolean isEmpty() {
return _size == 0;
}
/**
* Indicates if this {@link FastMap} contains a mapping for the specified
* key.
*
* @param key the key whose presence in this map is to be tested.
* @return <code>true</code> if this map contains a mapping for the
* specified key; <code>false</code> otherwise.
* @throws NullPointerException if the key is <code>null</code>.
*/
public boolean containsKey(Object key) {
EntryImpl entry = _entries[keyHash(key) & _mask];
while (entry != null) {
if (key.equals(entry._key) ) {
return true;
}
entry = entry._next;
}
return false;
}
/**
* Indicates if this {@link FastMap} maps one or more keys to the
* specified value.
*
* @param value the value whose presence in this map is to be tested.
* @return <code>true</code> if this map maps one or more keys to the
* specified value.
* @throws NullPointerException if the key is <code>null</code>.
*/
public boolean containsValue(Object value) {
EntryImpl entry = _mapFirst;
while (entry != null) {
if (value.equals(entry._value) ) {
return true;
}
entry = entry._after;
}
return false;
}
/**
* Returns the value to which this {@link FastMap} maps the specified key.
*
* @param key the key whose associated value is to be returned.
* @return the value to which this map maps the specified key,
* or <code>null</code> if there is no mapping for the key.
* @throws NullPointerException if key is <code>null</code>.
*/
public Object get(Object key) {
EntryImpl entry = _entries[keyHash(key) & _mask];
while (entry != null) {
if (key.equals(entry._key) ) {
return entry._value;
}
entry = entry._next;
}
return null;
}
/**
* Returns the entry with the specified key.
*
* @param key the key whose associated entry is to be returned.
* @return the entry for the specified key or <code>null</code> if none.
*/
public Entry getEntry(Object key) {
EntryImpl entry = _entries[keyHash(key) & _mask];
while (entry != null) {
if (key.equals(entry._key)) {
return entry;
}
entry = entry._next;
}
return null;
}
/**
* Associates the specified value with the specified key in this
* {@link FastMap}. If the {@link FastMap} previously contained a mapping
* for this key, the old value is replaced.
*
* @param key the key with which the specified value is to be associated.
* @param value the value to be associated with the specified key.
* @return the previous value associated with specified key,
* or <code>null</code> if there was no mapping for key.
* A <code>null</code> return can also indicate that the map
* previously associated <code>null</code> with the specified key.
* @throws NullPointerException if the key is <code>null</code>.
*/
public Object put(Object key, Object value) {
EntryImpl entry = _entries[keyHash(key) & _mask];
while (entry != null) {
if (key.equals(entry._key) ) {
Object prevValue = entry._value;
entry._value = value;
return prevValue;
}
entry = entry._next;
}
// No previous mapping.
addEntry(key, value);
return null;
}
/**
* Returns a reusable {@link FastIterator} over this {@link FastMap} entries
* (unique instance per map). For example:<pre>
* // Iteration without memory allocation!
* for (FastIterator i=map.fastIterator().toFirst(); i.hasNext();) {
* Entry entry = i.nextEntry();
* ...
* }</pre>
*
* @return an iterator which can be reset for reuse over and over.
* @see FastMap.FastIterator
*/
public FastIterator fastIterator() {
return _fastIterator;
}
private final FastIterator _fastIterator = new FastIterator();
/**
* Copies all of the mappings from the specified map to this
* {@link FastMap}.
*
* @param map the mappings to be stored in this map.
* @throws NullPointerException the specified map is <code>null</code>, or
* the specified map contains <code>null</code> keys.
*/
public void putAll(Map map) {
for (Iterator i = map.entrySet().iterator(); i.hasNext(); ) {
Entry e = (Entry) i.next();
addEntry(e.getKey(), e.getValue());
}
}
/**
* Removes the mapping for this key from this {@link FastMap} if present.
*
* @param key the key whose mapping is to be removed from the map.
* @return previous value associated with specified key,
* or <code>null</code> if there was no mapping for key.
* A <code>null</code> return can also indicate that the map
* previously associated <code>null</code> with the specified key.
* @throws NullPointerException if the key is <code>null</code>.
*/
public Object remove(Object key) {
EntryImpl entry = _entries[keyHash(key) & _mask];
while (entry != null) {
if (key.equals(entry._key) ) {
Object prevValue = entry._value;
removeEntry(entry);
return prevValue;
}
entry = entry._next;
}
return null;
}
/**
* Removes all mappings from this {@link FastMap}.
*/
public void clear() {
// Clears all keys, values and buckets linked lists.
for (EntryImpl entry = _mapFirst; entry != null; entry = entry._after) {
entry._key = null;
entry._value = null;
entry._before = null;
entry._next = null;
if (entry._previous == null) { // First in bucket.
_entries[entry._index] = null;
} else {
entry._previous = null;
}
}
// Recycles all entries.
if (_mapLast != null) {
_mapLast._after = _poolFirst; // Connects to pool.
_poolFirst = _mapFirst;
_mapFirst = null;
_mapLast = null;
_size = 0;
sizeChanged();
}
}
/**
* Changes the current capacity of this {@link FastMap}. If the capacity
* is increased, new entries are allocated and added to the pool.
* If the capacity is decreased, entries from the pool are deallocated
* (and are garbage collected eventually). The capacity also determined
* the number of buckets for the hash table.
*
* @param newCapacity the new capacity of this map.
*/
public void setCapacity(int newCapacity) {
if (newCapacity > _capacity) { // Capacity increases.
for (int i = _capacity; i < newCapacity; i++) {
EntryImpl entry = new EntryImpl();
entry._after = _poolFirst;
_poolFirst = entry;
}
} else if (newCapacity < _capacity) { // Capacity decreases.
for ( int i = newCapacity;
(i < _capacity) && (_poolFirst != null); i++) {
// Disconnects the entry for gc to do its work.
EntryImpl entry = _poolFirst;
_poolFirst = entry._after;
entry._after = null; // All pointers are now null!
}
}
// Find a power of 2 >= capacity
int tableLength = 16;
while (tableLength < newCapacity) {
tableLength <<= 1;
}
// Checks if the hash table has to be re-sized.
if (_entries.length != tableLength) {
_entries = new EntryImpl[tableLength];
_mask = tableLength - 1;
// Repopulates the hash table.
EntryImpl entry = _mapFirst;
while (entry != null) {
int index = keyHash(entry._key) & _mask;
entry._index = index;
// Connects to bucket.
entry._previous = null; // Resets previous.
EntryImpl next = _entries[index];
entry._next = next;
if (next != null) {
next._previous = entry;
}
_entries[index] = entry;
entry = entry._after;
}
}
_capacity = newCapacity;
}
/**
* Returns a shallow copy of this {@link FastMap}. The keys and
* the values themselves are not cloned.
*
* @return a shallow copy of this map.
*/
public Object clone() {
try {
FastMap clone = (FastMap) super.clone();
clone.initialize(_capacity);
clone.putAll(this);
return clone;
} catch (CloneNotSupportedException e) {
// Should not happen, since we are Cloneable.
throw new InternalError();
}
}
/**
* Compares the specified object with this {@link FastMap} for equality.
* Returns <code>true</code> if the given object is also a map and the two
* maps represent the same mappings (regardless of collection iteration
* order).
*
* @param obj the object to be compared for equality with this map.
* @return <code>true</code> if the specified object is equal to this map;
* <code>false</code> otherwise.
*/
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (obj instanceof Map) {
Map that = (Map) obj;
if (this.size() == that.size()) {
EntryImpl entry = _mapFirst;
while (entry != null) {
if (!that.entrySet().contains(entry)) {
return false;
}
entry = entry._after;
}
return true;
} else {
return false;
}
} else {
return false;
}
}
/**
* Returns the hash code value for this {@link FastMap}.
*
* @return the hash code value for this map.
*/
public int hashCode() {
int code = 0;
EntryImpl entry = _mapFirst;
while (entry != null) {
code += entry.hashCode();
entry = entry._after;
}
return code;
}
/**
* Returns a <code>String</code> representation of this {@link FastMap}.
*
* @return <code>this.entrySet().toString();</code>
*/
public String toString() {
return entrySet().toString();
}
/**
* Returns a collection view of the values contained in this
* {@link FastMap}. The collection is backed by the map, so changes to
* the map are reflected in the collection, and vice-versa.
* The collection supports element removal, which removes the corresponding
* mapping from this map, via the
* <code>Iterator.remove</code>, <code>Collection.remove</code>,
* <code>removeAll</code>, <code>retainAll</code>,
* and <code>clear</code> operations. It does not support the
* <code>add</code> or <code>addAll</code> operations.
*
* @return a collection view of the values contained in this map.
*/
public Collection values() {
return _values;
}
private transient Values _values;
private class Values extends AbstractCollection {
public Iterator iterator() {
return new Iterator() {
EntryImpl after = _mapFirst;
EntryImpl before;
public void remove() {
if (before != null) {
removeEntry(before);
} else {
throw new IllegalStateException();
}
}
public boolean hasNext() {
return after != null;
}
public Object next() {
if (after != null) {
before = after;
after = after._after;
return before._value;
} else {
throw new NoSuchElementException();
}
}
};
}
public int size() {
return _size;
}
public boolean contains(Object o) {
return containsValue(o);
}
public void clear() {
FastMap.this.clear();
}
}
/**
* Returns a collection view of the mappings contained in this
* {@link FastMap}. Each element in the returned collection is a
* <code>Map.Entry</code>. The collection is backed by the map,
* so changes to the map are reflected in the collection, and vice-versa.
* The collection supports element removal, which removes the corresponding
* mapping from this map, via the
* <code>Iterator.remove</code>, <code>Collection.remove</code>,
* <code>removeAll</code>, <code>retainAll</code>,
* and <code>clear</code> operations. It does not support the
* <code>add</code> or <code>addAll</code> operations.
*
* @return a collection view of the mappings contained in this map.
*/
public Set entrySet() {
return _entrySet;
}
private transient EntrySet _entrySet;
private class EntrySet extends AbstractSet {
public Iterator iterator() {
return new Iterator() {
EntryImpl after = _mapFirst;
EntryImpl before;
public void remove() {
if (before != null) {
removeEntry(before);
} else {
throw new IllegalStateException();
}
}
public boolean hasNext() {
return after != null;
}
public Object next() {
if (after != null) {
before = after;
after = after._after;
return before;
} else {
throw new NoSuchElementException();
}
}
};
}
public int size() {
return _size;
}
public boolean contains(Object obj) { // Optimization.
if (obj instanceof Entry) {
Entry entry = (Entry) obj;
Entry mapEntry = getEntry(entry.getKey());
return entry.equals(mapEntry);
} else {
return false;
}
}
public boolean remove(Object obj) { // Optimization.
if (obj instanceof Entry) {
Entry entry = (Entry)obj;
EntryImpl mapEntry = (EntryImpl) getEntry(entry.getKey());
if ((mapEntry != null) &&
(entry.getValue()).equals(mapEntry._value)) {
removeEntry(mapEntry);
return true;
}
}
return false;
}
}
/**
* Returns a set view of the keys contained in this {@link FastMap}.
* The set is backed by the map, so changes to the map are reflected
* in the set, and vice-versa. The set supports element removal,
* which removes the corresponding mapping from this map, via the
* <code>Iterator.remove</code>, <code>Collection.remove</code>,
* <code>removeAll</code>, <code>retainAll</code>,
* and <code>clear</code> operations. It does not support the
* <code>add</code> or <code>addAll</code> operations.
*
* @return a set view of the keys contained in this map.
*/
public Set keySet() {
return _keySet;
}
private transient KeySet _keySet;
private class KeySet extends AbstractSet {
public Iterator iterator() {
return new Iterator() {
EntryImpl after = _mapFirst;
EntryImpl before;
public void remove() {
if (before != null) {
removeEntry(before);
} else {
throw new IllegalStateException();
}
}
public boolean hasNext() {
return after != null;
}
public Object next() {
if (after != null) {
before = after;
after = after._after;
return before._key;
} else {
throw new NoSuchElementException();
}
}
};
}
public int size() {
return _size;
}
public boolean contains(Object obj) { // Optimization.
return FastMap.this.containsKey(obj);
}
public boolean remove(Object obj) { // Optimization.
return FastMap.this.remove(obj) != null;
}
public void clear() { // Optimization.
FastMap.this.clear();
}
}
/**
* This methods is being called when the size of this {@link FastMap}
* has changed. The default behavior is to double the map"s capacity
* when the map"s size exceeds the current map"s capacity.
* Sub-class may override this method to implement custom resizing
* policies or to disable automatic resizing. For example:<pre>
* Map fixedCapacityMap = new FastMap(256) {
* protected sizeChanged() {
* // Do nothing, automatic resizing disabled.
* }
* };</pre>
* @see #setCapacity
*/
protected void sizeChanged() {
if (size() > capacity()) {
setCapacity(capacity() * 2);
}
}
/**
* Returns the hash code for the specified key. The formula being used
* is identical to the formula used by <code>java.util.HashMap</code>
* (ensures similar behavior for ill-conditioned hashcode keys).
*
* @param key the key to calculate the hashcode for.
* @return the hash code for the specified key.
*/
private static int keyHash(Object key) {
// From HashMap.hash(Object) function.
int hashCode = key.hashCode();
hashCode += ~(hashCode << 9);
hashCode ^= (hashCode >>> 14);
hashCode += (hashCode << 4);
hashCode ^= (hashCode >>> 10);
return hashCode;
}
/**
* Adds a new entry for the specified key and value.
* @param key the entry"s key.
* @param value the entry"s value.
*/
private void addEntry(Object key, Object value) {
EntryImpl entry = _poolFirst;
if (entry != null) {
_poolFirst = entry._after;
entry._after = null;
} else { // Pool empty.
entry = new EntryImpl();
}
// Setup entry parameters.
entry._key = key;
entry._value = value;
int index = keyHash(key) & _mask;
entry._index = index;
// Connects to bucket.
EntryImpl next = _entries[index];
entry._next = next;
if (next != null) {
next._previous = entry;
}
_entries[index] = entry;
// Connects to collection.
if (_mapLast != null) {
entry._before = _mapLast;
_mapLast._after = entry;
} else {
_mapFirst = entry;
}
_mapLast = entry;
// Updates size.
_size++;
sizeChanged();
}
/**
* Removes the specified entry from the map.
*
* @param entry the entry to be removed.
*/
private void removeEntry(EntryImpl entry) {
// Removes from bucket.
EntryImpl previous = entry._previous;
EntryImpl next = entry._next;
if (previous != null) {
previous._next = next;
entry._previous = null;
} else { // First in bucket.
_entries[entry._index] = next;
}
if (next != null) {
next._previous = previous;
entry._next = null;
} // Else do nothing, no last pointer.
// Removes from collection.
EntryImpl before = entry._before;
EntryImpl after = entry._after;
if (before != null) {
before._after = after;
entry._before = null;
} else { // First in collection.
_mapFirst = after;
}
if (after != null) {
after._before = before;
} else { // Last in collection.
_mapLast = before;
}
// Clears value and key.
entry._key = null;
entry._value = null;
// Recycles.
entry._after = _poolFirst;
_poolFirst = entry;
// Updates size.
_size--;
sizeChanged();
}
/**
* Initializes this instance for the specified capacity.
* Once initialized, operations on this map should not create new objects
* (unless the map"s size exceeds the specified capacity).
*
* @param capacity the initial capacity.
*/
private void initialize(int capacity) {
// Find a power of 2 >= capacity
int tableLength = 16;
while (tableLength < capacity) {
tableLength <<= 1;
}
// Allocates hash table.
_entries = new EntryImpl[tableLength];
_mask = tableLength - 1;
_capacity = capacity;
_size = 0;
// Allocates views.
_values = new Values();
_entrySet = new EntrySet();
_keySet = new KeySet();
// Resets pointers.
_poolFirst = null;
_mapFirst = null;
_mapLast = null;
// Allocates entries.
for (int i=0; i < capacity; i++) {
EntryImpl entry = new EntryImpl();
entry._after = _poolFirst;
_poolFirst = entry;
}
}
/**
* Requires special handling during de-serialization process.
*
* @param stream the object input stream.
* @throws IOException if an I/O error occurs.
* @throws ClassNotFoundException if the class for the object de-serialized
* is not found.
*/
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException {
int capacity = stream.readInt();
initialize(capacity);
int size = stream.readInt();
for (int i=0; i < size; i++) {
Object key = stream.readObject();
Object value = stream.readObject();
addEntry(key, value);
}
}
/**
* Requires special handling during serialization process.
*
* @param stream the object output stream.
* @throws IOException if an I/O error occurs.
*/
private void writeObject(ObjectOutputStream stream) throws IOException {
stream.writeInt(_capacity);
stream.writeInt(_size);
int count = 0;
EntryImpl entry = _mapFirst;
while (entry != null) {
stream.writeObject(entry._key);
stream.writeObject(entry._value);
count++;
entry = entry._after;
}
if (count != _size) {
throw new IOException("FastMap Corrupted");
}
}
/**
* This inner class represents a reusable list iterator over
* {@link FastMap} entries. This iterator is bi-directional and can be
* directly moved to the {@link #toFirst first} or {@link #toLast last}
* entry. For example:<pre>
* for (FastIterator i=map.fastIterator().toFirst(); i.hasNext();) {
* Entry entry = i.nextEntry();
* ...
* }</pre>
* {@link #set setting} or {@link #add adding} new entries is not
* supported.
*/
public final class FastIterator implements ListIterator {
EntryImpl after = _mapFirst;
EntryImpl before;
int nextIndex = 0;
public FastIterator toFirst() {
after = _mapFirst;
before = null;
nextIndex = 0;
return this;
}
public FastIterator toLast() {
after = null;
before = _mapLast;
nextIndex = _size;
return this;
}
public boolean hasNext() {
return after != null;
}
public Entry nextEntry() {
if (after != null) {
nextIndex++;
before = after;
after = after._after;
return before;
} else {
throw new NoSuchElementException();
}
}
public Object next() {
return nextEntry();
}
public boolean hasPrevious() {
return before != null;
}
public Entry previousEntry() {
if (before != null) {
nextIndex--;
after = before;
before = before._after;
return after;
} else {
throw new NoSuchElementException();
}
}
public Object previous() {
return previousEntry();
}
public int nextIndex() {
return nextIndex;
}
public int previousIndex() {
return nextIndex - 1;
}
public void remove() {
if (before != null) {
removeEntry(before);
} else {
throw new IllegalStateException();
}
}
public void set(Object o) {
throw new UnsupportedOperationException();
}
public void add(Object o) {
throw new UnsupportedOperationException();
}
}
/**
* This class represents a {@link FastMap} entry.
*/
private static final class EntryImpl implements Entry {
/**
* Holds the entry key (null when in pool).
*/
private Object _key;
/**
* Holds the entry value (null when in pool).
*/
private Object _value;
/**
* Holds the bucket index (undefined when in pool).
*/
private int _index;
/**
* Holds the previous entry in the same bucket (null when in pool).
*/
private EntryImpl _previous;
/**
* Holds the next entry in the same bucket (null when in pool).
*/
private EntryImpl _next;
/**
* Holds the entry added before this entry (null when in pool).
*/
private EntryImpl _before;
/**
* Holds the entry added after this entry
* or the next available entry when in pool.
*/
private EntryImpl _after;
/**
* Returns the key for this entry.
*
* @return the entry"s key.
*/
public Object getKey() {
return _key;
}
/**
* Returns the value for this entry.
*
* @return the entry"s value.
*/
public Object getValue() {
return _value;
}
/**
* Sets the value for this entry.
*
* @param value the new value.
* @return the previous value.
*/
public Object setValue(Object value) {
Object old = _value;
_value = value;
return old;
}
/**
* Indicates if this entry is considered equals to the specified
* entry.
*
* @param that the object to test for equality.
* @return <code>true<code> if both entry are considered equal;
* <code>false<code> otherwise.
*/
public boolean equals(Object that) {
if (that instanceof Entry) {
Entry entry = (Entry) that;
return (_key.equals(entry.getKey())) &&
((_value != null) ?
_value.equals(entry.getValue()) :
(entry.getValue() == null));
} else {
return false;
}
}
/**
* Returns the hash code for this entry.
*
* @return this entry"s hash code.
*/
public int hashCode() {
return _key.hashCode() ^ ((_value != null) ? _value.hashCode() : 0);
}
/**
* Returns the text representation of this entry.
*
* @return this entry"s textual representation.
*/
public String toString() {
return _key + "=" + _value;
}
}
}
A Map that accepts int or Integer keys only
/*
* Copyright 2005 Brian S O"Neill
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.io.IOException;
import java.io.Serializable;
/**
* A Map that accepts int or Integer keys only. This class is not thread-safe.
*
* @author Brian S O"Neill
*/
public class IntHashMap<V> extends AbstractMap<Integer, V>
implements Map<Integer, V>, Cloneable, Serializable
{
/**
* The hash table data.
*/
private transient Entry<V> table[];
/**
* The total number of mappings in the hash table.
*/
private transient int count;
/**
* The table is rehashed when its size exceeds this threshold. (The
* value of this field is (int)(capacity * loadFactor).)
*
* @serial
*/
private int threshold;
/**
* The load factor for the hashtable.
*
* @serial
*/
private float loadFactor;
/**
* The number of times this IntHashMap has been structurally modified
* Structural modifications are those that change the number of mappings in
* the IntHashMap or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the IntHashMap fail-fast. (See ConcurrentModificationException).
*/
private transient int modCount = 0;
/**
* Constructs a new, empty map with the specified initial
* capacity and the specified load factor.
*
* @param initialCapacity the initial capacity of the IntHashMap.
* @param loadFactor the load factor of the IntHashMap
* @throws IllegalArgumentException if the initial capacity is less
* than zero, or if the load factor is nonpositive.
*/
public IntHashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0) {
throw new IllegalArgumentException("Illegal Initial Capacity: "+
initialCapacity);
}
if (loadFactor <= 0) {
throw new IllegalArgumentException("Illegal Load factor: "+
loadFactor);
}
if (initialCapacity==0) {
initialCapacity = 1;
}
this.loadFactor = loadFactor;
table = new Entry[initialCapacity];
threshold = (int)(initialCapacity * loadFactor);
}
/**
* Constructs a new, empty map with the specified initial capacity
* and default load factor, which is <tt>0.75</tt>.
*
* @param initialCapacity the initial capacity of the IntHashMap.
* @throws IllegalArgumentException if the initial capacity is less
* than zero.
*/
public IntHashMap(int initialCapacity) {
this(initialCapacity, 0.75f);
}
/**
* Constructs a new, empty map with a default capacity and load
* factor, which is <tt>0.75</tt>.
*/
public IntHashMap() {
this(101, 0.75f);
}
/**
* Constructs a new map with the same mappings as the given map. The
* map is created with a capacity of twice the number of mappings in
* the given map or 11 (whichever is greater), and a default load factor,
* which is <tt>0.75</tt>.
*/
public IntHashMap(Map<? extends Integer, ? extends V> t) {
this(Math.max(2 * t.size(), 11), 0.75f);
putAll(t);
}
/**
* Returns the number of key-value mappings in this map.
*
* @return the number of key-value mappings in this map.
*/
public int size() {
return count;
}
/**
* Returns <tt>true</tt> if this map contains no key-value mappings.
*
* @return <tt>true</tt> if this map contains no key-value mappings.
*/
public boolean isEmpty() {
return count == 0;
}
/**
* Returns <tt>true</tt> if this map maps one or more keys to the
* specified value.
*
* @param value value whose presence in this map is to be tested.
* @return <tt>true</tt> if this map maps one or more keys to the
* specified value.
*/
public boolean containsValue(Object value) {
Entry tab[] = table;
if (value == null) {
for (int i = tab.length ; i-- > 0 ;) {
for (Entry e = tab[i] ; e != null ; e = e.next) {
if (e.value == null) {
return true;
}
}
}
} else {
for (int i = tab.length ; i-- > 0 ;) {
for (Entry e = tab[i] ; e != null ; e = e.next) {
if (value.equals(e.value)) {
return true;
}
}
}
}
return false;
}
/**
* Returns <tt>true</tt> if this map contains a mapping for the specified
* key.
*
* @return <tt>true</tt> if this map contains a mapping for the specified
* key.
* @param key key whose presence in this Map is to be tested.
*/
public boolean containsKey(Integer key) {
return containsKey(key.intValue());
}
/**
* Returns <tt>true</tt> if this map contains a mapping for the specified
* key.
*
* @return <tt>true</tt> if this map contains a mapping for the specified
* key.
* @param key key whose presence in this Map is to be tested.
*/
public boolean containsKey(int key) {
Entry tab[] = table;
int index = (key & 0x7fffffff) % tab.length;
for (Entry e = tab[index]; e != null; e = e.next) {
if (e.key == key) {
return true;
}
}
return false;
}
/**
* Returns the value to which this map maps the specified key. Returns
* <tt>null</tt> if the map contains no mapping for this key. A return
* value of <tt>null</tt> does not <i>necessarily</i> indicate that the
* map contains no mapping for the key; it"s also possible that the map
* explicitly maps the key to <tt>null</tt>. The <tt>containsKey</tt>
* operation may be used to distinguish these two cases.
*
* @return the value to which this map maps the specified key.
* @param key key whose associated value is to be returned.
*/
public V get(Integer key) {
return get(key.intValue());
}
/**
* Returns the value to which this map maps the specified key. Returns
* <tt>null</tt> if the map contains no mapping for this key. A return
* value of <tt>null</tt> does not <i>necessarily</i> indicate that the
* map contains no mapping for the key; it"s also possible that the map
* explicitly maps the key to <tt>null</tt>. The <tt>containsKey</tt>
* operation may be used to distinguish these two cases.
*
* @return the value to which this map maps the specified key.
* @param key key whose associated value is to be returned.
*/
public V get(int key) {
Entry<V> tab[] = table;
int index = (key & 0x7fffffff) % tab.length;
for (Entry<V> e = tab[index]; e != null; e = e.next) {
if (e.key == key) {
return e.value;
}
}
return null;
}
/**
* Rehashes the contents of this map into a new <tt>IntHashMap</tt> instance
* with a larger capacity. This method is called automatically when the
* number of keys in this map exceeds its capacity and load factor.
*/
private void rehash() {
int oldCapacity = table.length;
Entry oldMap[] = table;
int newCapacity = oldCapacity * 2 + 1;
Entry<V> newMap[] = new Entry[newCapacity];
modCount++;
threshold = (int)(newCapacity * loadFactor);
table = newMap;
for (int i = oldCapacity ; i-- > 0 ;) {
for (Entry old = oldMap[i] ; old != null ; ) {
Entry e = old;
old = old.next;
int index = (e.key & 0x7fffffff) % newCapacity;
e.next = newMap[index];
newMap[index] = e;
}
}
}
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for this key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated.
* @param value value to be associated with the specified key.
* @return previous value associated with specified key, or <tt>null</tt>
* if there was no mapping for key. A <tt>null</tt> return can
* also indicate that the IntHashMap previously associated
* <tt>null</tt> with the specified key.
*/
public V put(Integer key, V value) {
return put(key.intValue(), value);
}
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for this key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated.
* @param value value to be associated with the specified key.
* @return previous value associated with specified key, or <tt>null</tt>
* if there was no mapping for key. A <tt>null</tt> return can
* also indicate that the IntHashMap previously associated
* <tt>null</tt> with the specified key.
*/
public V put(int key, V value) {
// Makes sure the key is not already in the IntHashMap.
Entry<V> tab[] = table;
int index = 0;
index = (key & 0x7fffffff) % tab.length;
for (Entry<V> e = tab[index] ; e != null ; e = e.next) {
if (e.key == key) {
V old = e.value;
e.value = value;
return old;
}
}
modCount++;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
index = (key & 0x7fffffff) % tab.length;
}
// Creates the new entry.
Entry<V> e = new Entry<V>(key, value, tab[index]);
tab[index] = e;
count++;
return null;
}
/**
* Removes the mapping for this key from this map if present.
*
* @param key key whose mapping is to be removed from the map.
* @return previous value associated with specified key, or <tt>null</tt>
* if there was no mapping for key. A <tt>null</tt> return can
* also indicate that the map previously associated <tt>null</tt>
* with the specified key.
*/
public V remove(Integer key) {
return remove(key.intValue());
}
/**
* Removes the mapping for this key from this map if present.
*
* @param key key whose mapping is to be removed from the map.
* @return previous value associated with specified key, or <tt>null</tt>
* if there was no mapping for key. A <tt>null</tt> return can
* also indicate that the map previously associated <tt>null</tt>
* with the specified key.
*/
public V remove(int key) {
Entry<V> tab[] = table;
int index = (key & 0x7fffffff) % tab.length;
for (Entry<V> e = tab[index], prev = null; e != null; prev = e, e = e.next) {
if (e.key == key) {
modCount++;
if (prev != null) {
prev.next = e.next;
} else {
tab[index] = e.next;
}
count--;
V oldValue = e.value;
e.value = null;
return oldValue;
}
}
return null;
}
/**
* Removes all mappings from this map.
*/
public void clear() {
Entry tab[] = table;
modCount++;
for (int index = tab.length; --index >= 0; ) {
tab[index] = null;
}
count = 0;
}
/**
* Returns a shallow copy of this <tt>IntHashMap</tt> instance: the keys and
* values themselves are not cloned.
*
* @return a shallow copy of this map.
*/
public Object clone() {
try {
IntHashMap t = (IntHashMap) super.clone();
t.table = new Entry[table.length];
for (int i = table.length ; i-- > 0 ; ) {
t.table[i] = (table[i] != null) ? (Entry) table[i].clone() : null;
}
t.keySet = null;
t.entrySet = null;
t.values = null;
t.modCount = 0;
return t;
} catch (CloneNotSupportedException e) {
// this shouldn"t happen, since we are Cloneable
throw new InternalError();
}
}
// Views
private transient Set keySet = null;
private transient Set entrySet = null;
private transient Collection values = null;
/**
* Returns a set view of the keys contained in this map. The set is
* backed by the map, so changes to the map are reflected in the set, and
* vice-versa. The set supports element removal, which removes the
* corresponding mapping from this map, via the <tt>Iterator.remove</tt>,
* <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt>, and
* <tt>clear</tt> operations. It does not support the <tt>add</tt> or
* <tt>addAll</tt> operations.
*
* @return a set view of the keys contained in this map.
*/
public Set<Integer> keySet() {
if (keySet == null) {
keySet = new AbstractSet<Integer>() {
public Iterator iterator() {
return new IntHashIterator(KEYS);
}
public int size() {
return count;
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
return IntHashMap.this.remove(o) != null;
}
public void clear() {
IntHashMap.this.clear();
}
};
}
return keySet;
}
/**
* Returns a collection view of the values contained in this map. The
* collection is backed by the map, so changes to the map are reflected in
* the collection, and vice-versa. The collection supports element
* removal, which removes the corresponding mapping from this map, via the
* <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
* <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations.
* It does not support the <tt>add</tt> or <tt>addAll</tt> operations.
*
* @return a collection view of the values contained in this map.
*/
public Collection<V> values() {
if (values==null) {
values = new AbstractCollection<V>() {
public Iterator iterator() {
return (Iterator) new IntHashIterator(VALUES);
}
public int size() {
return count;
}
public boolean contains(Object o) {
return containsValue(o);
}
public void clear() {
IntHashMap.this.clear();
}
};
}
return values;
}
/**
* Returns a collection view of the mappings contained in this map. Each
* element in the returned collection is a <tt>Map.Entry</tt>. The
* collection is backed by the map, so changes to the map are reflected in
* the collection, and vice-versa. The collection supports element
* removal, which removes the corresponding mapping from the map, via the
* <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
* <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations.
* It does not support the <tt>add</tt> or <tt>addAll</tt> operations.
*
* @return a collection view of the mappings contained in this map.
*/
public Set<Map.Entry<Integer, V>> entrySet() {
if (entrySet==null) {
entrySet = new AbstractSet<Map.Entry<Integer, V>>() {
public Iterator iterator() {
return (Iterator) new IntHashIterator(ENTRIES);
}
public boolean contains(Object o) {
if (!(o instanceof Map.Entry)) {
return false;
}
Map.Entry entry = (Map.Entry) o;
Integer key = (Integer) entry.getKey();
Entry tab[] = table;
int hash = (key == null ? 0 : key.hashCode());
int index = (hash & 0x7fffffff) % tab.length;
for (Entry e = tab[index]; e != null; e = e.next) {
if (e.key == hash && e.equals(entry)) {
return true;
}
}
return false;
}
public boolean remove(Object o) {
if (!(o instanceof Map.Entry)) {
return false;
}
Map.Entry entry = (Map.Entry) o;
Integer key = (Integer) entry.getKey();
Entry tab[] = table;
int hash = (key == null ? 0 : key.hashCode());
int index = (hash & 0x7fffffff) % tab.length;
for (Entry e = tab[index], prev = null; e != null; prev = e, e = e.next) {
if (e.key == hash && e.equals(entry)) {
modCount++;
if (prev != null) {
prev.next = e.next;
} else {
tab[index] = e.next;
}
count--;
e.value = null;
return true;
}
}
return false;
}
public int size() {
return count;
}
public void clear() {
IntHashMap.this.clear();
}
};
}
return entrySet;
}
/**
* IntHashMap collision list entry.
*/
private static class Entry<V> implements Map.Entry<Integer, V> {
int key;
V value;
Entry<V> next;
private Integer objectKey;
Entry(int key, V value, Entry<V> next) {
this.key = key;
this.value = value;
this.next = next;
}
protected Object clone() {
return new Entry<V>(key, value, (next == null ? null : (Entry<V>) next.clone()));
}
// Map.Entry Ops
public Integer getKey() {
return (objectKey != null) ? objectKey : (objectKey = Integer.valueOf(key));
}
public V getValue() {
return value;
}
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry)) {
return false;
}
Map.Entry e = (Map.Entry) o;
return (getKey().equals(e.getKey())) &&
(value==null ? e.getValue()==null : value.equals(e.getValue()));
}
public int hashCode() {
return key ^ (value==null ? 0 : value.hashCode());
}
public String toString() {
return String.valueOf(key) + "=" + value;
}
}
// Types of Iterators
private static final int KEYS = 0;
private static final int VALUES = 1;
private static final int ENTRIES = 2;
private class IntHashIterator implements Iterator {
Entry[] table = IntHashMap.this.table;
int index = table.length;
Entry entry;
Entry lastReturned;
int type;
/**
* The modCount value that the iterator believes that the backing
* List should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
private int expectedModCount = modCount;
IntHashIterator(int type) {
this.type = type;
}
public boolean hasNext() {
while (entry == null && index > 0) {
entry = table[--index];
}
return entry != null;
}
public Object next() {
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
while (entry == null && index > 0) {
entry = table[--index];
}
if (entry != null) {
Entry e = lastReturned = entry;
entry = e.next;
return type == KEYS ? e.getKey() : (type == VALUES ? e.value : e);
}
throw new NoSuchElementException();
}
public void remove() {
if (lastReturned == null) {
throw new IllegalStateException();
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
Entry<V>[] tab = IntHashMap.this.table;
int index = (lastReturned.key & 0x7fffffff) % tab.length;
for (Entry e = tab[index], prev = null; e != null; prev = e, e = e.next) {
if (e == lastReturned) {
modCount++;
expectedModCount++;
if (prev == null) {
tab[index] = e.next;
} else {
prev.next = e.next;
}
count--;
lastReturned = null;
return;
}
}
throw new ConcurrentModificationException();
}
}
/**
* Save the state of the <tt>IntHashMap</tt> instance to a stream (i.e.,
* serialize it).
*
* @serialData The <i>capacity</i> of the IntHashMap (the length of the
* bucket array) is emitted (int), followed by the
* <i>size</i> of the IntHashMap (the number of key-value
* mappings), followed by the key (Object) and value (Object)
* for each key-value mapping represented by the IntHashMap
* The key-value mappings are emitted in no particular order.
*/
private void writeObject(java.io.ObjectOutputStream s) throws IOException {
// Write out the threshold, loadfactor, and any hidden stuff
s.defaultWriteObject();
// Write out number of buckets
s.writeInt(table.length);
// Write out size (number of Mappings)
s.writeInt(count);
// Write out keys and values (alternating)
for (int index = table.length - 1; index >= 0; index--) {
Entry entry = table[index];
while (entry != null) {
s.writeInt(entry.key);
s.writeObject(entry.value);
entry = entry.next;
}
}
}
/**
* Reconstitute the <tt>IntHashMap</tt> instance from a stream (i.e.,
* deserialize it).
*/
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException
{
// Read in the threshold, loadfactor, and any hidden stuff
s.defaultReadObject();
// Read in number of buckets and allocate the bucket array;
int numBuckets = s.readInt();
table = new Entry[numBuckets];
// Read in size (number of Mappings)
int size = s.readInt();
// Read the keys and values, and put the mappings in the IntHashMap
for (int i=0; i<size; i++) {
int key = s.readInt();
V value = (V) s.readObject();
put(key, value);
}
}
int capacity() {
return table.length;
}
float loadFactor() {
return loadFactor;
}
}
A Map where keys are compared by object identity, rather than equals()
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*
*/
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* A <tt>Map</tt> where keys are compared by object identity,
* rather than <tt>equals()</tt>.
*/
public final class IdentityMap implements Map {
private final Map map;
private transient Map.Entry[] entryArray = new Map.Entry[0];
private transient boolean dirty = false;
/**
* Return a new instance of this class, with an undefined
* iteration order.
*
* @param size The size of the map
* @return Map
*/
public static Map instantiate(int size) {
return new IdentityMap( new HashMap( size ) );
}
/**
* Return a new instance of this class, with iteration
* order defined as the order in which entries were added
*
* @param size The size of the map to create
* @return
*/
public static Map instantiateSequenced(int size) {
return new IdentityMap( new LinkedHashMap( size ) );
}
/**
* Private ctor used in serialization.
*
* @param underlyingMap The delegate map.
*/
private IdentityMap(Map underlyingMap) {
map = underlyingMap;
dirty = true;
}
/**
* Return the map entries (as instances of <tt>Map.Entry</tt> in a collection that
* is safe from concurrent modification). ie. we may safely add new instances to
* the underlying <tt>Map</tt> during iteration of the <tt>entries()</tt>.
*
* @param map
* @return Collection
*/
public static Map.Entry[] concurrentEntries(Map map) {
return ( (IdentityMap) map ).entryArray();
}
public static List entries(Map map) {
return ( (IdentityMap) map ).entryList();
}
public static Iterator keyIterator(Map map) {
return ( (IdentityMap) map ).keyIterator();
}
public Iterator keyIterator() {
return new KeyIterator( map.keySet().iterator() );
}
public static final class IdentityMapEntry implements java.util.Map.Entry {
IdentityMapEntry(Object key, Object value) {
this.key=key;
this.value=value;
}
private Object key;
private Object value;
public Object getKey() {
return key;
}
public Object getValue() {
return value;
}
public Object setValue(Object value) {
Object result = this.value;
this.value = value;
return result;
}
}
public static final class IdentityKey implements Serializable {
private Object key;
IdentityKey(Object key) {
this.key=key;
}
public boolean equals(Object other) {
return key == ( (IdentityKey) other ).key;
}
public int hashCode() {
return System.identityHashCode(key);
}
public String toString() {
return key.toString();
}
public Object getRealKey() {
return key;
}
}
public int size() {
return map.size();
}
public boolean isEmpty() {
return map.isEmpty();
}
public boolean containsKey(Object key) {
IdentityKey k = new IdentityKey(key);
return map.containsKey(k);
}
public boolean containsValue(Object val) {
return map.containsValue(val);
}
public Object get(Object key) {
IdentityKey k = new IdentityKey(key);
return map.get(k);
}
public Object put(Object key, Object value) {
dirty = true;
return map.put( new IdentityKey(key), value );
}
public Object remove(Object key) {
dirty = true;
IdentityKey k = new IdentityKey(key);
return map.remove(k);
}
public void putAll(Map otherMap) {
Iterator iter = otherMap.entrySet().iterator();
while ( iter.hasNext() ) {
Map.Entry me = (Map.Entry) iter.next();
put( me.getKey(), me.getValue() );
}
}
public void clear() {
dirty = true;
entryArray = null;
map.clear();
}
public Set keySet() {
// would need an IdentitySet for this!
throw new UnsupportedOperationException();
}
public Collection values() {
return map.values();
}
public Set entrySet() {
Set set = new HashSet( map.size() );
Iterator iter = map.entrySet().iterator();
while ( iter.hasNext() ) {
Map.Entry me = (Map.Entry) iter.next();
set.add( new IdentityMapEntry( ( (IdentityKey) me.getKey() ).key, me.getValue() ) );
}
return set;
}
public List entryList() {
ArrayList list = new ArrayList( map.size() );
Iterator iter = map.entrySet().iterator();
while ( iter.hasNext() ) {
Map.Entry me = (Map.Entry) iter.next();
list.add( new IdentityMapEntry( ( (IdentityKey) me.getKey() ).key, me.getValue() ) );
}
return list;
}
public Map.Entry[] entryArray() {
if (dirty) {
entryArray = new Map.Entry[ map.size() ];
Iterator iter = map.entrySet().iterator();
int i=0;
while ( iter.hasNext() ) {
Map.Entry me = (Map.Entry) iter.next();
entryArray[i++] = new IdentityMapEntry( ( (IdentityKey) me.getKey() ).key, me.getValue() );
}
dirty = false;
}
return entryArray;
}
/**
* Workaround for a JDK 1.4.1 bug where <tt>IdentityHashMap</tt>s are not
* correctly deserialized.
*
* @param map
* @return Object
*/
public static Object serialize(Map map) {
return ( (IdentityMap) map ).map;
}
/**
* Workaround for a JDK 1.4.1 bug where <tt>IdentityHashMap</tt>s are not
* correctly deserialized.
*
* @param o
* @return Map
*/
public static Map deserialize(Object o) {
return new IdentityMap( (Map) o );
}
public String toString() {
return map.toString();
}
public static Map invert(Map map) {
Map result = instantiate( map.size() );
Iterator iter = map.entrySet().iterator();
while ( iter.hasNext() ) {
Map.Entry me = (Map.Entry) iter.next();
result.put( me.getValue(), me.getKey() );
}
return result;
}
static final class KeyIterator implements Iterator {
private KeyIterator(Iterator iter) {
identityKeyIterator = iter;
}
private final Iterator identityKeyIterator;
public boolean hasNext() {
return identityKeyIterator.hasNext();
}
public Object next() {
return ( (IdentityKey) identityKeyIterator.next() ).key;
}
public void remove() {
throw new UnsupportedOperationException();
}
}
}
A Map with multiple values for a key
/**********************************************************************
Copyright (c) 2003 Andy Jefferson and others. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Contributors:
...
**********************************************************************/
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* An implementation of a <code>MultiMap</code>, which is basically a Map
* with multiple values for a key. This will be removed when SUN see sense and
* include it in the JDK java.util package as standard.
*
* @version $Revision: 1.6 $
*/
public class MultiMap extends HashMap
{
private transient Collection values=null;
/**
* Constructor.
*/
public MultiMap()
{
super();
}
/**
* Constructor.
*
* @param initialCapacity the initial capacity
*/
public MultiMap(int initialCapacity)
{
super(initialCapacity);
}
/**
* Constructor.
* @param initialCapacity initial capacity
* @param loadFactor load factor for the Map.
*/
public MultiMap(int initialCapacity, float loadFactor)
{
super(initialCapacity, loadFactor);
}
/**
* Constructor.
* @param map The initial Map.
*/
public MultiMap(MultiMap map)
{
super();
if( map != null )
{
Iterator it = map.entrySet().iterator();
while( it.hasNext() )
{
Map.Entry entry = (Map.Entry) it.next();
super.put(entry.getKey(), new ArrayList((List)entry.getValue()));
}
}
}
/**
* Check if the map contains the passed value.
*
* @param value the value to search for
* @return true if the list contains the value
*/
public boolean containsValue(Object value)
{
Set pairs = super.entrySet();
if (pairs == null)
{
return false;
}
Iterator pairsIterator = pairs.iterator();
while (pairsIterator.hasNext())
{
Map.Entry keyValuePair = (Map.Entry) pairsIterator.next();
Collection coll = (Collection) keyValuePair.getValue();
if (coll.contains(value))
{
return true;
}
}
return false;
}
/**
* Add a key, and its value, to the map.
*
* @param key the key to set
* @param value the value to set the key to
* @return the value added when successful, or null if an error
*/
public Object put(Object key,Object value)
{
Collection c=(Collection)super.get(key);
if (c == null)
{
c = createCollection(null);
super.put(key, c);
}
boolean results = c.add(value);
return (results ? value : null);
}
/**
* Removes a specific value from map.
* The item is removed from the collection mapped to the specified key.
*
* @param key the key to remove from
* @param item the value to remove
* @return the value removed (which was passed in)
*/
public Object remove(Object key, Object item)
{
Collection valuesForKey=(Collection)super.get(key);
if (valuesForKey == null)
{
return null;
}
valuesForKey.remove(item);
// remove the list if it is now empty
// (saves space, and allows equals to work)
if (valuesForKey.isEmpty())
{
remove(key);
}
return item;
}
/**
* Clear the map.
*/
public void clear()
{
// Clear the mappings
Set pairs=super.entrySet();
Iterator pairsIterator = pairs.iterator();
while (pairsIterator.hasNext())
{
Map.Entry keyValuePair=(Map.Entry) pairsIterator.next();
Collection coll=(Collection)keyValuePair.getValue();
coll.clear();
}
super.clear();
}
/**
* Accessor for the values in the Map.
* @return all of the values in the map
*/
public Collection values()
{
Collection vs = values;
return (vs != null ? vs : (values = new ValueElement()));
}
/**
* Method to clone the Map. Performs a shallow copy of the entry set.
* @return the cloned map
*/
public Object clone()
{
MultiMap obj = (MultiMap) super.clone();
// Clone the entry set.
for (Iterator it = entrySet().iterator(); it.hasNext();)
{
Map.Entry entry = (Map.Entry) it.next();
Collection coll = (Collection) entry.getValue();
Collection newColl = createCollection(coll);
entry.setValue(newColl);
}
return obj;
}
/**
* Creates a new instance of the map value Collection container.
*
* @param c the collection to copy
* @return new collection
*/
protected Collection createCollection(Collection c)
{
if (c == null)
{
return new ArrayList();
}
else
{
return new ArrayList(c);
}
}
/**
* Representation of the values.
*/
private class ValueElement extends AbstractCollection
{
public Iterator iterator()
{
return new ValueElementIter();
}
public int size()
{
int i=0;
Iterator iter = iterator();
while (iter.hasNext())
{
iter.next();
i++;
}
return i;
}
public void clear()
{
MultiMap.this.clear();
}
}
/**
* Iterator for the values.
*/
private class ValueElementIter implements Iterator
{
private Iterator backing;
private Iterator temp;
private ValueElementIter()
{
backing = MultiMap.super.values().iterator();
}
private boolean searchNextIterator()
{
while (temp == null ||
temp.hasNext() == false)
{
if (backing.hasNext() == false)
{
return false;
}
temp = ((Collection) backing.next()).iterator();
}
return true;
}
public boolean hasNext()
{
return searchNextIterator();
}
public Object next()
{
if (searchNextIterator() == false)
{
throw new NoSuchElementException();
}
return temp.next();
}
public void remove()
{
if (temp == null)
{
throw new IllegalStateException();
}
temp.remove();
}
}
}
A memory-efficient hash map.
/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* A memory-efficient hash map.
*
* @param <K> the key type
* @param <V> the value type
*/
public class HashMap<K, V> implements Map<K, V>, Serializable {
/**
* In the interest of memory-savings, we start with the smallest feasible
* power-of-two table size that can hold three items without rehashing. If we
* started with a size of 2, we"d have to expand as soon as the second item
* was added.
*/
private static final int INITIAL_TABLE_SIZE = 4;
private class EntryIterator implements Iterator<Entry<K, V>> {
private int index = 0;
private int last = -1;
{
advanceToItem();
}
public boolean hasNext() {
return index < keys.length;
}
public Entry<K, V> next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
last = index;
Entry<K, V> toReturn = new HashEntry(index++);
advanceToItem();
return toReturn;
}
public void remove() {
if (last < 0) {
throw new IllegalStateException();
}
internalRemove(last);
if (keys[last] != null) {
index = last;
}
last = -1;
}
private void advanceToItem() {
for (; index < keys.length; ++index) {
if (keys[index] != null) {
return;
}
}
}
}
private class EntrySet extends AbstractSet<Entry<K, V>> {
@Override
public boolean add(Entry<K, V> entry) {
boolean result = !HashMap.this.containsKey(entry.getKey());
HashMap.this.put(entry.getKey(), entry.getValue());
return result;
}
@Override
public boolean addAll(Collection<? extends Entry<K, V>> c) {
HashMap.this.ensureSizeFor(size() + c.size());
return super.addAll(c);
}
@Override
public void clear() {
HashMap.this.clear();
}
@Override
@SuppressWarnings("unchecked")
public boolean contains(Object o) {
if (!(o instanceof Entry)) {
return false;
}
Entry<K, V> entry = (Entry<K, V>) o;
V value = HashMap.this.get(entry.getKey());
return HashMap.this.valueEquals(value, entry.getValue());
}
@Override
public int hashCode() {
return HashMap.this.hashCode();
}
@Override
public Iterator<java.util.Map.Entry<K, V>> iterator() {
return new EntryIterator();
}
@Override
@SuppressWarnings("unchecked")
public boolean remove(Object o) {
if (!(o instanceof Entry)) {
return false;
}
Entry<K, V> entry = (Entry<K, V>) o;
int index = findKey(entry.getKey());
if (index >= 0 && valueEquals(values[index], entry.getValue())) {
internalRemove(index);
return true;
}
return false;
}
@Override
public boolean removeAll(Collection<?> c) {
boolean didRemove = false;
for (Object o : c) {
didRemove |= remove(o);
}
return didRemove;
}
@Override
public int size() {
return HashMap.this.size;
}
}
private class HashEntry implements Entry<K, V> {
private final int index;
public HashEntry(int index) {
this.index = index;
}
@Override
@SuppressWarnings("unchecked")
public boolean equals(Object o) {
if (!(o instanceof Entry)) {
return false;
}
Entry<K, V> entry = (Entry<K, V>) o;
return keyEquals(getKey(), entry.getKey())
&& valueEquals(getValue(), entry.getValue());
}
@SuppressWarnings("unchecked")
public K getKey() {
return (K) unmaskNullKey(keys[index]);
}
@SuppressWarnings("unchecked")
public V getValue() {
return (V) values[index];
}
@Override
public int hashCode() {
return keyHashCode(getKey()) ^ valueHashCode(getValue());
}
@SuppressWarnings("unchecked")
public V setValue(V value) {
V previous = (V) values[index];
values[index] = value;
return previous;
}
@Override
public String toString() {
return getKey() + "=" + getValue();
}
}
private class KeyIterator implements Iterator<K> {
private int index = 0;
private int last = -1;
{
advanceToItem();
}
public boolean hasNext() {
return index < keys.length;
}
@SuppressWarnings("unchecked")
public K next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
last = index;
Object toReturn = unmaskNullKey(keys[index++]);
advanceToItem();
return (K) toReturn;
}
public void remove() {
if (last < 0) {
throw new IllegalStateException();
}
internalRemove(last);
if (keys[last] != null) {
index = last;
}
last = -1;
}
private void advanceToItem() {
for (; index < keys.length; ++index) {
if (keys[index] != null) {
return;
}
}
}
}
private class KeySet extends AbstractSet<K> {
@Override
public void clear() {
HashMap.this.clear();
}
@Override
public boolean contains(Object o) {
return HashMap.this.containsKey(o);
}
@Override
public int hashCode() {
int result = 0;
for (int i = 0; i < keys.length; ++i) {
Object key = keys[i];
if (key != null) {
result += keyHashCode(unmaskNullKey(key));
}
}
return result;
}
@Override
public Iterator<K> iterator() {
return new KeyIterator();
}
@Override
public boolean remove(Object o) {
int index = findKey(o);
if (index >= 0) {
internalRemove(index);
return true;
}
return false;
}
@Override
public boolean removeAll(Collection<?> c) {
boolean didRemove = false;
for (Object o : c) {
didRemove |= remove(o);
}
return didRemove;
}
@Override
public int size() {
return HashMap.this.size;
}
}
private class ValueIterator implements Iterator<V> {
private int index = 0;
private int last = -1;
{
advanceToItem();
}
public boolean hasNext() {
return index < keys.length;
}
@SuppressWarnings("unchecked")
public V next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
last = index;
Object toReturn = values[index++];
advanceToItem();
return (V) toReturn;
}
public void remove() {
if (last < 0) {
throw new IllegalStateException();
}
internalRemove(last);
if (keys[last] != null) {
index = last;
}
last = -1;
}
private void advanceToItem() {
for (; index < keys.length; ++index) {
if (keys[index] != null) {
return;
}
}
}
}
private class Values extends AbstractCollection<V> {
@Override
public void clear() {
HashMap.this.clear();
}
@Override
public boolean contains(Object o) {
return HashMap.this.containsValue(o);
}
@Override
public int hashCode() {
int result = 0;
for (int i = 0; i < keys.length; ++i) {
if (keys[i] != null) {
result += valueHashCode(values[i]);
}
}
return result;
}
@Override
public Iterator<V> iterator() {
return new ValueIterator();
}
@Override
public boolean remove(Object o) {
if (o == null) {
for (int i = 0; i < keys.length; ++i) {
if (keys[i] != null && values[i] == null) {
internalRemove(i);
return true;
}
}
} else {
for (int i = 0; i < keys.length; ++i) {
if (valueEquals(values[i], o)) {
internalRemove(i);
return true;
}
}
}
return false;
}
@Override
public boolean removeAll(Collection<?> c) {
boolean didRemove = false;
for (Object o : c) {
didRemove |= remove(o);
}
return didRemove;
}
@Override
public int size() {
return HashMap.this.size;
}
}
private static final Object NULL_KEY = new Serializable() {
Object readResolve() {
return NULL_KEY;
}
};
static Object maskNullKey(Object k) {
return (k == null) ? NULL_KEY : k;
}
static Object unmaskNullKey(Object k) {
return (k == NULL_KEY) ? null : k;
}
/**
* Backing store for all the keys; transient due to custom serialization.
* Default access to avoid synthetic accessors from inner classes.
*/
transient Object[] keys;
/**
* Number of pairs in this set; transient due to custom serialization. Default
* access to avoid synthetic accessors from inner classes.
*/
transient int size = 0;
/**
* Backing store for all the values; transient due to custom serialization.
* Default access to avoid synthetic accessors from inner classes.
*/
transient Object[] values;
public HashMap() {
initTable(INITIAL_TABLE_SIZE);
}
public HashMap(Map<? extends K, ? extends V> m) {
int newCapacity = INITIAL_TABLE_SIZE;
int expectedSize = m.size();
while (newCapacity * 3 < expectedSize * 4) {
newCapacity <<= 1;
}
initTable(newCapacity);
internalPutAll(m);
}
public void clear() {
initTable(INITIAL_TABLE_SIZE);
size = 0;
}
public boolean containsKey(Object key) {
return findKey(key) >= 0;
}
public boolean containsValue(Object value) {
if (value == null) {
for (int i = 0; i < keys.length; ++i) {
if (keys[i] != null && values[i] == null) {
return true;
}
}
} else {
for (Object existing : values) {
if (valueEquals(existing, value)) {
return true;
}
}
}
return false;
}
public Set<Entry<K, V>> entrySet() {
return new EntrySet();
}
@Override
@SuppressWarnings("unchecked")
public boolean equals(Object o) {
if (!(o instanceof Map)) {
return false;
}
Map<K, V> other = (Map<K, V>) o;
return entrySet().equals(other.entrySet());
}
@SuppressWarnings("unchecked")
public V get(Object key) {
int index = findKey(key);
return (index < 0) ? null : (V) values[index];
}
@Override
public int hashCode() {
int result = 0;
for (int i = 0; i < keys.length; ++i) {
Object key = keys[i];
if (key != null) {
result += keyHashCode(unmaskNullKey(key)) ^ valueHashCode(values[i]);
}
}
return result;
}
public boolean isEmpty() {
return size == 0;
}
public Set<K> keySet() {
return new KeySet();
}
@SuppressWarnings("unchecked")
public V put(K key, V value) {
ensureSizeFor(size + 1);
int index = findKeyOrEmpty(key);
if (keys[index] == null) {
++size;
keys[index] = maskNullKey(key);
values[index] = value;
return null;
} else {
Object previousValue = values[index];
values[index] = value;
return (V) previousValue;
}
}
public void putAll(Map<? extends K, ? extends V> m) {
ensureSizeFor(size + m.size());
internalPutAll(m);
}
@SuppressWarnings("unchecked")
public V remove(Object key) {
int index = findKey(key);
if (index < 0) {
return null;
}
Object previousValue = values[index];
internalRemove(index);
return (V) previousValue;
}
public int size() {
return size;
}
@Override
public String toString() {
if (size == 0) {
return "{}";
}
StringBuilder buf = new StringBuilder(32 * size());
buf.append("{");
boolean needComma = false;
for (int i = 0; i < keys.length; ++i) {
Object key = keys[i];
if (key != null) {
if (needComma) {
buf.append(",").append(" ");
}
key = unmaskNullKey(key);
Object value = values[i];
buf.append(key == this ? "(this Map)" : key).append("=").append(
value == this ? "(this Map)" : value);
needComma = true;
}
}
buf.append("}");
return buf.toString();
}
public Collection<V> values() {
return new Values();
}
/**
* Adapted from {@link org.apache.rumons.collections.map.AbstractHashedMap}.
*/
@SuppressWarnings("unchecked")
protected void doReadObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
int capacity = in.readInt();
initTable(capacity);
int items = in.readInt();
for (int i = 0; i < items; i++) {
Object key = in.readObject();
Object value = in.readObject();
put((K) key, (V) value);
}
}
/**
* Adapted from {@link org.apache.rumons.collections.map.AbstractHashedMap}.
*/
protected void doWriteObject(ObjectOutputStream out) throws IOException {
out.writeInt(keys.length);
out.writeInt(size);
for (int i = 0; i < keys.length; ++i) {
Object key = keys[i];
if (key != null) {
out.writeObject(unmaskNullKey(key));
out.writeObject(values[i]);
}
}
}
/**
* Returns whether two keys are equal for the purposes of this set.
*/
protected boolean keyEquals(Object a, Object b) {
return (a == null) ? (b == null) : a.equals(b);
}
/**
* Returns the hashCode for a key.
*/
protected int keyHashCode(Object k) {
return (k == null) ? 0 : k.hashCode();
}
/**
* Returns whether two values are equal for the purposes of this set.
*/
protected boolean valueEquals(Object a, Object b) {
return (a == null) ? (b == null) : a.equals(b);
}
/**
* Returns the hashCode for a value.
*/
protected int valueHashCode(Object v) {
return (v == null) ? 0 : v.hashCode();
}
/**
* Ensures the map is large enough to contain the specified number of entries.
* Default access to avoid synthetic accessors from inner classes.
*/
void ensureSizeFor(int expectedSize) {
if (keys.length * 3 >= expectedSize * 4) {
return;
}
int newCapacity = keys.length << 1;
while (newCapacity * 3 < expectedSize * 4) {
newCapacity <<= 1;
}
Object[] oldKeys = keys;
Object[] oldValues = values;
initTable(newCapacity);
for (int i = 0; i < oldKeys.length; ++i) {
Object k = oldKeys[i];
if (k != null) {
int newIndex = getKeyIndex(unmaskNullKey(k));
while (keys[newIndex] != null) {
if (++newIndex == keys.length) {
newIndex = 0;
}
}
keys[newIndex] = k;
values[newIndex] = oldValues[i];
}
}
}
/**
* Returns the index in the key table at which a particular key resides, or -1
* if the key is not in the table. Default access to avoid synthetic accessors
* from inner classes.
*/
int findKey(Object k) {
int index = getKeyIndex(k);
while (true) {
Object existing = keys[index];
if (existing == null) {
return -1;
}
if (keyEquals(k, unmaskNullKey(existing))) {
return index;
}
if (++index == keys.length) {
index = 0;
}
}
}
/**
* Returns the index in the key table at which a particular key resides, or
* the index of an empty slot in the table where this key should be inserted
* if it is not already in the table. Default access to avoid synthetic
* accessors from inner classes.
*/
int findKeyOrEmpty(Object k) {
int index = getKeyIndex(k);
while (true) {
Object existing = keys[index];
if (existing == null) {
return index;
}
if (keyEquals(k, unmaskNullKey(existing))) {
return index;
}
if (++index == keys.length) {
index = 0;
}
}
}
/**
* Removes the entry at the specified index, and performs internal management
* to make sure we don"t wind up with a hole in the table. Default access to
* avoid synthetic accessors from inner classes.
*/
void internalRemove(int index) {
keys[index] = null;
values[index] = null;
--size;
plugHole(index);
}
private int getKeyIndex(Object k) {
int h = keyHashCode(k);
// Copied from Apache"s AbstractHashedMap; prevents power-of-two collisions.
h += ~(h << 9);
h ^= (h >>> 14);
h += (h << 4);
h ^= (h >>> 10);
// Power of two trick.
return h & (keys.length - 1);
}
private void initTable(int capacity) {
keys = new Object[capacity];
values = new Object[capacity];
}
private void internalPutAll(Map<? extends K, ? extends V> m) {
for (Entry<? extends K, ? extends V> entry : m.entrySet()) {
K key = entry.getKey();
V value = entry.getValue();
int index = findKeyOrEmpty(key);
if (keys[index] == null) {
++size;
keys[index] = maskNullKey(key);
values[index] = value;
} else {
values[index] = value;
}
}
}
/**
* Tricky, we left a hole in the map, which we have to fill. The only way to
* do this is to search forwards through the map shuffling back values that
* match this index until we hit a null.
*/
private void plugHole(int hole) {
int index = hole + 1;
if (index == keys.length) {
index = 0;
}
while (keys[index] != null) {
int targetIndex = getKeyIndex(unmaskNullKey(keys[index]));
if (hole < index) {
/*
* "Normal" case, the index is past the hole and the "bad range" is from
* hole (exclusive) to index (inclusive).
*/
if (!(hole < targetIndex && targetIndex <= index)) {
// Plug it!
keys[hole] = keys[index];
values[hole] = values[index];
keys[index] = null;
values[index] = null;
hole = index;
}
} else {
/*
* "Wrapped" case, the index is before the hole (we"ve wrapped) and the
* "good range" is from index (exclusive) to hole (inclusive).
*/
if (index < targetIndex && targetIndex <= hole) {
// Plug it!
keys[hole] = keys[index];
values[hole] = values[index];
keys[index] = null;
values[index] = null;
hole = index;
}
}
if (++index == keys.length) {
index = 0;
}
}
}
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
in.defaultReadObject();
doReadObject(in);
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
doWriteObject(out);
}
}
A multi valued Map
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
//
// Copyright 2004-2005 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
/* ------------------------------------------------------------ */
/** A multi valued Map.
* This Map specializes HashMap and provides methods
* that operate on multi valued items.
* <P>
* Implemented as a map of LazyList values
*
* @see LazyList
* @author Greg Wilkins (gregw)
*/
public class MultiMap extends HashMap
implements Cloneable
{
/* ------------------------------------------------------------ */
/** Constructor.
*/
public MultiMap()
{}
/* ------------------------------------------------------------ */
/** Constructor.
* @param size Capacity of the map
*/
public MultiMap(int size)
{
super(size);
}
/* ------------------------------------------------------------ */
/** Constructor.
* @param map
*/
public MultiMap(Map map)
{
super((map.size()*3)/2);
putAll(map);
}
/* ------------------------------------------------------------ */
/** Get multiple values.
* Single valued entries are converted to singleton lists.
* @param name The entry key.
* @return Unmodifieable List of values.
*/
public List getValues(Object name)
{
return LazyList.getList(super.get(name),true);
}
/* ------------------------------------------------------------ */
/** Get a value from a multiple value.
* If the value is not a multivalue, then index 0 retrieves the
* value or null.
* @param name The entry key.
* @param i Index of element to get.
* @return Unmodifieable List of values.
*/
public Object getValue(Object name,int i)
{
Object l=super.get(name);
if (i==0 && LazyList.size(l)==0)
return null;
return LazyList.get(l,i);
}
/* ------------------------------------------------------------ */
/** Get value as String.
* Single valued items are converted to a String with the toString()
* Object method. Multi valued entries are converted to a comma separated
* List. No quoting of commas within values is performed.
* @param name The entry key.
* @return String value.
*/
public String getString(Object name)
{
Object l=super.get(name);
switch(LazyList.size(l))
{
case 0:
return null;
case 1:
Object o=LazyList.get(l,0);
return o==null?null:o.toString();
default:
StringBuffer values=new StringBuffer(128);
synchronized(values)
{
for (int i=0; i<LazyList.size(l); i++)
{
Object e=LazyList.get(l,i);
if (e!=null)
{
if (values.length()>0)
values.append(",");
values.append(e.toString());
}
}
return values.toString();
}
}
}
/* ------------------------------------------------------------ */
public Object get(Object name)
{
Object l=super.get(name);
switch(LazyList.size(l))
{
case 0:
return null;
case 1:
Object o=LazyList.get(l,0);
return o;
default:
return LazyList.getList(l,true);
}
}
/* ------------------------------------------------------------ */
/** Put and entry into the map.
* @param name The entry key.
* @param value The entry value.
* @return The previous value or null.
*/
public Object put(Object name, Object value)
{
return super.put(name,LazyList.add(null,value));
}
/* ------------------------------------------------------------ */
/** Put multi valued entry.
* @param name The entry key.
* @param values The List of multiple values.
* @return The previous value or null.
*/
public Object putValues(Object name, List values)
{
return super.put(name,values);
}
/* ------------------------------------------------------------ */
/** Put multi valued entry.
* @param name The entry key.
* @param values The String array of multiple values.
* @return The previous value or null.
*/
public Object putValues(Object name, String[] values)
{
Object list=null;
for (int i=0;i<values.length;i++)
list=LazyList.add(list,values[i]);
return put(name,list);
}
/* ------------------------------------------------------------ */
/** Add value to multi valued entry.
* If the entry is single valued, it is converted to the first
* value of a multi valued entry.
* @param name The entry key.
* @param value The entry value.
*/
public void add(Object name, Object value)
{
Object lo = super.get(name);
Object ln = LazyList.add(lo,value);
if (lo!=ln)
super.put(name,ln);
}
/* ------------------------------------------------------------ */
/** Add values to multi valued entry.
* If the entry is single valued, it is converted to the first
* value of a multi valued entry.
* @param name The entry key.
* @param values The List of multiple values.
*/
public void addValues(Object name, List values)
{
Object lo = super.get(name);
Object ln = LazyList.addCollection(lo,values);
if (lo!=ln)
super.put(name,ln);
}
/* ------------------------------------------------------------ */
/** Add values to multi valued entry.
* If the entry is single valued, it is converted to the first
* value of a multi valued entry.
* @param name The entry key.
* @param values The String array of multiple values.
*/
public void addValues(Object name, String[] values)
{
Object lo = super.get(name);
Object ln = LazyList.addCollection(lo,Arrays.asList(values));
if (lo!=ln)
super.put(name,ln);
}
/* ------------------------------------------------------------ */
/** Remove value.
* @param name The entry key.
* @param value The entry value.
* @return true if it was removed.
*/
public boolean removeValue(Object name,Object value)
{
Object lo = super.get(name);
Object ln=lo;
int s=LazyList.size(lo);
if (s>0)
{
ln=LazyList.remove(lo,value);
if (ln==null)
super.remove(name);
else
super.put(name, ln);
}
return LazyList.size(ln)!=s;
}
/* ------------------------------------------------------------ */
/** Put all contents of map.
* @param m Map
*/
public void putAll(Map m)
{
Iterator i = m.entrySet().iterator();
boolean multi=m instanceof MultiMap;
while(i.hasNext())
{
Map.Entry entry =
(Map.Entry)i.next();
if (multi)
super.put(entry.getKey(),LazyList.clone(entry.getValue()));
else
put(entry.getKey(),entry.getValue());
}
}
/* ------------------------------------------------------------ */
/**
* @return Map of String arrays
*/
public Map toStringArrayMap()
{
HashMap map = new HashMap(size()*3/2);
Iterator i = super.entrySet().iterator();
while(i.hasNext())
{
Map.Entry entry = (Map.Entry)i.next();
Object l = entry.getValue();
String[] a = LazyList.toStringArray(l);
// for (int j=a.length;j-->0;)
// if (a[j]==null)
// a[j]="";
map.put(entry.getKey(),a);
}
return map;
}
/* ------------------------------------------------------------ */
public Object clone()
{
MultiMap mm = (MultiMap) super.clone();
Iterator iter = mm.entrySet().iterator();
while (iter.hasNext())
{
Map.Entry entry = (Map.Entry)iter.next();
entry.setValue(LazyList.clone(entry.getValue()));
}
return mm;
}
}
//
//Copyright 2004-2005 Mort Bay Consulting Pty. Ltd.
//------------------------------------------------------------------------
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//http://www.apache.org/licenses/LICENSE-2.0
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
//
/* ------------------------------------------------------------ */
/** Lazy List creation.
* A List helper class that attempts to avoid unneccessary List
* creation. If a method needs to create a List to return, but it is
* expected that this will either be empty or frequently contain a
* single item, then using LazyList will avoid additional object
* creations by using Collections.EMPTY_LIST or
* Collections.singletonList where possible.
*
* <p><h4>Usage</h4>
* <pre>
* Object lazylist =null;
* while(loopCondition)
* {
* Object item = getItem();
* if (item.isToBeAdded())
* lazylist = LazyList.add(lazylist,item);
* }
* return LazyList.getList(lazylist);
* </pre>
*
* An ArrayList of default size is used as the initial LazyList.
*
* @see java.util.List
* @author Greg Wilkins (gregw)
*/
class LazyList
implements Cloneable, Serializable
{
private static final String[] __EMTPY_STRING_ARRAY = new String[0];
/* ------------------------------------------------------------ */
private LazyList()
{}
/* ------------------------------------------------------------ */
/** Add an item to a LazyList
* @param list The list to add to or null if none yet created.
* @param item The item to add.
* @return The lazylist created or added to.
*/
public static Object add(Object list, Object item)
{
if (list==null)
{
if (item instanceof List || item==null)
{
List l = new ArrayList();
l.add(item);
return l;
}
return item;
}
if (list instanceof List)
{
((List)list).add(item);
return list;
}
List l=new ArrayList();
l.add(list);
l.add(item);
return l;
}
/* ------------------------------------------------------------ */
/** Add an item to a LazyList
* @param list The list to add to or null if none yet created.
* @param index The index to add the item at.
* @param item The item to add.
* @return The lazylist created or added to.
*/
public static Object add(Object list, int index, Object item)
{
if (list==null)
{
if (index>0 || item instanceof List || item==null)
{
List l = new ArrayList();
l.add(index,item);
return l;
}
return item;
}
if (list instanceof List)
{
((List)list).add(index,item);
return list;
}
List l=new ArrayList();
l.add(list);
l.add(index,item);
return l;
}
/* ------------------------------------------------------------ */
/** Add the contents of a Collection to a LazyList
* @param list The list to add to or null if none yet created.
* @param collection The Collection whose contents should be added.
* @return The lazylist created or added to.
*/
public static Object addCollection(Object list, Collection collection)
{
Iterator i=collection.iterator();
while(i.hasNext())
list=LazyList.add(list,i.next());
return list;
}
/* ------------------------------------------------------------ */
/** Add the contents of an array to a LazyList
* @param list The list to add to or null if none yet created.
* @param collection The Collection whose contents should be added.
* @return The lazylist created or added to.
*/
public static Object addArray(Object list, Object[] array)
{
for(int i=0;array!=null && i<array.length;i++)
list=LazyList.add(list,array[i]);
return list;
}
/* ------------------------------------------------------------ */
/** Ensure the capcity of the underlying list.
*
*/
public static Object ensureSize(Object list, int initialSize)
{
if (list==null)
return new ArrayList(initialSize);
if (list instanceof ArrayList)
{
ArrayList ol=(ArrayList)list;
if (ol.size()>initialSize)
return ol;
ArrayList nl = new ArrayList(initialSize);
nl.addAll(ol);
return nl;
}
List l= new ArrayList(initialSize);
l.add(list);
return l;
}
/* ------------------------------------------------------------ */
public static Object remove(Object list, Object o)
{
if (list==null)
return null;
if (list instanceof List)
{
List l = (List)list;
l.remove(o);
if (l.size()==0)
return null;
return list;
}
if (list.equals(o))
return null;
return list;
}
/* ------------------------------------------------------------ */
public static Object remove(Object list, int i)
{
if (list==null)
return null;
if (list instanceof List)
{
List l = (List)list;
l.remove(i);
if (l.size()==0)
return null;
return list;
}
if (i==0)
return null;
return list;
}
/* ------------------------------------------------------------ */
/** Get the real List from a LazyList.
*
* @param list A LazyList returned from LazyList.add(Object)
* @return The List of added items, which may be an EMPTY_LIST
* or a SingletonList.
*/
public static List getList(Object list)
{
return getList(list,false);
}
/* ------------------------------------------------------------ */
/** Get the real List from a LazyList.
*
* @param list A LazyList returned from LazyList.add(Object) or null
* @param nullForEmpty If true, null is returned instead of an
* empty list.
* @return The List of added items, which may be null, an EMPTY_LIST
* or a SingletonList.
*/
public static List getList(Object list, boolean nullForEmpty)
{
if (list==null)
return nullForEmpty?null:Collections.EMPTY_LIST;
if (list instanceof List)
return (List)list;
List l = new ArrayList(1);
l.add(list);
return l;
}
/* ------------------------------------------------------------ */
public static String[] toStringArray(Object list)
{
if (list==null)
return __EMTPY_STRING_ARRAY;
if (list instanceof List)
{
List l = (List)list;
String[] a = new String[l.size()];
for (int i=l.size();i-->0;)
{
Object o=l.get(i);
if (o!=null)
a[i]=o.toString();
}
return a;
}
return new String[] {list.toString()};
}
/* ------------------------------------------------------------ */
public static Object toArray(Object list,Class aClass)
{
if (list==null)
return (Object[])Array.newInstance(aClass,0);
if (list instanceof List)
{
List l = (List)list;
if (aClass.isPrimitive())
{
Object a = Array.newInstance(aClass,l.size());
for (int i=0;i<l.size();i++)
Array.set(a,i,l.get(i));
return a;
}
return l.toArray((Object[])Array.newInstance(aClass,l.size()));
}
Object a = Array.newInstance(aClass,1);
Array.set(a,0,list);
return a;
}
/* ------------------------------------------------------------ */
/** The size of a lazy List
* @param list A LazyList returned from LazyList.add(Object) or null
* @return the size of the list.
*/
public static int size(Object list)
{
if (list==null)
return 0;
if (list instanceof List)
return ((List)list).size();
return 1;
}
/* ------------------------------------------------------------ */
/** Get item from the list
* @param list A LazyList returned from LazyList.add(Object) or null
* @param i int index
* @return the item from the list.
*/
public static Object get(Object list, int i)
{
if (list==null)
throw new IndexOutOfBoundsException();
if (list instanceof List)
return ((List)list).get(i);
if (i==0)
return list;
throw new IndexOutOfBoundsException();
}
/* ------------------------------------------------------------ */
public static boolean contains(Object list,Object item)
{
if (list==null)
return false;
if (list instanceof List)
return ((List)list).contains(item);
return list.equals(item);
}
/* ------------------------------------------------------------ */
public static Object clone(Object list)
{
if (list==null)
return null;
if (list instanceof List)
return new ArrayList((List)list);
return list;
}
/* ------------------------------------------------------------ */
public static String toString(Object list)
{
if (list==null)
return "[]";
if (list instanceof List)
return ((List)list).toString();
return "["+list+"]";
}
/* ------------------------------------------------------------ */
public static Iterator iterator(Object list)
{
if (list==null)
return Collections.EMPTY_LIST.iterator();
if (list instanceof List)
return ((List)list).iterator();
return getList(list).iterator();
}
/* ------------------------------------------------------------ */
public static ListIterator listIterator(Object list)
{
if (list==null)
return Collections.EMPTY_LIST.listIterator();
if (list instanceof List)
return ((List)list).listIterator();
return getList(list).listIterator();
}
/* ------------------------------------------------------------ */
/**
* @param array Any array of object
* @return A new <i>modifiable</i> list initialised with the elements from <code>array</code>.
*/
public static List array2List(Object[] array)
{
if (array==null || array.length==0)
return new ArrayList();
return new ArrayList(Arrays.asList(array));
}
/* ------------------------------------------------------------ */
/** Add element to an array
* @param array The array to add to (or null)
* @param item The item to add
* @param type The type of the array (in case of null array)
* @return new array with contents of array plus item
*/
public static Object[] addToArray(Object[] array, Object item, Class type)
{
if (array==null)
{
if (type==null && item!=null)
type= item.getClass();
Object[] na = (Object[])Array.newInstance(type, 1);
na[0]=item;
return na;
}
else
{
Class c = array.getClass().getComponentType();
Object[] na = (Object[])Array.newInstance(c, Array.getLength(array)+1);
System.arraycopy(array, 0, na, 0, array.length);
na[array.length]=item;
return na;
}
}
/* ------------------------------------------------------------ */
public static Object[] removeFromArray(Object[] array, Object item)
{
if (item==null || array==null)
return array;
for (int i=array.length;i-->0;)
{
if (item.equals(array[i]))
{
Class c = array==null?item.getClass():array.getClass().getComponentType();
Object[] na = (Object[])Array.newInstance(c, Array.getLength(array)-1);
if (i>0)
System.arraycopy(array, 0, na, 0, i);
if (i+1<array.length)
System.arraycopy(array, i+1, na, i, array.length-(i+1));
return na;
}
}
return array;
}
}
An IdentityMap that uses reference-equality instead of object-equality
/*
* Copyright 2005 Ralf Joachim
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* An IdentityMap that uses reference-equality instead of object-equality. According
* to its special function it violates some design contracts of the <code>Map</code>
* interface.
*
* @author
* @version $Revision: 7491 $ $Date: 2006-04-13 10:49:49 -0600 (Thu, 13 Apr 2006) $
* @since 0.9.9
*/
class IdentitySet implements Set {
//--------------------------------------------------------------------------
/** Default number of buckets. */
private static final int DEFAULT_CAPACITY = 17;
/** Default load factor. */
private static final float DEFAULT_LOAD = 0.75f;
/** Default number of entries. */
private static final int DEFAULT_ENTRIES = (int) (DEFAULT_CAPACITY * DEFAULT_LOAD);
/** Default factor to increment capacity. */
private static final int DEFAULT_INCREMENT = 2;
/** First prime number to check is 3 as we prevent 2 by design. */
private static final int FIRST_PRIME_TO_CHECK = 3;
//--------------------------------------------------------------------------
/** Number of buckets. */
private int _capacity;
/** Maximum number of entries before rehashing. */
private int _maximum;
/** Buckets. */
private Entry[] _buckets;
/** Number of map entries. */
private int _entries = 0;
//--------------------------------------------------------------------------
/**
* Construct a set with default capacity.
*/
public IdentitySet() {
_capacity = DEFAULT_CAPACITY;
_maximum = DEFAULT_ENTRIES;
_buckets = new Entry[DEFAULT_CAPACITY];
}
/**
* Construct a set with given capacity.
*
* @param capacity The capacity of entries this set should be initialized with.
*/
public IdentitySet(final int capacity) {
_capacity = capacity;
_maximum = (int) (capacity * DEFAULT_LOAD);
_buckets = new Entry[capacity];
}
/**
* {@inheritDoc}
* @see java.util.Collection#clear()
*/
public void clear() {
_capacity = DEFAULT_CAPACITY;
_maximum = DEFAULT_ENTRIES;
_buckets = new Entry[DEFAULT_CAPACITY];
_entries = 0;
}
/**
* {@inheritDoc}
* @see java.util.Collection#size()
*/
public int size() { return _entries; }
/**
* {@inheritDoc}
* @see java.util.Collection#isEmpty()
*/
public boolean isEmpty() { return (_entries == 0); }
/**
* {@inheritDoc}
* @see java.util.Collection#add(java.lang.Object)
*/
public boolean add(final Object key) {
int hash = System.identityHashCode(key);
int index = hash % _capacity;
if (index < 0) { index = -index; }
Entry entry = _buckets[index];
Entry prev = null;
while (entry != null) {
if (entry.getKey() == key) {
// There is already a mapping for this key.
return false;
}
prev = entry;
entry = entry.getNext();
}
if (prev == null) {
// There is no previous entry in this bucket.
_buckets[index] = new Entry(key, hash);
} else {
// Next entry is empty so we have no mapping for this key.
prev.setNext(new Entry(key, hash));
}
_entries++;
if (_entries > _maximum) { rehash(); }
return true;
}
/**
* Rehash the map into a new array with increased capacity.
*/
private void rehash() {
long nextCapacity = _capacity * DEFAULT_INCREMENT;
if (nextCapacity > Integer.MAX_VALUE) { return; }
nextCapacity = nextPrime(nextCapacity);
if (nextCapacity > Integer.MAX_VALUE) { return; }
int newCapacity = (int) nextCapacity;
Entry[] newBuckets = new Entry[newCapacity];
Entry entry = null;
Entry temp = null;
Entry next = null;
int newIndex = 0;
for (int index = 0; index < _capacity; index++) {
entry = _buckets[index];
while (entry != null) {
next = entry.getNext();
newIndex = entry.getHash() % newCapacity;
if (newIndex < 0) { newIndex = -newIndex; }
temp = newBuckets[newIndex];
if (temp == null) {
// First entry of the bucket.
entry.setNext(null);
} else {
// Hook entry into beginning of the buckets chain.
entry.setNext(temp);
}
newBuckets[newIndex] = entry;
entry = next;
}
}
_capacity = newCapacity;
_maximum = (int) (newCapacity * DEFAULT_LOAD);
_buckets = newBuckets;
}
/**
* Find next prime number greater than minimum.
*
* @param minimum The minimum (exclusive) value of the next prime number.
* @return The next prime number greater than minimum.
*/
private long nextPrime(final long minimum) {
long candidate = ((minimum + 1) / 2) * 2 + 1;
while (!isPrime(candidate)) { candidate += 2; }
return candidate;
}
/**
* Check for prime number.
*
* @param candidate Number to be checked for being a prime number.
* @return <code>true</code> if the given number is a prime number
* <code>false</code> otherwise.
*/
private boolean isPrime(final long candidate) {
if ((candidate / 2) * 2 == candidate) { return false; }
long stop = candidate / 2;
for (long i = FIRST_PRIME_TO_CHECK; i < stop; i += 2) {
if ((candidate / i) * i == candidate) { return false; }
}
return true;
}
/**
* {@inheritDoc}
* @see java.util.Collection#contains(java.lang.Object)
*/
public boolean contains(final Object key) {
int hash = System.identityHashCode(key);
int index = hash % _capacity;
if (index < 0) { index = -index; }
Entry entry = _buckets[index];
while (entry != null) {
if (entry.getKey() == key) { return true; }
entry = entry.getNext();
}
return false;
}
/**
* {@inheritDoc}
* @see java.util.Collection#remove(java.lang.Object)
*/
public boolean remove(final Object key) {
int hash = System.identityHashCode(key);
int index = hash % _capacity;
if (index < 0) { index = -index; }
Entry entry = _buckets[index];
Entry prev = null;
while (entry != null) {
if (entry.getKey() == key) {
// Found the entry.
if (prev == null) {
// First element in bucket matches.
_buckets[index] = entry.getNext();
} else {
// Remove the entry from the chain.
prev.setNext(entry.getNext());
}
_entries--;
return true;
}
prev = entry;
entry = entry.getNext();
}
return false;
}
/**
* {@inheritDoc}
* @see java.util.Collection#iterator()
*/
public Iterator iterator() {
return new IdentityIterator();
}
/**
* {@inheritDoc}
* @see java.util.Collection#toArray()
*/
public Object[] toArray() {
Object[] result = new Object[_entries];
int j = 0;
for (int i = 0; i < _capacity; i++) {
Entry entry = _buckets[i];
while (entry != null) {
result[j++] = entry.getKey();
entry = entry.getNext();
}
}
return result;
}
/**
* {@inheritDoc}
* @see java.util.Collection#toArray(java.lang.Object[])
*/
public Object[] toArray(final Object[] a) {
Object[] result = a;
if (result.length < _entries) {
result = (Object[]) java.lang.reflect.Array.newInstance(
result.getClass().getComponentType(), _entries);
}
int j = 0;
for (int i = 0; i < _capacity; i++) {
Entry entry = _buckets[i];
while (entry != null) {
result[j++] = entry.getKey();
entry = entry.getNext();
}
}
while (j < result.length) {
result[j++] = null;
}
return result;
}
/**
* In contrast with the design contract of the <code>Set</code> interface this method
* has not been implemented and throws a <code>UnsupportedOperationException</code>.
*
* {@inheritDoc}
* @see java.util.Set#containsAll
*/
public boolean containsAll(final Collection c) {
throw new UnsupportedOperationException();
}
/**
* This optional method has not been implemented for <code>IdentitySet</code> instead
* it throws a <code>UnsupportedOperationException</code> as defined in the
* <code>Set</code> interface.
*
* {@inheritDoc}
* @see java.util.Set#addAll
*/
public boolean addAll(final Collection c) {
throw new UnsupportedOperationException();
}
/**
* This optional method has not been implemented for <code>IdentitySet</code> instead
* it throws a <code>UnsupportedOperationException</code> as defined in the
* <code>Set</code> interface.
*
* {@inheritDoc}
* @see java.util.Set#removeAll
*/
public boolean removeAll(final Collection c) {
throw new UnsupportedOperationException();
}
/**
* This optional method has not been implemented for <code>IdentitySet</code> instead
* it throws a <code>UnsupportedOperationException</code> as defined in the
* <code>Set</code> interface.
*
* {@inheritDoc}
* @see java.util.Set#retainAll
*/
public boolean retainAll(final Collection c) {
throw new UnsupportedOperationException();
}
//--------------------------------------------------------------------------
/**
* An entry of the <code>IdentitySet</code>.
*/
public final class Entry {
/** Key of entry. */
private Object _key;
/** Identity hashcode of key. */
private int _hash;
/** Reference to next entry. */
private Entry _next = null;
/**
* Construct an entry.
*
* @param key Key of entry.
* @param hash Identity hashcode of key.
*/
public Entry(final Object key, final int hash) {
_key = key;
_hash = hash;
}
/**
* Get key of entry.
*
* @return Key of entry.
*/
public Object getKey() { return _key; }
/**
* Get identity hashcode of key.
*
* @return Identity hashcode of key.
*/
public int getHash() { return _hash; }
/**
* Set reference to next entry.
*
* @param next New reference to next entry.
*/
public void setNext(final Entry next) { _next = next; }
/**
* Get reference to next entry.
*
* @return Reference to next entry.
*/
public Entry getNext() { return _next; }
}
//--------------------------------------------------------------------------
/**
* An iterator over all entries of the <code>IdentitySet</code>.
*/
private class IdentityIterator implements Iterator {
/** Index of the current bucket. */
private int _index = 0;
/** The next entry to be returned. <code>null</code> when there is none. */
private Entry _next = _buckets[0];
/**
* Construct a iterator over all entries of the <code>IdentitySet</code>.
*/
public IdentityIterator() {
if (_entries > 0) {
while ((_next == null) && (++_index < _capacity)) {
_next = _buckets[_index];
}
}
}
/**
* {@inheritDoc}
* @see java.util.Iterator#hasNext()
*/
public boolean hasNext() {
return (_next != null);
}
/**
* {@inheritDoc}
* @see java.util.Iterator#next()
*/
public Object next() {
Entry entry = _next;
if (entry == null) { throw new NoSuchElementException(); }
_next = entry.getNext();
while ((_next == null) && (++_index < _capacity)) {
_next = _buckets[_index];
}
return entry.getKey();
}
/**
* This optional method is not implemented for <code>IdentityIterator</code>
* instead it throws a <code>UnsupportedOperationException</code> as defined
* in the <code>Iterator</code> interface.
*
* @see java.util.Iterator#remove()
*/
public void remove() {
throw new UnsupportedOperationException();
}
}
//--------------------------------------------------------------------------
}
An implementation of the java.util.Map interface which can only hold a single object.
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.Serializable;
import java.util.AbstractList;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* An implementation of the java.util.Map interface which can only hold a single object. This is
* particularly useful to control memory usage in Wicket because many containers hold only a single
* component.
*
* @author Jonathan Locke
* @param <K>
* Key type
* @param <V>
* Value type
*/
public final class MicroMap<K, V> implements Map<K, V>, Serializable
{
private static final long serialVersionUID = 1L;
/** The maximum number of entries this map supports. */
public static final int MAX_ENTRIES = 1;
/** The one and only key in this tiny map */
private K key;
/** The value for the only key in this tiny map */
private V value;
/**
* Constructor
*/
public MicroMap()
{
}
/**
* Constructs map with a single key and value pair.
*
* @param key
* The key
* @param value
* The value
*/
public MicroMap(final K key, final V value)
{
put(key, value);
}
/**
* @return True if this MicroMap is full
*/
public boolean isFull()
{
return size() == MAX_ENTRIES;
}
/**
* @see java.util.Map#size()
*/
public int size()
{
return (key != null) ? 1 : 0;
}
/**
* @see java.util.Map#isEmpty()
*/
public boolean isEmpty()
{
return size() == 0;
}
/**
* @see java.util.Map#containsKey(java.lang.Object)
*/
public boolean containsKey(final Object key)
{
return key.equals(this.key);
}
/**
* @see java.util.Map#containsValue(java.lang.Object)
*/
public boolean containsValue(final Object value)
{
return value.equals(this.value);
}
/**
* @see java.util.Map#get(java.lang.Object)
*/
public V get(final Object key)
{
if (key.equals(this.key))
{
return value;
}
return null;
}
/**
* @see java.util.Map#put(java.lang.Object, java.lang.Object)
*/
public V put(final K key, final V value)
{
// Replace?
if (key.equals(this.key))
{
final V oldValue = this.value;
this.value = value;
return oldValue;
}
else
{
// Is there room for a new entry?
if (size() < MAX_ENTRIES)
{
// Store
this.key = key;
this.value = value;
return null;
}
else
{
throw new IllegalStateException("Map full");
}
}
}
/**
* @see java.util.Map#remove(java.lang.Object)
*/
public V remove(final Object key)
{
if (key.equals(this.key))
{
final V oldValue = value;
this.key = null;
value = null;
return oldValue;
}
return null;
}
/**
* @see java.util.Map#putAll(java.util.Map)
*/
public void putAll(final Map< ? extends K, ? extends V> map)
{
if (map.size() <= MAX_ENTRIES)
{
final Entry< ? extends K, ? extends V> e = map.entrySet().iterator().next();
put(e.getKey(), e.getValue());
}
else
{
throw new IllegalStateException("Map full. Cannot add " + map.size() + " entries");
}
}
/**
* @see java.util.Map#clear()
*/
public void clear()
{
key = null;
value = null;
}
/**
* @see java.util.Map#keySet()
*/
public Set<K> keySet()
{
return new AbstractSet<K>()
{
@Override
public Iterator<K> iterator()
{
return new Iterator<K>()
{
public boolean hasNext()
{
return index < MicroMap.this.size();
}
public K next()
{
if (!hasNext())
{
throw new NoSuchElementException();
}
index++;
return key;
}
public void remove()
{
MicroMap.this.clear();
}
int index;
};
}
@Override
public int size()
{
return MicroMap.this.size();
}
};
}
/**
* @see java.util.Map#values()
*/
public Collection<V> values()
{
return new AbstractList<V>()
{
@Override
public V get(final int index)
{
if (index > size() - 1)
{
throw new IndexOutOfBoundsException();
}
return value;
}
@Override
public int size()
{
return MicroMap.this.size();
}
};
}
/**
* @see java.util.Map#entrySet()
*/
public Set<Entry<K, V>> entrySet()
{
return new AbstractSet<Entry<K, V>>()
{
@Override
public Iterator<Entry<K, V>> iterator()
{
return new Iterator<Entry<K, V>>()
{
public boolean hasNext()
{
return index < MicroMap.this.size();
}
public Entry<K, V> next()
{
if (!hasNext())
{
throw new NoSuchElementException();
}
index++;
return new Map.Entry<K, V>()
{
public K getKey()
{
return key;
}
public V getValue()
{
return value;
}
public V setValue(final V value)
{
final V oldValue = MicroMap.this.value;
MicroMap.this.value = value;
return oldValue;
}
};
}
public void remove()
{
clear();
}
int index = 0;
};
}
@Override
public int size()
{
return MicroMap.this.size();
}
};
}
}
An integer hashmap
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.IOException;
import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* This is an integer hashmap that has the exact same features and interface as a normal Map except
* that the key is directly an integer. So no hash is calculated or key object is stored.
*
* @author jcompagner
*
* @param <V>
* The value in the map
*/
public class IntHashMap<V> implements Cloneable, Serializable
{
transient volatile Set<Integer> keySet = null;
transient volatile Collection<V> values = null;
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 16;
/**
* The maximum capacity, used if a higher value is implicitly specified by either of the
* constructors with arguments. MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* The table, resized as necessary. Length MUST Always be a power of two.
*/
transient Entry<V>[] table;
/**
* The number of key-value mappings contained in this identity hash map.
*/
transient int size;
/**
* The next size value at which to resize (capacity * load factor).
*
* @serial
*/
int threshold;
/**
* The load factor for the hash table.
*
* @serial
*/
final float loadFactor;
/**
* The number of times this HashMap has been structurally modified Structural modifications are
* those that change the number of mappings in the HashMap or otherwise modify its internal
* structure (e.g., rehash). This field is used to make iterators on Collection-views of the
* HashMap fail-fast. (See ConcurrentModificationException).
*/
transient volatile int modCount;
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial capacity and load factor.
*
* @param initialCapacity
* The initial capacity.
* @param loadFactor
* The load factor.
* @throws IllegalArgumentException
* if the initial capacity is negative or the load factor is nonpositive.
*/
@SuppressWarnings("unchecked")
public IntHashMap(int initialCapacity, float loadFactor)
{
if (initialCapacity < 0)
{
throw new IllegalArgumentException("Illegal initial capacity: " + //$NON-NLS-1$
initialCapacity);
}
if (initialCapacity > MAXIMUM_CAPACITY)
{
initialCapacity = MAXIMUM_CAPACITY;
}
if (loadFactor <= 0 || Float.isNaN(loadFactor))
{
throw new IllegalArgumentException("Illegal load factor: " + //$NON-NLS-1$
loadFactor);
}
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
{
capacity <<= 1;
}
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
table = new Entry[capacity];
init();
}
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial capacity and the default
* load factor (0.75).
*
* @param initialCapacity
* the initial capacity.
* @throws IllegalArgumentException
* if the initial capacity is negative.
*/
public IntHashMap(int initialCapacity)
{
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity (16) and the default
* load factor (0.75).
*/
@SuppressWarnings("unchecked")
public IntHashMap()
{
loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}
// internal utilities
/**
* Initialization hook for subclasses. This method is called in all constructors and
* pseudo-constructors (clone, readObject) after HashMap has been initialized but before any
* entries have been inserted. (In the absence of this method, readObject would require explicit
* knowledge of subclasses.)
*/
void init()
{
}
/**
* Returns index for hash code h.
*
* @param h
* @param length
* @return The index for the hash integer for the given length
*/
static int indexFor(int h, int length)
{
return h & (length - 1);
}
/**
* Returns the number of key-value mappings in this map.
*
* @return the number of key-value mappings in this map.
*/
public int size()
{
return size;
}
/**
* Returns <tt>true</tt> if this map contains no key-value mappings.
*
* @return <tt>true</tt> if this map contains no key-value mappings.
*/
public boolean isEmpty()
{
return size == 0;
}
/**
* Returns the value to which the specified key is mapped in this identity hash map, or
* <tt>null</tt> if the map contains no mapping for this key. A return value of <tt>null</tt>
* does not <i>necessarily</i> indicate that the map contains no mapping for the key; it is
* also possible that the map explicitly maps the key to <tt>null</tt>. The
* <tt>containsKey</tt> method may be used to distinguish these two cases.
*
* @param key
* the key whose associated value is to be returned.
* @return the value to which this map maps the specified key, or <tt>null</tt> if the map
* contains no mapping for this key.
* @see #put(int, Object)
*/
public V get(int key)
{
int i = indexFor(key, table.length);
Entry<V> e = table[i];
while (true)
{
if (e == null)
{
return null;
}
if (key == e.key)
{
return e.value;
}
e = e.next;
}
}
/**
* Returns <tt>true</tt> if this map contains a mapping for the specified key.
*
* @param key
* The key whose presence in this map is to be tested
* @return <tt>true</tt> if this map contains a mapping for the specified key.
*/
public boolean containsKey(int key)
{
int i = indexFor(key, table.length);
Entry<V> e = table[i];
while (e != null)
{
if (key == e.key)
{
return true;
}
e = e.next;
}
return false;
}
/**
* Returns the entry associated with the specified key in the HashMap. Returns null if the
* HashMap contains no mapping for this key.
*
* @param key
* @return The Entry object for the given hash key
*/
Entry<V> getEntry(int key)
{
int i = indexFor(key, table.length);
Entry<V> e = table[i];
while (e != null && !(key == e.key))
{
e = e.next;
}
return e;
}
/**
* Associates the specified value with the specified key in this map. If the map previously
* contained a mapping for this key, the old value is replaced.
*
* @param key
* key with which the specified value is to be associated.
* @param value
* value to be associated with the specified key.
* @return previous value associated with specified key, or <tt>null</tt> if there was no
* mapping for key. A <tt>null</tt> return can also indicate that the HashMap
* previously associated <tt>null</tt> with the specified key.
*/
public V put(int key, V value)
{
int i = indexFor(key, table.length);
for (Entry<V> e = table[i]; e != null; e = e.next)
{
if (key == e.key)
{
V oldValue = e.value;
e.value = value;
return oldValue;
}
}
modCount++;
addEntry(key, value, i);
return null;
}
/**
* This method is used instead of put by constructors and pseudoconstructors (clone,
* readObject). It does not resize the table, check for comodification, etc. It calls
* createEntry rather than addEntry.
*
* @param key
* @param value
*/
private void putForCreate(int key, V value)
{
int i = indexFor(key, table.length);
/**
* Look for preexisting entry for key. This will never happen for clone or deserialize. It
* will only happen for construction if the input Map is a sorted map whose ordering is
* inconsistent w/ equals.
*/
for (Entry<V> e = table[i]; e != null; e = e.next)
{
if (key == e.key)
{
e.value = value;
return;
}
}
createEntry(key, value, i);
}
void putAllForCreate(IntHashMap<V> m)
{
for (Iterator<Entry<V>> i = m.entrySet().iterator(); i.hasNext();)
{
Entry<V> e = i.next();
putForCreate(e.getKey(), e.getValue());
}
}
/**
* Rehashes the contents of this map into a new array with a larger capacity. This method is
* called automatically when the number of keys in this map reaches its threshold.
*
* If current capacity is MAXIMUM_CAPACITY, this method does not resize the map, but but sets
* threshold to Integer.MAX_VALUE. This has the effect of preventing future calls.
*
* @param newCapacity
* the new capacity, MUST be a power of two; must be greater than current capacity
* unless current capacity is MAXIMUM_CAPACITY (in which case value is irrelevant).
*/
@SuppressWarnings("unchecked")
void resize(int newCapacity)
{
Entry<V>[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY)
{
threshold = Integer.MAX_VALUE;
return;
}
Entry<V>[] newTable = new Entry[newCapacity];
transfer(newTable);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
/**
* Transfer all entries from current table to newTable.
*
* @param newTable
*/
void transfer(Entry<V>[] newTable)
{
Entry<V>[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++)
{
Entry<V> e = src[j];
if (e != null)
{
src[j] = null;
do
{
Entry<V> next = e.next;
int i = indexFor(e.key, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
while (e != null);
}
}
}
/**
* Copies all of the mappings from the specified map to this map These mappings will replace any
* mappings that this map had for any of the keys currently in the specified map.
*
* @param m
* mappings to be stored in this map.
* @throws NullPointerException
* if the specified map is null.
*/
public void putAll(IntHashMap<V> m)
{
int numKeysToBeAdded = m.size();
if (numKeysToBeAdded == 0)
{
return;
}
/*
* Expand the map if the map if the number of mappings to be added is greater than or equal
* to threshold. This is conservative; the obvious condition is (m.size() + size) >=
* threshold, but this condition could result in a map with twice the appropriate capacity,
* if the keys to be added overlap with the keys already in this map. By using the
* conservative calculation, we subject ourself to at most one extra resize.
*/
if (numKeysToBeAdded > threshold)
{
int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);
if (targetCapacity > MAXIMUM_CAPACITY)
{
targetCapacity = MAXIMUM_CAPACITY;
}
int newCapacity = table.length;
while (newCapacity < targetCapacity)
{
newCapacity <<= 1;
}
if (newCapacity > table.length)
{
resize(newCapacity);
}
}
for (Iterator<Entry<V>> i = m.entrySet().iterator(); i.hasNext();)
{
Entry<V> e = i.next();
put(e.getKey(), e.getValue());
}
}
/**
* Removes the mapping for this key from this map if present.
*
* @param key
* key whose mapping is to be removed from the map.
* @return previous value associated with specified key, or <tt>null</tt> if there was no
* mapping for key. A <tt>null</tt> return can also indicate that the map previously
* associated <tt>null</tt> with the specified key.
*/
public V remove(int key)
{
Entry<V> e = removeEntryForKey(key);
return (e == null ? null : e.value);
}
/**
* Removes and returns the entry associated with the specified key in the HashMap. Returns null
* if the HashMap contains no mapping for this key.
*
* @param key
* @return The Entry object that was removed
*/
Entry<V> removeEntryForKey(int key)
{
int i = indexFor(key, table.length);
Entry<V> prev = table[i];
Entry<V> e = prev;
while (e != null)
{
Entry<V> next = e.next;
if (key == e.key)
{
modCount++;
size--;
if (prev == e)
{
table[i] = next;
}
else
{
prev.next = next;
}
return e;
}
prev = e;
e = next;
}
return e;
}
/**
* Special version of remove for EntrySet.
*
* @param o
* @return The entry that was removed
*/
@SuppressWarnings("unchecked")
Entry<V> removeMapping(Object o)
{
if (!(o instanceof Entry))
{
return null;
}
Entry<V> entry = (Entry<V>)o;
int key = entry.getKey();
int i = indexFor(key, table.length);
Entry<V> prev = table[i];
Entry<V> e = prev;
while (e != null)
{
Entry<V> next = e.next;
if (e.key == key && e.equals(entry))
{
modCount++;
size--;
if (prev == e)
{
table[i] = next;
}
else
{
prev.next = next;
}
return e;
}
prev = e;
e = next;
}
return e;
}
/**
* Removes all mappings from this map.
*/
public void clear()
{
modCount++;
Entry<V> tab[] = table;
for (int i = 0; i < tab.length; i++)
{
tab[i] = null;
}
size = 0;
}
/**
* Returns <tt>true</tt> if this map maps one or more keys to the specified value.
*
* @param value
* value whose presence in this map is to be tested.
* @return <tt>true</tt> if this map maps one or more keys to the specified value.
*/
public boolean containsValue(Object value)
{
if (value == null)
{
return containsNullValue();
}
Entry<V> tab[] = table;
for (int i = 0; i < tab.length; i++)
{
for (Entry<V> e = tab[i]; e != null; e = e.next)
{
if (value.equals(e.value))
{
return true;
}
}
}
return false;
}
/**
* Special-case code for containsValue with null argument
*
* @return boolean true if there is a null value in this map
*/
private boolean containsNullValue()
{
Entry<V> tab[] = table;
for (int i = 0; i < tab.length; i++)
{
for (Entry<V> e = tab[i]; e != null; e = e.next)
{
if (e.value == null)
{
return true;
}
}
}
return false;
}
/**
* Returns a shallow copy of this <tt>HashMap</tt> instance: the keys and values themselves
* are not cloned.
*
* @return a shallow copy of this map.
*/
@SuppressWarnings("unchecked")
@Override
public Object clone() throws CloneNotSupportedException
{
IntHashMap<V> result = null;
try
{
result = (IntHashMap<V>)super.clone();
result.table = new Entry[table.length];
result.entrySet = null;
result.modCount = 0;
result.size = 0;
result.init();
result.putAllForCreate(this);
}
catch (CloneNotSupportedException e)
{
// assert false;
}
return result;
}
/**
* @author jcompagner
* @param <V>
* type of value object
*/
public static class Entry<V>
{
final int key;
V value;
Entry<V> next;
/**
* Create new entry.
*
* @param k
* @param v
* @param n
*/
Entry(int k, V v, Entry<V> n)
{
value = v;
next = n;
key = k;
}
/**
* @return The int key of this entry
*/
public int getKey()
{
return key;
}
/**
* @return Gets the value object of this entry
*/
public V getValue()
{
return value;
}
/**
* @param newValue
* @return The previous value
*/
public V setValue(V newValue)
{
V oldValue = value;
value = newValue;
return oldValue;
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@SuppressWarnings("unchecked")
@Override
public boolean equals(Object o)
{
if (!(o instanceof Entry))
{
return false;
}
Entry<V> e = (Entry<V>)o;
int k1 = getKey();
int k2 = e.getKey();
if (k1 == k2)
{
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
{
return true;
}
}
return false;
}
/**
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode()
{
return key ^ (value == null ? 0 : value.hashCode());
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
return getKey() + "=" + getValue(); //$NON-NLS-1$
}
}
/**
* Add a new entry with the specified key, value and hash code to the specified bucket. It is
* the responsibility of this method to resize the table if appropriate.
*
* Subclass overrides this to alter the behavior of put method.
*
* @param key
* @param value
* @param bucketIndex
*/
void addEntry(int key, V value, int bucketIndex)
{
table[bucketIndex] = new Entry<V>(key, value, table[bucketIndex]);
if (size++ >= threshold)
{
resize(2 * table.length);
}
}
/**
* Like addEntry except that this version is used when creating entries as part of Map
* construction or "pseudo-construction" (cloning, deserialization). This version needn"t worry
* about resizing the table.
*
* Subclass overrides this to alter the behavior of HashMap(Map), clone, and readObject.
*
* @param key
* @param value
* @param bucketIndex
*/
void createEntry(int key, V value, int bucketIndex)
{
table[bucketIndex] = new Entry<V>(key, value, table[bucketIndex]);
size++;
}
private abstract class HashIterator<H> implements Iterator<H>
{
Entry<V> next; // next entry to return
int expectedModCount; // For fast-fail
int index; // current slot
Entry<V> current; // current entry
HashIterator()
{
expectedModCount = modCount;
Entry<V>[] t = table;
int i = t.length;
Entry<V> n = null;
if (size != 0)
{ // advance to first entry
while (i > 0 && (n = t[--i]) == null)
{
/* NoOp */;
}
}
next = n;
index = i;
}
/**
* @see java.util.Iterator#hasNext()
*/
public boolean hasNext()
{
return next != null;
}
Entry<V> nextEntry()
{
if (modCount != expectedModCount)
{
throw new ConcurrentModificationException();
}
Entry<V> e = next;
if (e == null)
{
throw new NoSuchElementException();
}
Entry<V> n = e.next;
Entry<V>[] t = table;
int i = index;
while (n == null && i > 0)
{
n = t[--i];
}
index = i;
next = n;
return current = e;
}
/**
* @see java.util.Iterator#remove()
*/
public void remove()
{
if (current == null)
{
throw new IllegalStateException();
}
if (modCount != expectedModCount)
{
throw new ConcurrentModificationException();
}
int k = current.key;
current = null;
removeEntryForKey(k);
expectedModCount = modCount;
}
}
private class ValueIterator extends HashIterator<V>
{
/**
* @see java.util.Iterator#next()
*/
public V next()
{
return nextEntry().value;
}
}
private class KeyIterator extends HashIterator<Integer>
{
/**
* @see java.util.Iterator#next()
*/
public Integer next()
{
return new Integer(nextEntry().getKey());
}
}
private class EntryIterator extends HashIterator<Entry<V>>
{
/**
* @see java.util.Iterator#next()
*/
public Entry<V> next()
{
Entry<V> nextEntry = nextEntry();
return nextEntry;
}
}
// Subclass overrides these to alter behavior of views" iterator() method
Iterator<Integer> newKeyIterator()
{
return new KeyIterator();
}
Iterator<V> newValueIterator()
{
return new ValueIterator();
}
Iterator<Entry<V>> newEntryIterator()
{
return new EntryIterator();
}
// Views
private transient Set<Entry<V>> entrySet = null;
/**
* Returns a set view of the keys contained in this map. The set is backed by the map, so
* changes to the map are reflected in the set, and vice-versa. The set supports element
* removal, which removes the corresponding mapping from this map, via the
* <tt>Iterator.remove</tt>, <tt>Set.remove</tt>, <tt>removeAll</tt>,
* <tt>retainAll</tt>, and <tt>clear</tt> operations. It does not support the <tt>add</tt>
* or <tt>addAll</tt> operations.
*
* @return a set view of the keys contained in this map.
*/
public Set<Integer> keySet()
{
Set<Integer> ks = keySet;
return (ks != null ? ks : (keySet = new KeySet()));
}
private class KeySet extends AbstractSet<Integer>
{
/**
* @see java.util.AbstractCollection#iterator()
*/
@Override
public Iterator<Integer> iterator()
{
return newKeyIterator();
}
/**
* @see java.util.AbstractCollection#size()
*/
@Override
public int size()
{
return size;
}
/**
* @see java.util.AbstractCollection#contains(java.lang.Object)
*/
@Override
public boolean contains(Object o)
{
if (o instanceof Number)
{
return containsKey(((Number)o).intValue());
}
return false;
}
/**
* @see java.util.AbstractCollection#remove(java.lang.Object)
*/
@Override
public boolean remove(Object o)
{
if (o instanceof Number)
{
return removeEntryForKey(((Number)o).intValue()) != null;
}
return false;
}
/**
* @see java.util.AbstractCollection#clear()
*/
@Override
public void clear()
{
IntHashMap.this.clear();
}
}
/**
* Returns a collection view of the values contained in this map. The collection is backed by
* the map, so changes to the map are reflected in the collection, and vice-versa. The
* collection supports element removal, which removes the corresponding mapping from this map,
* via the <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>, <tt>removeAll</tt>,
* <tt>retainAll</tt>, and <tt>clear</tt> operations. It does not support the <tt>add</tt>
* or <tt>addAll</tt> operations.
*
* @return a collection view of the values contained in this map.
*/
public Collection<V> values()
{
Collection<V> vs = values;
return (vs != null ? vs : (values = new Values()));
}
private class Values extends AbstractCollection<V>
{
/**
* @see java.util.AbstractCollection#iterator()
*/
@Override
public Iterator<V> iterator()
{
return newValueIterator();
}
/**
* @see java.util.AbstractCollection#size()
*/
@Override
public int size()
{
return size;
}
/**
* @see java.util.AbstractCollection#contains(java.lang.Object)
*/
@Override
public boolean contains(Object o)
{
return containsValue(o);
}
/**
* @see java.util.AbstractCollection#clear()
*/
@Override
public void clear()
{
IntHashMap.this.clear();
}
}
/**
* Returns a collection view of the mappings contained in this map. Each element in the returned
* collection is a <tt>Map.Entry</tt>. The collection is backed by the map, so changes to the
* map are reflected in the collection, and vice-versa. The collection supports element removal,
* which removes the corresponding mapping from the map, via the <tt>Iterator.remove</tt>,
* <tt>Collection.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt>, and
* <tt>clear</tt> operations. It does not support the <tt>add</tt> or <tt>addAll</tt>
* operations.
*
* @return a collection view of the mappings contained in this map.
* @see Map.Entry
*/
public Set<Entry<V>> entrySet()
{
Set<Entry<V>> es = entrySet;
return (es != null ? es : (entrySet = new EntrySet()));
}
private class EntrySet extends AbstractSet<Entry<V>>
{
/**
* @see java.util.AbstractCollection#iterator()
*/
@Override
public Iterator<Entry<V>> iterator()
{
return newEntryIterator();
}
/**
* @see java.util.AbstractCollection#contains(java.lang.Object)
*/
@SuppressWarnings("unchecked")
@Override
public boolean contains(Object o)
{
if (!(o instanceof Entry))
{
return false;
}
Entry<V> e = (Entry<V>)o;
Entry<V> candidate = getEntry(e.getKey());
return candidate != null && candidate.equals(e);
}
/**
* @see java.util.AbstractCollection#remove(java.lang.Object)
*/
@Override
public boolean remove(Object o)
{
return removeMapping(o) != null;
}
/**
* @see java.util.AbstractCollection#size()
*/
@Override
public int size()
{
return size;
}
/**
* @see java.util.AbstractCollection#clear()
*/
@Override
public void clear()
{
IntHashMap.this.clear();
}
}
/**
* Save the state of the <tt>HashMap</tt> instance to a stream (i.e., serialize it).
*
* @param s
* The ObjectOutputStream
* @throws IOException
*
* @serialData The <i>capacity</i> of the HashMap (the length of the bucket array) is emitted
* (int), followed by the <i>size</i> of the HashMap (the number of key-value
* mappings), followed by the key (Object) and value (Object) for each key-value
* mapping represented by the HashMap The key-value mappings are emitted in the
* order that they are returned by <tt>entrySet().iterator()</tt>.
*
*/
private void writeObject(java.io.ObjectOutputStream s) throws IOException
{
// Write out the threshold, loadfactor, and any hidden stuff
s.defaultWriteObject();
// Write out number of buckets
s.writeInt(table.length);
// Write out size (number of Mappings)
s.writeInt(size);
// Write out keys and values (alternating)
for (Iterator<Entry<V>> i = entrySet().iterator(); i.hasNext();)
{
Entry<V> e = i.next();
s.writeInt(e.getKey());
s.writeObject(e.getValue());
}
}
private static final long serialVersionUID = 362498820763181265L;
/**
* Reconstitute the <tt>HashMap</tt> instance from a stream (i.e., deserialize it).
*
* @param s
* @throws IOException
* @throws ClassNotFoundException
*/
@SuppressWarnings("unchecked")
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException
{
// Read in the threshold, loadfactor, and any hidden stuff
s.defaultReadObject();
// Read in number of buckets and allocate the bucket array;
int numBuckets = s.readInt();
table = new Entry[numBuckets];
init(); // Give subclass a chance to do its thing.
// Read in size (number of Mappings)
int size = s.readInt();
// Read the keys and values, and put the mappings in the HashMap
for (int i = 0; i < size; i++)
{
int key = s.readInt();
V value = (V)s.readObject();
putForCreate(key, value);
}
}
// These methods are used when serializing HashSets
int capacity()
{
return table.length;
}
float loadFactor()
{
return loadFactor;
}
}
Array Map
/*
* Copyright (c) 2002-2008 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.ru]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
public class ArrayMap<K,V>
{
private ArrayEntry<K,V>[] arrayEntries;
private volatile int arrayCount = 0;
private int toMapThreshold = 5;
private Map<K,V> propertyMap = null;
private final boolean useThreadSafeMap;
private boolean switchBackToArray = false;
public ArrayMap()
{
useThreadSafeMap = false;
arrayEntries = new ArrayEntry[toMapThreshold];
}
public ArrayMap( int mapThreshold, boolean threadSafe, boolean shrinkToArray )
{
this.toMapThreshold = mapThreshold;
this.useThreadSafeMap = threadSafe;
this.switchBackToArray = shrinkToArray;
arrayEntries = new ArrayEntry[toMapThreshold];
}
public void put( K key, V value )
{
if ( useThreadSafeMap )
{
synchronizedPut( key, value );
return;
}
for ( int i = 0; i < arrayCount; i++ )
{
if ( arrayEntries[i].getKey().equals( key ) )
{
arrayEntries[i].setNewValue( value );
return;
}
}
if ( arrayCount != -1 )
{
if ( arrayCount < arrayEntries.length )
{
arrayEntries[arrayCount++] = new ArrayEntry<K,V>( key, value );
}
else
{
propertyMap = new HashMap<K,V>();
for ( int i = 0; i < arrayCount; i++ )
{
propertyMap.put( arrayEntries[i].getKey(), arrayEntries[i]
.getValue() );
}
arrayCount = -1;
propertyMap.put( key, value );
}
}
else
{
propertyMap.put( key, value );
}
}
private synchronized void synchronizedPut( K key, V value )
{
for ( int i = 0; i < arrayCount; i++ )
{
if ( arrayEntries[i].getKey().equals( key ) )
{
arrayEntries[i].setNewValue( value );
return;
}
}
if ( arrayCount != -1 )
{
if ( arrayCount < arrayEntries.length )
{
arrayEntries[arrayCount++] = new ArrayEntry<K,V>( key, value );
}
else
{
propertyMap = new ConcurrentHashMap<K,V>();
for ( int i = 0; i < arrayCount; i++ )
{
propertyMap.put( arrayEntries[i].getKey(), arrayEntries[i]
.getValue() );
}
arrayEntries = null;
arrayCount = -1;
propertyMap.put( key, value );
}
}
else
{
propertyMap.put( key, value );
}
}
public V get( K key )
{
if ( key == null )
{
return null;
}
if ( useThreadSafeMap )
{
return synchronizedGet( key );
}
int count = arrayCount;
for ( int i = 0; i < count; i++ )
{
if ( key.equals( arrayEntries[i].getKey() ) )
{
return arrayEntries[i].getValue();
}
}
if ( arrayCount == -1 )
{
return propertyMap.get( key );
}
return null;
}
private synchronized V synchronizedGet( K key )
{
for ( int i = 0; i < arrayCount; i++ )
{
if ( key.equals( arrayEntries[i].getKey() ) )
{
return arrayEntries[i].getValue();
}
}
if ( arrayCount == -1 )
{
return propertyMap.get( key );
}
return null;
}
private synchronized V synchronizedRemove( K key )
{
for ( int i = 0; i < arrayCount; i++ )
{
if ( arrayEntries[i].getKey().equals( key ) )
{
V removedProperty = arrayEntries[i].getValue();
arrayCount--;
System.arraycopy( arrayEntries, i + 1, arrayEntries, i,
arrayCount - i );
return removedProperty;
}
}
if ( arrayCount == -1 )
{
V value = propertyMap.remove( key );
if ( switchBackToArray && propertyMap.size() < toMapThreshold )
{
arrayEntries = new ArrayEntry[toMapThreshold];
int tmpCount = 0;
for ( Entry<K,V> entry : propertyMap.entrySet() )
{
arrayEntries[tmpCount++] = new ArrayEntry<K,V>( entry
.getKey(), entry.getValue() );
}
arrayCount = tmpCount;
}
return value;
}
return null;
}
public V remove( K key )
{
if ( useThreadSafeMap )
{
return synchronizedRemove( key );
}
for ( int i = 0; i < arrayCount; i++ )
{
if ( arrayEntries[i].getKey().equals( key ) )
{
V removedProperty = arrayEntries[i].getValue();
arrayCount--;
System.arraycopy( arrayEntries, i + 1, arrayEntries, i,
arrayCount - i );
return removedProperty;
}
}
if ( arrayCount == -1 )
{
V value = propertyMap.remove( key );
if ( switchBackToArray && propertyMap.size() < toMapThreshold )
{
arrayEntries = new ArrayEntry[toMapThreshold];
int tmpCount = 0;
for ( Entry<K,V> entry : propertyMap.entrySet() )
{
arrayEntries[tmpCount++] = new ArrayEntry<K,V>( entry
.getKey(), entry.getValue() );
}
arrayCount = tmpCount;
}
return value;
}
return null;
}
static class ArrayEntry<K,V> implements Entry<K,V>
{
private K key;
private V value;
ArrayEntry( K key, V value )
{
this.key = key;
this.value = value;
}
public K getKey()
{
return key;
}
public V getValue()
{
return value;
}
void setNewValue( V value )
{
this.value = value;
}
public V setValue( V value )
{
V oldValue = value;
this.value = value;
return oldValue;
}
}
public Iterable<K> keySet()
{
if ( arrayCount == -1 )
{
return propertyMap.keySet();
}
List<K> keys = new LinkedList<K>();
for ( int i = 0; i < arrayCount; i++ )
{
keys.add( arrayEntries[i].getKey() );
}
return keys;
}
public Iterable<V> values()
{
if ( arrayCount == -1 )
{
return propertyMap.values();
}
List<V> values = new LinkedList<V>();
for ( int i = 0; i < arrayCount; i++ )
{
values.add( arrayEntries[i].getValue() );
}
return values;
}
public Set<Entry<K,V>> entrySet()
{
if ( arrayCount == -1 )
{
return propertyMap.entrySet();
}
Set<Entry<K,V>> entries = new HashSet<Entry<K,V>>();
for ( int i = 0; i < arrayCount; i++ )
{
entries.add( arrayEntries[i] );
}
return entries;
}
public int size()
{
if ( arrayCount != -1 )
{
return arrayCount;
}
return propertyMap.size();
}
public void clear()
{
if ( arrayCount != -1 )
{
arrayCount = 0;
}
else
{
propertyMap.clear();
}
}
}
A simple hashmap from keys to integers
/*
* Copyright (c) 2001-2008 Caucho Technology, Inc. All rights reserved.
*
* The Apache Software License, Version 1.1
*
* 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 acknowlegement:
* "This product includes software developed by the
* Caucho Technology (http://www.caucho.ru/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "Burlap", "Resin", and "Caucho" must not be used to
* endorse or promote products derived from this software without prior
* written permission. For written permission, please contact
* info@caucho.ru.
*
* 5. Products derived from this software may not be called "Resin"
* nor may "Resin" appear in their names without prior written
* permission of Caucho Technology.
*
* 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 CAUCHO TECHNOLOGY 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.
*
* @author Scott Ferguson
*/
/**
* The IntMap provides a simple hashmap from keys to integers. The API is
* an abbreviation of the HashMap collection API.
*
* <p>The convenience of IntMap is avoiding all the silly wrapping of
* integers.
*/
public class IntMap {
/**
* Encoding of a null entry. Since NULL is equal to Integer.MIN_VALUE,
* it"s impossible to distinguish between the two.
*/
public final static int NULL = 0xdeadbeef; // Integer.MIN_VALUE + 1;
private static final Object DELETED = new Object();
private Object []_keys;
private int []_values;
private int _size;
private int _mask;
/**
* Create a new IntMap. Default size is 16.
*/
public IntMap()
{
_keys = new Object[256];
_values = new int[256];
_mask = _keys.length - 1;
_size = 0;
}
/**
* Clear the hashmap.
*/
public void clear()
{
Object []keys = _keys;
int []values = _values;
for (int i = keys.length - 1; i >= 0; i--) {
keys[i] = null;
values[i] = 0;
}
_size = 0;
}
/**
* Returns the current number of entries in the map.
*/
public int size()
{
return _size;
}
/**
* Puts a new value in the property table with the appropriate flags
*/
public int get(Object key)
{
int mask = _mask;
int hash = key.hashCode() % mask & mask;
Object []keys = _keys;
while (true) {
Object mapKey = keys[hash];
if (mapKey == null)
return NULL;
else if (mapKey == key || mapKey.equals(key))
return _values[hash];
hash = (hash + 1) % mask;
}
}
/**
* Expands the property table
*/
private void resize(int newSize)
{
Object []newKeys = new Object[newSize];
int []newValues = new int[newSize];
int mask = _mask = newKeys.length - 1;
Object []keys = _keys;
int values[] = _values;
for (int i = keys.length - 1; i >= 0; i--) {
Object key = keys[i];
if (key == null || key == DELETED)
continue;
int hash = key.hashCode() % mask & mask;
while (true) {
if (newKeys[hash] == null) {
newKeys[hash] = key;
newValues[hash] = values[i];
break;
}
hash = (hash + 1) % mask;
}
}
_keys = newKeys;
_values = newValues;
}
/**
* Puts a new value in the property table with the appropriate flags
*/
public int put(Object key, int value)
{
int mask = _mask;
int hash = key.hashCode() % mask & mask;
Object []keys = _keys;
while (true) {
Object testKey = keys[hash];
if (testKey == null || testKey == DELETED) {
keys[hash] = key;
_values[hash] = value;
_size++;
if (keys.length <= 4 * _size)
resize(4 * keys.length);
return NULL;
}
else if (key != testKey && ! key.equals(testKey)) {
hash = (hash + 1) % mask;
continue;
}
else {
int old = _values[hash];
_values[hash] = value;
return old;
}
}
}
/**
* Deletes the entry. Returns true if successful.
*/
public int remove(Object key)
{
int mask = _mask;
int hash = key.hashCode() % mask & mask;
while (true) {
Object mapKey = _keys[hash];
if (mapKey == null)
return NULL;
else if (mapKey == key) {
_keys[hash] = DELETED;
_size--;
return _values[hash];
}
hash = (hash + 1) % mask;
}
}
public String toString()
{
StringBuffer sbuf = new StringBuffer();
sbuf.append("IntMap[");
boolean isFirst = true;
for (int i = 0; i <= _mask; i++) {
if (_keys[i] != null && _keys[i] != DELETED) {
if (! isFirst)
sbuf.append(", ");
isFirst = false;
sbuf.append(_keys[i]);
sbuf.append(":");
sbuf.append(_values[i]);
}
}
sbuf.append("]");
return sbuf.toString();
}
}
Cache Map
/*******************************************************************************
* Copyright (c) 2007, 2008 IBM Corporation and Others
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Hisashi MIYASHITA - initial API and implementation
* Kentarou FUKUDA - initial API and implementation
*******************************************************************************/
import java.util.Iterator;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* Utility class for cache map.
*/
public class CacheMap extends TreeMap<String, Object> {
private static final long serialVersionUID = 6681131647931821052L;
private final int maxSize;
private final int evictSize;
private final LinkedList<Object> accessList = new LinkedList<Object>();
/**
* Constructor of cache map. If the map exceed the maximum size, the
* key/value sets will be removed from map based on specified evict size.
*
* @param maxSize
* maximum size of the map
* @param evictSize
* number of evict object
*/
public CacheMap(int maxSize, int evictSize) {
this.maxSize = maxSize;
this.evictSize = evictSize;
}
private void evict() {
Iterator<Object> it = accessList.iterator();
for (int i = 0; i < evictSize; i++) {
if (!it.hasNext())
return;
Object key = it.next();
this.remove(key);
it.remove();
}
}
private int searchAccessList(Object key) {
return accessList.indexOf(key);
}
private void accessEntry(Object key) {
int idx = searchAccessList(key);
if (idx >= 0) {
accessList.remove(idx);
}
accessList.add(key);
}
public Object put(String key, Object val) {
if (size() >= maxSize)
evict();
accessEntry(key);
return super.put(key, val);
}
public Object get(Object key) {
accessEntry(key);
return super.get(key);
}
/**
* Search a key that starts with the specified prefix from the map, and
* return the value corresponding to the key.
*
* @param prefix
* target prefix
* @return the value whose key starts with prefix, or null if not available
*/
public Object matchStartsWith(String prefix) {
SortedMap<String, Object> smap = super.tailMap(prefix);
Object okey;
try {
okey = smap.firstKey();
} catch (NoSuchElementException e) {
return null;
}
if (!(okey instanceof String))
return null;
String key = (String) okey;
// System.err.println("MSW:" + key + " / " + prefix);
if (!key.startsWith(prefix))
return null;
return super.get(key);
}
}
CaseBlindHashMap - a HashMap extension, using Strings as key values.
/**********************************************************************************
*
* Copyright (c) 2003, 2004 The Regents of the University of Michigan, Trustees of Indiana University,
* Board of Trustees of the Leland Stanford, Jr., University, and The MIT Corporation
*
* Licensed under the Educational Community License Version 1.0 (the "License");
* By obtaining, using and/or copying this Original Work, you agree that you have read,
* understand, and will comply with the terms and conditions of the Educational Community License.
* You may obtain a copy of the License at:
*
* http://cvs.sakaiproject.org/licenses/license_1_0.html
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
**********************************************************************************/
/*
Copyright (c) 2000-2003 Board of Trustees of Leland Stanford Jr. University,
all rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
STANFORD UNIVERSITY BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of Stanford University shall not
be used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization from Stanford University.
*/
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
/**
* CaseBlindHashMap - a HashMap extension, using <code>String</code>s as key
* values.
* <p>
* Internally, keys are case insensitive: <code>ABC</code> = <code>abc</code>.
* <p>
* Two methods have been added to facilitate working with Sets of key strings.
* See <code>stringKeySet()</code> and <code>stringKeyIterator()</code>.
*/
public class CaseBlindHashMap extends HashMap {
/**
* Constructors
*/
public CaseBlindHashMap() {
super();
}
public CaseBlindHashMap(CaseBlindHashMap map) {
super(map);
}
public CaseBlindHashMap(int initCap) {
super(initCap);
}
public CaseBlindHashMap(int initCap, float loadFactor) {
super(initCap, loadFactor);
}
/*
* Extensions
*/
/**
* Get the set of keys contained in this map. Keys values are returned as
* simple <code>String</code>s (not the <code>CaseBlindString</code>s
* used internally).
* <p>
* This is accopmlished by making a copy of the original map - modifications
* made to this copy are not reflected in the original.
*
* @return The set of keys
*/
public Set stringKeySet() {
Iterator iterator = super.keySet().iterator();
HashMap stringKeys = new HashMap();
while (iterator.hasNext()) {
String key = ((CaseBlindString) iterator.next()).toString();
stringKeys.put(key, get(key));
}
return stringKeys.keySet();
}
/**
* Get an Iterator to the String based key set
*
* @return An iterator to the key set
*/
public Iterator stringKeyIterator() {
return stringKeySet().iterator();
}
/*
* Overridden HashMap methods
*/
/**
* Does the map contain this key?
*
* @param key
* The key to look up
* @return true If the key is present in the map
*/
public boolean containsKey(String key) {
return super.containsKey(new CaseBlindString(key));
}
/**
* Fetch a value by name - null keys are not supported
*
* @param key
* The key to look up
* @return The associated value object
*/
public Object get(String key) {
return super.get(new CaseBlindString(key));
}
/**
* Add the key/value pair to the map - null values are not supported
*
* @param key
* The key name
* @param value
* The object to store
*/
public void put(String key, Object value) {
super.put(new CaseBlindString(key), value);
}
/**
* Remove a key/value pair from this map
*
* @param key
* Non-null key to remove
*/
public void remove(String key) {
if (key == null) {
throw new UnsupportedOperationException("null key");
}
super.remove(new CaseBlindString(key));
}
/**
* A crude, case insensitive string - used internally to represent key values.
* Preserve the originl case, but compare for equality in a case blind
* fashion.
*/
public static class CaseBlindString {
String string;
/**
* Constructors
*/
private CaseBlindString() {
}
public CaseBlindString(String string) {
this.string = string;
}
/**
* Fetch the original string
*
* @return The original string
*/
public String toString() {
return string;
}
/**
* Case insensitive compare
*
* @return True if the two strings match
*/
public boolean equals(Object object) {
if (string == null) {
return string == null;
}
return string.equalsIgnoreCase(((CaseBlindString) object).toString());
}
/**
* Get a hash code for this case insensitive string
*
* @return Hash code value
*/
public int hashCode() {
if (string == null) {
return "null".hashCode();
}
return string.toUpperCase().hashCode();
}
}
}
Case Insensitive Map
// Copyright 2007 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import java.io.Serializable;
import java.util.*;
/**
* An mapped collection where the keys are always strings and access to values is case-insensitive. The case of keys in
* the map is <em>maintained</em>, but on any access to a key (directly or indirectly), all key comparisons are
* performed in a case-insensitive manner. The map implementation is intended to support a reasonably finite number
* (dozens or hundreds, not thousands or millions of key/value pairs. Unlike HashMap, it is based on a sorted list of
* entries rather than hash bucket. It is also geared towards a largely static map, one that is created and then used
* without modification.
*
* @param <V> the type of value stored
*/
public class CaseInsensitiveMap<V> extends AbstractMap<String, V> implements Serializable
{
private static final long serialVersionUID = 3362718337611953298L;
private static final int NULL_HASH = Integer.MIN_VALUE;
private static final int DEFAULT_SIZE = 20;
private static class CIMEntry<V> implements Map.Entry<String, V>, Serializable
{
private static final long serialVersionUID = 6713986085221148350L;
private String key;
private final int hashCode;
V value;
public CIMEntry(final String key, final int hashCode, V value)
{
this.key = key;
this.hashCode = hashCode;
this.value = value;
}
public String getKey()
{
return key;
}
public V getValue()
{
return value;
}
public V setValue(V value)
{
V result = this.value;
this.value = value;
return result;
}
/**
* Returns true if both keys are null, or if the provided key is the same as, or case-insensitively equal to,
* the entrie"s key.
*
* @param key to compare against
* @return true if equal
*/
@SuppressWarnings({ "StringEquality" })
boolean matches(String key)
{
return key == this.key || (key != null && key.equalsIgnoreCase(this.key));
}
boolean valueMatches(Object value)
{
return value == this.value || (value != null && value.equals(this.value));
}
}
private class EntrySetIterator implements Iterator
{
int expectedModCount = modCount;
int index;
int current = -1;
public boolean hasNext()
{
return index < size;
}
public Object next()
{
check();
if (index >= size) throw new NoSuchElementException();
current = index++;
return entries[current];
}
public void remove()
{
check();
if (current < 0) throw new NoSuchElementException();
new Position(current, true).remove();
expectedModCount = modCount;
}
private void check()
{
if (expectedModCount != modCount) throw new ConcurrentModificationException();
}
}
@SuppressWarnings("unchecked")
private class EntrySet extends AbstractSet
{
@Override
public Iterator iterator()
{
return new EntrySetIterator();
}
@Override
public int size()
{
return size;
}
@Override
public void clear()
{
CaseInsensitiveMap.this.clear();
}
@Override
public boolean contains(Object o)
{
if (!(o instanceof Map.Entry)) return false;
Map.Entry e = (Map.Entry) o;
Position position = select(e.getKey());
return position.isFound() && position.entry().valueMatches(e.getValue());
}
@Override
public boolean remove(Object o)
{
if (!(o instanceof Map.Entry)) return false;
Map.Entry e = (Map.Entry) o;
Position position = select(e.getKey());
if (position.isFound() && position.entry().valueMatches(e.getValue()))
{
position.remove();
return true;
}
return false;
}
}
private class Position
{
private final int cursor;
private final boolean found;
Position(int cursor, boolean found)
{
this.cursor = cursor;
this.found = found;
}
boolean isFound()
{
return found;
}
CIMEntry<V> entry()
{
return entries[cursor];
}
V get()
{
return found ? entries[cursor].value : null;
}
V remove()
{
if (!found) return null;
V result = entries[cursor].value;
// Remove the entry by shifting everything else down.
System.arraycopy(entries, cursor + 1, entries, cursor, size - cursor - 1);
// We shifted down, leaving one (now duplicate) entry behind.
entries[--size] = null;
// A structural change for sure
modCount++;
return result;
}
@SuppressWarnings("unchecked")
V put(String key, int hashCode, V newValue)
{
if (found)
{
CIMEntry<V> e = entries[cursor];
V result = e.value;
// Not a structural change, so no change to modCount
// Update the key (to maintain case). By definition, the hash code
// will not change.
e.key = key;
e.value = newValue;
return result;
}
// Not found, we"re going to add it.
int newSize = size + 1;
if (newSize == entries.length)
{
// Time to expand!
int newCapacity = (size * 3) / 2 + 1;
CIMEntry<V>[] newEntries = new CIMEntry[newCapacity];
System.arraycopy(entries, 0, newEntries, 0, cursor);
System.arraycopy(entries, cursor, newEntries, cursor + 1, size - cursor);
entries = newEntries;
}
else
{
// Open up a space for the new entry
System.arraycopy(entries, cursor, entries, cursor + 1, size - cursor);
}
CIMEntry<V> newEntry = new CIMEntry<V>(key, hashCode, newValue);
entries[cursor] = newEntry;
size++;
// This is definately a structural change
modCount++;
return null;
}
}
// The list of entries. This is kept sorted by hash code. In some cases, there may be different
// keys with the same hash code in adjacent indexes.
private CIMEntry<V>[] entries;
private int size = 0;
// Used by iterators to check for concurrent modifications
private transient int modCount = 0;
private transient Set<Map.Entry<String, V>> entrySet;
public CaseInsensitiveMap()
{
this(DEFAULT_SIZE);
}
@SuppressWarnings("unchecked")
public CaseInsensitiveMap(int size)
{
entries = new CIMEntry[Math.max(size, 3)];
}
public CaseInsensitiveMap(Map<String, ? extends V> map)
{
this(map.size());
for (Map.Entry<String, ? extends V> entry : map.entrySet())
{
put(entry.getKey(), entry.getValue());
}
}
@Override
public void clear()
{
for (int i = 0; i < size; i++)
entries[i] = null;
size = 0;
modCount++;
}
@Override
public boolean isEmpty()
{
return size == 0;
}
@Override
public int size()
{
return size;
}
@SuppressWarnings("unchecked")
@Override
public V put(String key, V value)
{
int hashCode = caseInsenitiveHashCode(key);
return select(key, hashCode).put(key, hashCode, value);
}
@Override
public boolean containsKey(Object key)
{
return select(key).isFound();
}
@Override
public V get(Object key)
{
return select(key).get();
}
@Override
public V remove(Object key)
{
return select(key).remove();
}
@SuppressWarnings("unchecked")
@Override
public Set<Map.Entry<String, V>> entrySet()
{
if (entrySet == null) entrySet = new EntrySet();
return entrySet;
}
private Position select(Object key)
{
if (key == null || key instanceof String)
{
String keyString = (String) key;
return select(keyString, caseInsenitiveHashCode(keyString));
}
return new Position(0, false);
}
/**
* Searches the elements for the index of the indicated key and (case insensitive) hash code. Sets the _cursor and
* _found attributes.
*/
private Position select(String key, int hashCode)
{
if (size == 0) return new Position(0, false);
int low = 0;
int high = size - 1;
int cursor;
while (low <= high)
{
cursor = (low + high) >> 1;
CIMEntry e = entries[cursor];
if (e.hashCode < hashCode)
{
low = cursor + 1;
continue;
}
if (e.hashCode > hashCode)
{
high = cursor - 1;
continue;
}
return tunePosition(key, hashCode, cursor);
}
return new Position(low, false);
}
/**
* select() has located a matching hashCode, but there"s an outlying possibility that multiple keys share the same
* hashCode. Backup the cursor until we get to locate the initial hashCode match, then march forward until the key
* is located, or the hashCode stops matching.
*
* @param key
* @param hashCode
*/
private Position tunePosition(String key, int hashCode, int cursor)
{
boolean found = false;
while (cursor > 0)
{
if (entries[cursor - 1].hashCode != hashCode) break;
cursor--;
}
while (true)
{
if (entries[cursor].matches(key))
{
found = true;
break;
}
// Advance to the next entry.
cursor++;
// If out of entries,
if (cursor >= size || entries[cursor].hashCode != hashCode) break;
}
return new Position(cursor, found);
}
static int caseInsenitiveHashCode(String input)
{
if (input == null) return NULL_HASH;
int length = input.length();
int hash = 0;
// This should end up more or less equal to input.toLowerCase().hashCode(), unless String
// changes its implementation. Let"s hope this is reasonably fast.
for (int i = 0; i < length; i++)
{
int ch = input.charAt(i);
int caselessCh = Character.toLowerCase(ch);
hash = 31 * hash + caselessCh;
}
return hash;
}
}
Combines multiple values to form a single composite key. MultiKey can often be used as an alternative to nested maps.
// Copyright 2006 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import java.util.Arrays;
/**
* Combines multiple values to form a single composite key. MultiKey can often be used as an alternative to nested
* maps.
*/
public final class MultiKey
{
private static final int PRIME = 31;
private final Object[] values;
private final int hashCode;
/**
* Creates a new instance from the provided values. It is assumed that the values provided are good map keys
* themselves -- immutable, with proper implementations of equals() and hashCode().
*
* @param values
*/
public MultiKey(Object... values)
{
this.values = values;
hashCode = PRIME * Arrays.hashCode(this.values);
}
@Override
public int hashCode()
{
return hashCode;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final MultiKey other = (MultiKey) obj;
return Arrays.equals(values, other.values);
}
@Override
public String toString()
{
StringBuilder builder = new StringBuilder("MultiKey[");
boolean first = true;
for (Object o : values)
{
if (!first)
builder.append(", ");
builder.append(o);
first = false;
}
builder.append("]");
return builder.toString();
}
}
Complex Key HashMap
/*
* Copyright 2003-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.util.NoSuchElementException;
public class ComplexKeyHashMap
{
public static class Entry {
public int hash;
public Entry next;
public Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
protected Entry table [];
protected static final int DEFAULT_CAPACITY = 32;
protected static final int MINIMUM_CAPACITY = 4;
protected static final int MAXIMUM_CAPACITY = 1 << 28;
protected int size;
protected transient int threshold;
public ComplexKeyHashMap() {
init(DEFAULT_CAPACITY);
}
public ComplexKeyHashMap(boolean b) {
}
public ComplexKeyHashMap(int expectedMaxSize) {
init (capacity(expectedMaxSize));
}
public static int hash(int h) {
h += ~(h << 9);
h ^= (h >>> 14);
h += (h << 4);
h ^= (h >>> 10);
return h;
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public void clear() {
Object[] tab = table;
for (int i = 0; i < tab.length; i++)
tab[i] = null;
size = 0;
}
public void init(int initCapacity) {
threshold = (initCapacity * 6)/8;
table = new Entry[initCapacity];
}
public void resize(int newLength) {
Entry[] oldTable = table;
int oldLength = table.length;
Entry[] newTable = new Entry[newLength];
for (int j = 0; j < oldLength; j++) {
for (Entry e = oldTable [j]; e != null;) {
Entry next = e.next;
int index = e.hash & (newLength-1);
e.next = newTable[index];
newTable [index] = e;
e = next;
}
}
table = newTable;
threshold = (6 * newLength) / 8;
}
private int capacity(int expectedMaxSize) {
// Compute min capacity for expectedMaxSize given a load factor of 3/4
int minCapacity = (8 * expectedMaxSize)/6;
// Compute the appropriate capacity
int result;
if (minCapacity > MAXIMUM_CAPACITY || minCapacity < 0) {
result = MAXIMUM_CAPACITY;
} else {
result = MINIMUM_CAPACITY;
while (result < minCapacity)
result <<= 1;
}
return result;
}
public interface EntryIterator {
boolean hasNext ();
Entry next ();
}
public ComplexKeyHashMap.Entry[] getTable() {
return table;
}
public EntryIterator getEntrySetIterator() {
return new EntryIterator() {
Entry next; // next entry to return
int index; // current slot
Entry current; // current entry
{
Entry[] t = table;
int i = t.length;
Entry n = null;
if (size != 0) { // advance to first entry
while (i > 0 && (n = t[--i]) == null) {}
}
next = n;
index = i;
}
public boolean hasNext() {
return next != null;
}
public Entry next() {
return nextEntry();
}
Entry nextEntry() {
Entry e = next;
if (e == null)
throw new NoSuchElementException();
Entry n = e.next;
Entry[] t = table;
int i = index;
while (n == null && i > 0)
n = t[--i];
index = i;
next = n;
return current = e;
}
};
}
}
Concurrent Skip List Map
/*
* Written by Doug Lea with assistance from members of JCP JSR-166
* Expert Group and released to the public domain, as explained at
* http://creativecommons.org/licenses/publicdomain
*/
import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.ruparator;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
/**
* A {@link SortedMap} extended with navigation methods returning the closest
* matches for given search targets. Methods <tt>lowerEntry</tt>,
* <tt>floorEntry</tt>, <tt>ceilingEntry</tt>, and <tt>higherEntry</tt>
* return <tt>Map.Entry</tt> objects associated with keys respectively less
* than, less than or equal, greater than or equal, and greater than a given
* key, returning <tt>null</tt> if there is no such key. Similarly, methods
* <tt>lowerKey</tt>, <tt>floorKey</tt>, <tt>ceilingKey</tt>, and
* <tt>higherKey</tt> return only the associated keys. All of these methods
* are designed for locating, not traversing entries.
*
* <p>
* A <tt>NavigableMap</tt> may be viewed and traversed in either ascending or
* descending key order. The <tt>Map</tt> methods <tt>keySet</tt> and
* <tt>entrySet</tt> return ascending views, and the additional methods
* <tt>descendingKeySet</tt> and <tt>descendingEntrySet</tt> return
* descending views. The performance of ascending traversals is likely to be
* faster than descending traversals. Notice that it is possible to perform
* subrannge traversals in either direction using <tt>SubMap</tt>.
*
* <p>
* This interface additionally defines methods <tt>firstEntry</tt>,
* <tt>pollFirstEntry</tt>, <tt>lastEntry</tt>, and <tt>pollLastEntry</tt>
* that return and/or remove the least and greatest mappings, if any exist, else
* returning <tt>null</tt>.
*
* <p>
* Implementations of entry-returning methods are expected to return
* <tt>Map.Entry</tt> pairs representing snapshots of mappings at the time
* they were produced, and thus generally do <em>not</em> support the optional
* <tt>Entry.setValue</tt> method. Note however that it is possible to change
* mappings in the associated map using method <tt>put</tt>.
*
* @author Doug Lea
* @param <K>
* the type of keys maintained by this map
* @param <V>
* the type of mapped values
*/
interface NavigableMap<K, V> extends SortedMap<K, V> {
/**
* Returns a key-value mapping associated with the least key greater than or
* equal to the given key, or <tt>null</tt> if there is no such entry.
*
* @param key
* the key.
* @return an Entry associated with ceiling of given key, or <tt>null</tt>
* if there is no such Entry.
* @throws ClassCastException
* if key cannot be compared with the keys currently in the map.
* @throws NullPointerException
* if key is <tt>null</tt> and this map does not support
* <tt>null</tt> keys.
*/
public Map.Entry<K, V> ceilingEntry(K key);
/**
* Returns least key greater than or equal to the given key, or <tt>null</tt>
* if there is no such key.
*
* @param key
* the key.
* @return the ceiling key, or <tt>null</tt> if there is no such key.
* @throws ClassCastException
* if key cannot be compared with the keys currently in the map.
* @throws NullPointerException
* if key is <tt>null</tt> and this map does not support
* <tt>null</tt> keys.
*/
public K ceilingKey(K key);
/**
* Returns a key-value mapping associated with the greatest key strictly less
* than the given key, or <tt>null</tt> if there is no such entry.
*
* @param key
* the key.
* @return an Entry with greatest key less than the given key, or
* <tt>null</tt> if there is no such Entry.
* @throws ClassCastException
* if key cannot be compared with the keys currently in the map.
* @throws NullPointerException
* if key is <tt>null</tt> and this map does not support
* <tt>null</tt> keys.
*/
public Map.Entry<K, V> lowerEntry(K key);
/**
* Returns the greatest key strictly less than the given key, or <tt>null</tt>
* if there is no such key.
*
* @param key
* the key.
* @return the greatest key less than the given key, or <tt>null</tt> if
* there is no such key.
* @throws ClassCastException
* if key cannot be compared with the keys currently in the map.
* @throws NullPointerException
* if key is <tt>null</tt> and this map does not support
* <tt>null</tt> keys.
*/
public K lowerKey(K key);
/**
* Returns a key-value mapping associated with the greatest key less than or
* equal to the given key, or <tt>null</tt> if there is no such entry.
*
* @param key
* the key.
* @return an Entry associated with floor of given key, or <tt>null</tt> if
* there is no such Entry.
* @throws ClassCastException
* if key cannot be compared with the keys currently in the map.
* @throws NullPointerException
* if key is <tt>null</tt> and this map does not support
* <tt>null</tt> keys.
*/
public Map.Entry<K, V> floorEntry(K key);
/**
* Returns the greatest key less than or equal to the given key, or
* <tt>null</tt> if there is no such key.
*
* @param key
* the key.
* @return the floor of given key, or <tt>null</tt> if there is no such key.
* @throws ClassCastException
* if key cannot be compared with the keys currently in the map.
* @throws NullPointerException
* if key is <tt>null</tt> and this map does not support
* <tt>null</tt> keys.
*/
public K floorKey(K key);
/**
* Returns a key-value mapping associated with the least key strictly greater
* than the given key, or <tt>null</tt> if there is no such entry.
*
* @param key
* the key.
* @return an Entry with least key greater than the given key, or
* <tt>null</tt> if there is no such Entry.
* @throws ClassCastException
* if key cannot be compared with the keys currently in the map.
* @throws NullPointerException
* if key is <tt>null</tt> and this map does not support
* <tt>null</tt> keys.
*/
public Map.Entry<K, V> higherEntry(K key);
/**
* Returns the least key strictly greater than the given key, or <tt>null</tt>
* if there is no such key.
*
* @param key
* the key.
* @return the least key greater than the given key, or <tt>null</tt> if
* there is no such key.
* @throws ClassCastException
* if key cannot be compared with the keys currently in the map.
* @throws NullPointerException
* if key is <tt>null</tt> and this map does not support
* <tt>null</tt> keys.
*/
public K higherKey(K key);
/**
* Returns a key-value mapping associated with the least key in this map, or
* <tt>null</tt> if the map is empty.
*
* @return an Entry with least key, or <tt>null</tt> if the map is empty.
*/
public Map.Entry<K, V> firstEntry();
/**
* Returns a key-value mapping associated with the greatest key in this map,
* or <tt>null</tt> if the map is empty.
*
* @return an Entry with greatest key, or <tt>null</tt> if the map is empty.
*/
public Map.Entry<K, V> lastEntry();
/**
* Removes and returns a key-value mapping associated with the least key in
* this map, or <tt>null</tt> if the map is empty.
*
* @return the removed first entry of this map, or <tt>null</tt> if the map
* is empty.
*/
public Map.Entry<K, V> pollFirstEntry();
/**
* Removes and returns a key-value mapping associated with the greatest key in
* this map, or <tt>null</tt> if the map is empty.
*
* @return the removed last entry of this map, or <tt>null</tt> if the map
* is empty.
*/
public Map.Entry<K, V> pollLastEntry();
/**
* Returns a set view of the keys contained in this map, in descending key
* order. The set is backed by the map, so changes to the map are reflected in
* the set, and vice-versa. If the map is modified while an iteration over the
* set is in progress (except through the iterator"s own <tt>remove</tt>
* operation), the results of the iteration are undefined. The set supports
* element removal, which removes the corresponding mapping from the map, via
* the <tt>Iterator.remove</tt>, <tt>Set.remove</tt>,
* <tt>removeAll</tt> <tt>retainAll</tt>, and <tt>clear</tt>
* operations. It does not support the add or <tt>addAll</tt> operations.
*
* @return a set view of the keys contained in this map.
*/
Set<K> descendingKeySet();
/**
* Returns a set view of the mappings contained in this map, in descending key
* order. Each element in the returned set is a <tt>Map.Entry</tt>. The set
* is backed by the map, so changes to the map are reflected in the set, and
* vice-versa. If the map is modified while an iteration over the set is in
* progress (except through the iterator"s own <tt>remove</tt> operation, or
* through the <tt>setValue</tt> operation on a map entry returned by the
* iterator) the results of the iteration are undefined. The set supports
* element removal, which removes the corresponding mapping from the map, via
* the <tt>Iterator.remove</tt>, <tt>Set.remove</tt>, <tt>removeAll</tt>,
* <tt>retainAll</tt> and <tt>clear</tt> operations. It does not support
* the <tt>add</tt> or <tt>addAll</tt> operations.
*
* @return a set view of the mappings contained in this map.
*/
Set<Map.Entry<K, V>> descendingEntrySet();
/**
* Returns a view of the portion of this map whose keys range from
* <tt>fromKey</tt>, inclusive, to <tt>toKey</tt>, exclusive. (If
* <tt>fromKey</tt> and <tt>toKey</tt> are equal, the returned sorted map
* is empty.) The returned sorted map is backed by this map, so changes in the
* returned sorted map are reflected in this map, and vice-versa.
*
* @param fromKey
* low endpoint (inclusive) of the subMap.
* @param toKey
* high endpoint (exclusive) of the subMap.
*
* @return a view of the portion of this map whose keys range from
* <tt>fromKey</tt>, inclusive, to <tt>toKey</tt>, exclusive.
*
* @throws ClassCastException
* if <tt>fromKey</tt> and <tt>toKey</tt> cannot be compared to
* one another using this map"s comparator (or, if the map has no
* comparator, using natural ordering).
* @throws IllegalArgumentException
* if <tt>fromKey</tt> is greater than <tt>toKey</tt>.
* @throws NullPointerException
* if <tt>fromKey</tt> or <tt>toKey</tt> is <tt>null</tt> and
* this map does not support <tt>null</tt> keys.
*/
public NavigableMap<K, V> subMap(K fromKey, K toKey);
/**
* Returns a view of the portion of this map whose keys are strictly less than
* <tt>toKey</tt>. The returned sorted map is backed by this map, so
* changes in the returned sorted map are reflected in this map, and
* vice-versa.
*
* @param toKey
* high endpoint (exclusive) of the headMap.
* @return a view of the portion of this map whose keys are strictly less than
* <tt>toKey</tt>.
*
* @throws ClassCastException
* if <tt>toKey</tt> is not compatible with this map"s comparator
* (or, if the map has no comparator, if <tt>toKey</tt> does not
* implement <tt>Comparable</tt>).
* @throws NullPointerException
* if <tt>toKey</tt> is <tt>null</tt> and this map does not
* support <tt>null</tt> keys.
*/
public NavigableMap<K, V> headMap(K toKey);
/**
* Returns a view of the portion of this map whose keys are greater than or
* equal to <tt>fromKey</tt>. The returned sorted map is backed by this
* map, so changes in the returned sorted map are reflected in this map, and
* vice-versa.
*
* @param fromKey
* low endpoint (inclusive) of the tailMap.
* @return a view of the portion of this map whose keys are greater than or
* equal to <tt>fromKey</tt>.
* @throws ClassCastException
* if <tt>fromKey</tt> is not compatible with this map"s
* comparator (or, if the map has no comparator, if <tt>fromKey</tt>
* does not implement <tt>Comparable</tt>).
* @throws NullPointerException
* if <tt>fromKey</tt> is <tt>null</tt> and this map does not
* support <tt>null</tt> keys.
*/
public NavigableMap<K, V> tailMap(K fromKey);
}
/**
* A {@link ConcurrentMap} supporting {@link NavigableMap} operations.
*
* @author Doug Lea
* @param <K>
* the type of keys maintained by this map
* @param <V>
* the type of mapped values
*/
interface ConcurrentNavigableMap<K, V> extends ConcurrentMap<K, V>, NavigableMap<K, V> {
/**
* Returns a view of the portion of this map whose keys range from
* <tt>fromKey</tt>, inclusive, to <tt>toKey</tt>, exclusive. (If
* <tt>fromKey</tt> and <tt>toKey</tt> are equal, the returned sorted map
* is empty.) The returned sorted map is backed by this map, so changes in the
* returned sorted map are reflected in this map, and vice-versa.
*
* @param fromKey
* low endpoint (inclusive) of the subMap.
* @param toKey
* high endpoint (exclusive) of the subMap.
*
* @return a view of the portion of this map whose keys range from
* <tt>fromKey</tt>, inclusive, to <tt>toKey</tt>, exclusive.
*
* @throws ClassCastException
* if <tt>fromKey</tt> and <tt>toKey</tt> cannot be compared to
* one another using this map"s comparator (or, if the map has no
* comparator, using natural ordering).
* @throws IllegalArgumentException
* if <tt>fromKey</tt> is greater than <tt>toKey</tt>.
* @throws NullPointerException
* if <tt>fromKey</tt> or <tt>toKey</tt> is <tt>null</tt> and
* this map does not support <tt>null</tt> keys.
*/
public ConcurrentNavigableMap<K, V> subMap(K fromKey, K toKey);
/**
* Returns a view of the portion of this map whose keys are strictly less than
* <tt>toKey</tt>. The returned sorted map is backed by this map, so
* changes in the returned sorted map are reflected in this map, and
* vice-versa.
*
* @param toKey
* high endpoint (exclusive) of the headMap.
* @return a view of the portion of this map whose keys are strictly less than
* <tt>toKey</tt>.
*
* @throws ClassCastException
* if <tt>toKey</tt> is not compatible with this map"s comparator
* (or, if the map has no comparator, if <tt>toKey</tt> does not
* implement <tt>Comparable</tt>).
* @throws NullPointerException
* if <tt>toKey</tt> is <tt>null</tt> and this map does not
* support <tt>null</tt> keys.
*/
public ConcurrentNavigableMap<K, V> headMap(K toKey);
/**
* Returns a view of the portion of this map whose keys are greater than or
* equal to <tt>fromKey</tt>. The returned sorted map is backed by this
* map, so changes in the returned sorted map are reflected in this map, and
* vice-versa.
*
* @param fromKey
* low endpoint (inclusive) of the tailMap.
* @return a view of the portion of this map whose keys are greater than or
* equal to <tt>fromKey</tt>.
* @throws ClassCastException
* if <tt>fromKey</tt> is not compatible with this map"s
* comparator (or, if the map has no comparator, if <tt>fromKey</tt>
* does not implement <tt>Comparable</tt>).
* @throws NullPointerException
* if <tt>fromKey</tt> is <tt>null</tt> and this map does not
* support <tt>null</tt> keys.
*/
public ConcurrentNavigableMap<K, V> tailMap(K fromKey);
}
/**
* A scalable {@link ConcurrentNavigableMap} implementation. This class
* maintains a map in ascending key order, sorted according to the <i>natural
* order</i> for the key"s class (see {@link Comparable}), or by the
* {@link Comparator} provided at creation time, depending on which constructor
* is used.
*
* <p>
* This class implements a concurrent variant of providing expected average
* <i>log(n)</i> time cost for the <tt>containsKey</tt>, <tt>get</tt>,
* <tt>put</tt> and <tt>remove</tt> operations and their variants.
* Insertion, removal, update, and access operations safely execute concurrently
* by multiple threads. Iterators are <i>weakly consistent</i>, returning
* elements reflecting the state of the map at some point at or since the
* creation of the iterator. They do <em>not</em> throw {@link
* ConcurrentModificationException}, and may proceed concurrently with other
* operations. Ascending key ordered views and their iterators are faster than
* descending ones.
*
* <p>
* All <tt>Map.Entry</tt> pairs returned by methods in this class and its
* views represent snapshots of mappings at the time they were produced. They do
* <em>not</em> support the <tt>Entry.setValue</tt> method. (Note however
* that it is possible to change mappings in the associated map using
* <tt>put</tt>, <tt>putIfAbsent</tt>, or <tt>replace</tt>, depending
* on exactly which effect you need.)
*
* <p>
* Beware that, unlike in most collections, the <tt>size</tt> method is
* <em>not</em> a constant-time operation. Because of the asynchronous nature
* of these maps, determining the current number of elements requires a
* traversal of the elements. Additionally, the bulk operations <tt>putAll</tt>,
* <tt>equals</tt>, and <tt>clear</tt> are <em>not</em> guaranteed to be
* performed atomically. For example, an iterator operating concurrently with a
* <tt>putAll</tt> operation might view only some of the added elements.
*
* <p>
* This class and its views and iterators implement all of the <em>optional</em>
* methods of the {@link Map} and {@link Iterator} interfaces. Like most other
* concurrent collections, this class does not permit the use of <tt>null</tt>
* keys or values because some null return values cannot be reliably
* distinguished from the absence of elements.
*
* @author Doug Lea
* @param <K>
* the type of keys maintained by this map
* @param <V>
* the type of mapped values
*/
public class ConcurrentSkipListMap<K, V> extends AbstractMap<K, V> implements
ConcurrentNavigableMap<K, V>, Cloneable, java.io.Serializable {
/*
* This class implements a tree-like two-dimensionally linked skip list in
* which the index levels are represented in separate nodes from the base
* nodes holding data. There are two reasons for taking this approach instead
* of the usual array-based structure: 1) Array based implementations seem to
* encounter more complexity and overhead 2) We can use cheaper algorithms for
* the heavily-traversed index lists than can be used for the base lists.
* Here"s a picture of some of the basics for a possible list with 2 levels of
* index:
*
* Head nodes Index nodes +-+ right +-+ +-+ |2|---------------->|
* |--------------------->| |->null +-+ +-+ +-+ | down | | v v v +-+ +-+ +-+
* +-+ +-+ +-+ |1|----------->| |->| |------>| |----------->| |------>|
* |->null +-+ +-+ +-+ +-+ +-+ +-+ v | | | | | Nodes next v v v v v +-+ +-+
* +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ |
* |->|A|->|B|->|C|->|D|->|E|->|F|->|G|->|H|->|I|->|J|->|K|->null +-+ +-+ +-+
* +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+
*
* The base lists use a variant of the HM linked ordered set algorithm. See
* Tim Harris, "A pragmatic implementation of non-blocking linked lists"
* http://www.cl.cam.ac.uk/~tlh20/publications.html and Maged Michael "High
* Performance Dynamic Lock-Free Hash Tables and List-Based Sets"
* http://www.research.ibm.ru/people/m/michael/pubs.htm. The basic idea in
* these lists is to mark the "next" pointers of deleted nodes when deleting
* to avoid conflicts with concurrent insertions, and when traversing to keep
* track of triples (predecessor, node, successor) in order to detect when and
* how to unlink these deleted nodes.
*
* Rather than using mark-bits to mark list deletions (which can be slow and
* space-intensive using AtomicMarkedReference), nodes use direct CAS"able
* next pointers. On deletion, instead of marking a pointer, they splice in
* another node that can be thought of as standing for a marked pointer
* (indicating this by using otherwise impossible field values). Using plain
* nodes acts roughly like "boxed" implementations of marked pointers, but
* uses new nodes only when nodes are deleted, not for every link. This
* requires less space and supports faster traversal. Even if marked
* references were better supported by JVMs, traversal using this technique
* might still be faster because any search need only read ahead one more node
* than otherwise required (to check for trailing marker) rather than
* unmasking mark bits or whatever on each read.
*
* This approach maintains the essential property needed in the HM algorithm
* of changing the next-pointer of a deleted node so that any other CAS of it
* will fail, but implements the idea by changing the pointer to point to a
* different node, not by marking it. While it would be possible to further
* squeeze space by defining marker nodes not to have key/value fields, it
* isn"t worth the extra type-testing overhead. The deletion markers are
* rarely encountered during traversal and are normally quickly garbage
* collected. (Note that this technique would not work well in systems without
* garbage collection.)
*
* In addition to using deletion markers, the lists also use nullness of value
* fields to indicate deletion, in a style similar to typical lazy-deletion
* schemes. If a node"s value is null, then it is considered logically deleted
* and ignored even though it is still reachable. This maintains proper
* control of concurrent replace vs delete operations -- an attempted replace
* must fail if a delete beat it by nulling field, and a delete must return
* the last non-null value held in the field. (Note: Null, rather than some
* special marker, is used for value fields here because it just so happens to
* mesh with the Map API requirement that method get returns null if there is
* no mapping, which allows nodes to remain concurrently readable even when
* deleted. Using any other marker value here would be messy at best.)
*
* Here"s the sequence of events for a deletion of node n with predecessor b
* and successor f, initially:
*
* +------+ +------+ +------+ ... | b |------>| n |----->| f | ... +------+
* +------+ +------+
*
* 1. CAS n"s value field from non-null to null. From this point on, no public
* operations encountering the node consider this mapping to exist. However,
* other ongoing insertions and deletions might still modify n"s next pointer.
*
* 2. CAS n"s next pointer to point to a new marker node. From this point on,
* no other nodes can be appended to n. which avoids deletion errors in
* CAS-based linked lists.
*
* +------+ +------+ +------+ +------+ ... | b |------>| n
* |----->|marker|------>| f | ... +------+ +------+ +------+ +------+
*
* 3. CAS b"s next pointer over both n and its marker. From this point on, no
* new traversals will encounter n, and it can eventually be GCed. +------+
* +------+ ... | b |----------------------------------->| f | ... +------+
* +------+
*
* A failure at step 1 leads to simple retry due to a lost race with another
* operation. Steps 2-3 can fail because some other thread noticed during a
* traversal a node with null value and helped out by marking and/or
* unlinking. This helping-out ensures that no thread can become stuck waiting
* for progress of the deleting thread. The use of marker nodes slightly
* complicates helping-out code because traversals must track consistent reads
* of up to four nodes (b, n, marker, f), not just (b, n, f), although the
* next field of a marker is immutable, and once a next field is CAS"ed to
* point to a marker, it never again changes, so this requires less care.
*
* Skip lists add indexing to this scheme, so that the base-level traversals
* start close to the locations being found, inserted or deleted -- usually
* base level traversals only traverse a few nodes. This doesn"t change the
* basic algorithm except for the need to make sure base traversals start at
* predecessors (here, b) that are not (structurally) deleted, otherwise
* retrying after processing the deletion.
*
* Index levels are maintained as lists with volatile next fields, using CAS
* to link and unlink. Races are allowed in index-list operations that can
* (rarely) fail to link in a new index node or delete one. (We can"t do this
* of course for data nodes.) However, even when this happens, the index lists
* remain sorted, so correctly serve as indices. This can impact performance,
* but since skip lists are probabilistic anyway, the net result is that under
* contention, the effective "p" value may be lower than its nominal value.
* And race windows are kept small enough that in practice these failures are
* rare, even under a lot of contention.
*
* The fact that retries (for both base and index lists) are relatively cheap
* due to indexing allows some minor simplifications of retry logic. Traversal
* restarts are performed after most "helping-out" CASes. This isn"t always
* strictly necessary, but the implicit backoffs tend to help reduce other
* downstream failed CAS"s enough to outweigh restart cost. This worsens the
* worst case, but seems to improve even highly contended cases.
*
* Unlike most skip-list implementations, index insertion and deletion here
* require a separate traversal pass occuring after the base-level action, to
* add or remove index nodes. This adds to single-threaded overhead, but
* improves contended multithreaded performance by narrowing interference
* windows, and allows deletion to ensure that all index nodes will be made
* unreachable upon return from a public remove operation, thus avoiding
* unwanted garbage retention. This is more important here than in some other
* data structures because we cannot null out node fields referencing user
* keys since they might still be read by other ongoing traversals.
*
* Indexing uses skip list parameters that maintain good search performance
* while using sparser-than-usual indices: The hardwired parameters k=1, p=0.5
* (see method randomLevel) mean that about one-quarter of the nodes have
* indices. Of those that do, half have one level, a quarter have two, and so
* on (see Pugh"s Skip List Cookbook, sec 3.4). The expected total space
* requirement for a map is slightly less than for the current implementation
* of java.util.TreeMap.
*
* Changing the level of the index (i.e, the height of the tree-like
* structure) also uses CAS. The head index has initial level/height of one.
* Creation of an index with height greater than the current level adds a
* level to the head index by CAS"ing on a new top-most head. To maintain good
* performance after a lot of removals, deletion methods heuristically try to
* reduce the height if the topmost levels appear to be empty. This may
* encounter races in which it possible (but rare) to reduce and "lose" a
* level just as it is about to contain an index (that will then never be
* encountered). This does no structural harm, and in practice appears to be a
* better option than allowing unrestrained growth of levels.
*
* The code for all this is more verbose than you"d like. Most operations
* entail locating an element (or position to insert an element). The code to
* do this can"t be nicely factored out because subsequent uses require a
* snapshot of predecessor and/or successor and/or value fields which can"t be
* returned all at once, at least not without creating yet another object to
* hold them -- creating such little objects is an especially bad idea for
* basic internal search operations because it adds to GC overhead. (This is
* one of the few times I"ve wished Java had macros.) Instead, some traversal
* code is interleaved within insertion and removal operations. The control
* logic to handle all the retry conditions is sometimes twisty. Most search
* is broken into 2 parts. findPredecessor() searches index nodes only,
* returning a base-level predecessor of the key. findNode() finishes out the
* base-level search. Even with this factoring, there is a fair amount of
* near-duplication of code to handle variants.
*
* For explanation of algorithms sharing at least a couple of features with
* this one, see Mikhail Fomitchev"s thesis
* (http://www.cs.yorku.ca/~mikhail/), Keir Fraser"s thesis
* (http://www.cl.cam.ac.uk/users/kaf24/), and Hakan Sundell"s thesis
* (http://www.cs.chalmers.se/~phs/).
*
* Given the use of tree-like index nodes, you might wonder why this doesn"t
* use some kind of search tree instead, which would support somewhat faster
* search operations. The reason is that there are no known efficient
* lock-free insertion and deletion algorithms for search trees. The
* immutability of the "down" links of index nodes (as opposed to mutable
* "left" fields in true trees) makes this tractable using only CAS
* operations.
*
* Notation guide for local variables Node: b, n, f for predecessor, node,
* successor Index: q, r, d for index node, right, down. t for another index
* node Head: h Levels: j Keys: k, key Values: v, value Comparisons: c
*/
private static final long serialVersionUID = -8627078645895051609L;
/**
* Special value used to identify base-level header
*/
private static final Object BASE_HEADER = new Object();
/**
* The topmost head index of the skiplist.
*/
private transient volatile HeadIndex<K, V> head;
/**
* The Comparator used to maintain order in this Map, or null if using natural
* order.
*
* @serial
*/
private final Comparator<? super K> comparator;
/**
* Seed for simple random number generator. Not volatile since it doesn"t
* matter too much if different threads don"t see updates.
*/
private transient int randomSeed;
/** Lazily initialized key set */
private transient KeySet keySet;
/** Lazily initialized entry set */
private transient EntrySet entrySet;
/** Lazily initialized values collection */
private transient Values values;
/** Lazily initialized descending key set */
private transient DescendingKeySet descendingKeySet;
/** Lazily initialized descending entry set */
private transient DescendingEntrySet descendingEntrySet;
/**
* Initialize or reset state. Needed by constructors, clone, clear,
* readObject. and ConcurrentSkipListSet.clone. (Note that comparator must be
* separately initialized.)
*/
final void initialize() {
keySet = null;
entrySet = null;
values = null;
descendingEntrySet = null;
descendingKeySet = null;
randomSeed = (int) System.nanoTime();
head = new HeadIndex<K, V>(new Node<K, V>(null, BASE_HEADER, null), null, null, 1);
}
/** Updater for casHead */
private static final AtomicReferenceFieldUpdater<ConcurrentSkipListMap, HeadIndex> headUpdater = AtomicReferenceFieldUpdater
.newUpdater(ConcurrentSkipListMap.class, HeadIndex.class, "head");
/**
* compareAndSet head node
*/
private boolean casHead(HeadIndex<K, V> cmp, HeadIndex<K, V> val) {
return headUpdater.rupareAndSet(this, cmp, val);
}
/* ---------------- Nodes -------------- */
/**
* Nodes hold keys and values, and are singly linked in sorted order, possibly
* with some intervening marker nodes. The list is headed by a dummy node
* accessible as head.node. The value field is declared only as Object because
* it takes special non-V values for marker and header nodes.
*/
static final class Node<K, V> {
final K key;
volatile Object value;
volatile Node<K, V> next;
/**
* Creates a new regular node.
*/
Node(K key, Object value, Node<K, V> next) {
this.key = key;
this.value = value;
this.next = next;
}
/**
* Creates a new marker node. A marker is distinguished by having its value
* field point to itself. Marker nodes also have null keys, a fact that is
* exploited in a few places, but this doesn"t distinguish markers from the
* base-level header node (head.node), which also has a null key.
*/
Node(Node<K, V> next) {
this.key = null;
this.value = this;
this.next = next;
}
/** Updater for casNext */
static final AtomicReferenceFieldUpdater<Node, Node> nextUpdater = AtomicReferenceFieldUpdater
.newUpdater(Node.class, Node.class, "next");
/** Updater for casValue */
static final AtomicReferenceFieldUpdater<Node, Object> valueUpdater = AtomicReferenceFieldUpdater
.newUpdater(Node.class, Object.class, "value");
/**
* compareAndSet value field
*/
boolean casValue(Object cmp, Object val) {
return valueUpdater.rupareAndSet(this, cmp, val);
}
/**
* compareAndSet next field
*/
boolean casNext(Node<K, V> cmp, Node<K, V> val) {
return nextUpdater.rupareAndSet(this, cmp, val);
}
/**
* Return true if this node is a marker. This method isn"t actually called
* in an any current code checking for markers because callers will have
* already read value field and need to use that read (not another done
* here) and so directly test if value points to node.
*
* @param n
* a possibly null reference to a node
* @return true if this node is a marker node
*/
boolean isMarker() {
return value == this;
}
/**
* Return true if this node is the header of base-level list.
*
* @return true if this node is header node
*/
boolean isBaseHeader() {
return value == BASE_HEADER;
}
/**
* Tries to append a deletion marker to this node.
*
* @param f
* the assumed current successor of this node
* @return true if successful
*/
boolean appendMarker(Node<K, V> f) {
return casNext(f, new Node<K, V>(f));
}
/**
* Helps out a deletion by appending marker or unlinking from predecessor.
* This is called during traversals when value field seen to be null.
*
* @param b
* predecessor
* @param f
* successor
*/
void helpDelete(Node<K, V> b, Node<K, V> f) {
/*
* Rechecking links and then doing only one of the help-out stages per
* call tends to minimize CAS interference among helping threads.
*/
if (f == next && this == b.next) {
if (f == null || f.value != f) // not already marked
appendMarker(f);
else
b.casNext(this, f.next);
}
}
/**
* Return value if this node contains a valid key-value pair, else null.
*
* @return this node"s value if it isn"t a marker or header or is deleted,
* else null.
*/
V getValidValue() {
Object v = value;
if (v == this || v == BASE_HEADER)
return null;
return (V) v;
}
/**
* Create and return a new SnapshotEntry holding current mapping if this
* node holds a valid value, else null
*
* @return new entry or null
*/
SnapshotEntry<K, V> createSnapshot() {
V v = getValidValue();
if (v == null)
return null;
return new SnapshotEntry(key, v);
}
}
/* ---------------- Indexing -------------- */
/**
* Index nodes represent the levels of the skip list. To improve search
* performance, keys of the underlying nodes are cached. Note that even though
* both Nodes and Indexes have forward-pointing fields, they have different
* types and are handled in different ways, that can"t nicely be captured by
* placing field in a shared abstract class.
*/
static class Index<K, V> {
final K key;
final Node<K, V> node;
final Index<K, V> down;
volatile Index<K, V> right;
/**
* Creates index node with given values
*/
Index(Node<K, V> node, Index<K, V> down, Index<K, V> right) {
this.node = node;
this.key = node.key;
this.down = down;
this.right = right;
}
/** Updater for casRight */
static final AtomicReferenceFieldUpdater<Index, Index> rightUpdater = AtomicReferenceFieldUpdater
.newUpdater(Index.class, Index.class, "right");
/**
* compareAndSet right field
*/
final boolean casRight(Index<K, V> cmp, Index<K, V> val) {
return rightUpdater.rupareAndSet(this, cmp, val);
}
/**
* Returns true if the node this indexes has been deleted.
*
* @return true if indexed node is known to be deleted
*/
final boolean indexesDeletedNode() {
return node.value == null;
}
/**
* Tries to CAS newSucc as successor. To minimize races with unlink that may
* lose this index node, if the node being indexed is known to be deleted,
* it doesn"t try to link in.
*
* @param succ
* the expected current successor
* @param newSucc
* the new successor
* @return true if successful
*/
final boolean link(Index<K, V> succ, Index<K, V> newSucc) {
Node<K, V> n = node;
newSucc.right = succ;
return n.value != null && casRight(succ, newSucc);
}
/**
* Tries to CAS right field to skip over apparent successor succ. Fails
* (forcing a retraversal by caller) if this node is known to be deleted.
*
* @param succ
* the expected current successor
* @return true if successful
*/
final boolean unlink(Index<K, V> succ) {
return !indexesDeletedNode() && casRight(succ, succ.right);
}
}
/* ---------------- Head nodes -------------- */
/**
* Nodes heading each level keep track of their level.
*/
static final class HeadIndex<K, V> extends Index<K, V> {
final int level;
HeadIndex(Node<K, V> node, Index<K, V> down, Index<K, V> right, int level) {
super(node, down, right);
this.level = level;
}
}
/* ---------------- Map.Entry support -------------- */
/**
* An immutable representation of a key-value mapping as it existed at some
* point in time. This class does <em>not</em> support the
* <tt>Map.Entry.setValue</tt> method.
*/
static class SnapshotEntry<K, V> implements Map.Entry<K, V> {
private final K key;
private final V value;
/**
* Creates a new entry representing the given key and value.
*
* @param key
* the key
* @param value
* the value
*/
SnapshotEntry(K key, V value) {
this.key = key;
this.value = value;
}
/**
* Returns the key corresponding to this entry.
*
* @return the key corresponding to this entry.
*/
public K getKey() {
return key;
}
/**
* Returns the value corresponding to this entry.
*
* @return the value corresponding to this entry.
*/
public V getValue() {
return value;
}
/**
* Always fails, throwing <tt>UnsupportedOperationException</tt>.
*
* @throws UnsupportedOperationException
* always.
*/
public V setValue(V value) {
throw new UnsupportedOperationException();
}
// inherit javadoc
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry) o;
// As mandated by Map.Entry spec:
return ((key == null ? e.getKey() == null : key.equals(e.getKey())) && (value == null ? e
.getValue() == null : value.equals(e.getValue())));
}
// inherit javadoc
public int hashCode() {
// As mandated by Map.Entry spec:
return ((key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode()));
}
/**
* Returns a String consisting of the key followed by an equals sign (<tt>"="</tt>)
* followed by the associated value.
*
* @return a String representation of this entry.
*/
public String toString() {
return getKey() + "=" + getValue();
}
}
/* ---------------- Comparison utilities -------------- */
/**
* Represents a key with a comparator as a Comparable.
*
* Because most sorted collections seem to use natural order on Comparables
* (Strings, Integers, etc), most internal methods are geared to use them.
* This is generally faster than checking per-comparison whether to use
* comparator or comparable because it doesn"t require a (Comparable) cast for
* each comparison. (Optimizers can only sometimes remove such redundant
* checks themselves.) When Comparators are used, ComparableUsingComparators
* are created so that they act in the same way as natural orderings. This
* penalizes use of Comparators vs Comparables, which seems like the right
* tradeoff.
*/
static final class ComparableUsingComparator<K> implements Comparable<K> {
final K actualKey;
final Comparator<? super K> cmp;
ComparableUsingComparator(K key, Comparator<? super K> cmp) {
this.actualKey = key;
this.cmp = cmp;
}
public int compareTo(K k2) {
return cmp.rupare(actualKey, k2);
}
}
/**
* If using comparator, return a ComparableUsingComparator, else cast key as
* Comparator, which may cause ClassCastException, which is propagated back to
* caller.
*/
private Comparable<K> comparable(Object key) throws ClassCastException {
if (key == null)
throw new NullPointerException();
return (comparator != null) ? new ComparableUsingComparator(key, comparator)
: (Comparable<K>) key;
}
/**
* Compare using comparator or natural ordering. Used when the
* ComparableUsingComparator approach doesn"t apply.
*/
int compare(K k1, K k2) throws ClassCastException {
Comparator<? super K> cmp = comparator;
if (cmp != null)
return cmp.rupare(k1, k2);
else
return ((Comparable<K>) k1).rupareTo(k2);
}
/**
* Return true if given key greater than or equal to least and strictly less
* than fence, bypassing either test if least or fence oare null. Needed
* mainly in submap operations.
*/
boolean inHalfOpenRange(K key, K least, K fence) {
if (key == null)
throw new NullPointerException();
return ((least == null || compare(key, least) >= 0) && (fence == null || compare(key, fence) < 0));
}
/**
* Return true if given key greater than or equal to least and less or equal
* to fence. Needed mainly in submap operations.
*/
boolean inOpenRange(K key, K least, K fence) {
if (key == null)
throw new NullPointerException();
return ((least == null || compare(key, least) >= 0) && (fence == null || compare(key, fence) <= 0));
}
/* ---------------- Traversal -------------- */
/**
* Return a base-level node with key strictly less than given key, or the
* base-level header if there is no such node. Also unlinks indexes to deleted
* nodes found along the way. Callers rely on this side-effect of clearing
* indices to deleted nodes.
*
* @param key
* the key
* @return a predecessor of key
*/
private Node<K, V> findPredecessor(Comparable<K> key) {
for (;;) {
Index<K, V> q = head;
for (;;) {
Index<K, V> d, r;
if ((r = q.right) != null) {
if (r.indexesDeletedNode()) {
if (q.unlink(r))
continue; // reread r
else
break; // restart
}
if (key.rupareTo(r.key) > 0) {
q = r;
continue;
}
}
if ((d = q.down) != null)
q = d;
else
return q.node;
}
}
}
/**
* Return node holding key or null if no such, clearing out any deleted nodes
* seen along the way. Repeatedly traverses at base-level looking for key
* starting at predecessor returned from findPredecessor, processing
* base-level deletions as encountered. Some callers rely on this side-effect
* of clearing deleted nodes.
*
* Restarts occur, at traversal step centered on node n, if:
*
* (1) After reading n"s next field, n is no longer assumed predecessor b"s
* current successor, which means that we don"t have a consistent 3-node
* snapshot and so cannot unlink any subsequent deleted nodes encountered.
*
* (2) n"s value field is null, indicating n is deleted, in which case we help
* out an ongoing structural deletion before retrying. Even though there are
* cases where such unlinking doesn"t require restart, they aren"t sorted out
* here because doing so would not usually outweigh cost of restarting.
*
* (3) n is a marker or n"s predecessor"s value field is null, indicating
* (among other possibilities) that findPredecessor returned a deleted node.
* We can"t unlink the node because we don"t know its predecessor, so rely on
* another call to findPredecessor to notice and return some earlier
* predecessor, which it will do. This check is only strictly needed at
* beginning of loop, (and the b.value check isn"t strictly needed at all) but
* is done each iteration to help avoid contention with other threads by
* callers that will fail to be able to change links, and so will retry
* anyway.
*
* The traversal loops in doPut, doRemove, and findNear all include the same
* three kinds of checks. And specialized versions appear in doRemoveFirst,
* doRemoveLast, findFirst, and findLast. They can"t easily share code because
* each uses the reads of fields held in locals occurring in the orders they
* were performed.
*
* @param key
* the key
* @return node holding key, or null if no such.
*/
private Node<K, V> findNode(Comparable<K> key) {
for (;;) {
Node<K, V> b = findPredecessor(key);
Node<K, V> n = b.next;
for (;;) {
if (n == null)
return null;
Node<K, V> f = n.next;
if (n != b.next) // inconsistent read
break;
Object v = n.value;
if (v == null) { // n is deleted
n.helpDelete(b, f);
break;
}
if (v == n || b.value == null) // b is deleted
break;
int c = key.rupareTo(n.key);
if (c < 0)
return null;
if (c == 0)
return n;
b = n;
n = f;
}
}
}
/**
* Specialized variant of findNode to perform Map.get. Does a weak traversal,
* not bothering to fix any deleted index nodes, returning early if it happens
* to see key in index, and passing over any deleted base nodes, falling back
* to getUsingFindNode only if it would otherwise return value from an ongoing
* deletion. Also uses "bound" to eliminate need for some comparisons (see
* Pugh Cookbook). Also folds uses of null checks and node-skipping because
* markers have null keys.
*
* @param okey
* the key
* @return the value, or null if absent
*/
private V doGet(Object okey) {
Comparable<K> key = comparable(okey);
K bound = null;
Index<K, V> q = head;
for (;;) {
K rk;
Index<K, V> d, r;
if ((r = q.right) != null && (rk = r.key) != null && rk != bound) {
int c = key.rupareTo(rk);
if (c > 0) {
q = r;
continue;
}
if (c == 0) {
Object v = r.node.value;
return (v != null) ? (V) v : getUsingFindNode(key);
}
bound = rk;
}
if ((d = q.down) != null)
q = d;
else {
for (Node<K, V> n = q.node.next; n != null; n = n.next) {
K nk = n.key;
if (nk != null) {
int c = key.rupareTo(nk);
if (c == 0) {
Object v = n.value;
return (v != null) ? (V) v : getUsingFindNode(key);
}
if (c < 0)
return null;
}
}
return null;
}
}
}
/**
* Perform map.get via findNode. Used as a backup if doGet encounters an
* in-progress deletion.
*
* @param key
* the key
* @return the value, or null if absent
*/
private V getUsingFindNode(Comparable<K> key) {
/*
* Loop needed here and elsewhere in case value field goes null just as it
* is about to be returned, in which case we lost a race with a deletion, so
* must retry.
*/
for (;;) {
Node<K, V> n = findNode(key);
if (n == null)
return null;
Object v = n.value;
if (v != null)
return (V) v;
}
}
/* ---------------- Insertion -------------- */
/**
* Main insertion method. Adds element if not present, or replaces value if
* present and onlyIfAbsent is false.
*
* @param kkey
* the key
* @param value
* the value that must be associated with key
* @param onlyIfAbsent
* if should not insert if already present
* @return the old value, or null if newly inserted
*/
private V doPut(K kkey, V value, boolean onlyIfAbsent) {
Comparable<K> key = comparable(kkey);
for (;;) {
Node<K, V> b = findPredecessor(key);
Node<K, V> n = b.next;
for (;;) {
if (n != null) {
Node<K, V> f = n.next;
if (n != b.next) // inconsistent read
break;
;
Object v = n.value;
if (v == null) { // n is deleted
n.helpDelete(b, f);
break;
}
if (v == n || b.value == null) // b is deleted
break;
int c = key.rupareTo(n.key);
if (c > 0) {
b = n;
n = f;
continue;
}
if (c == 0) {
if (onlyIfAbsent || n.casValue(v, value))
return (V) v;
else
break; // restart if lost race to replace value
}
// else c < 0; fall through
}
Node<K, V> z = new Node<K, V>(kkey, value, n);
if (!b.casNext(n, z))
break; // restart if lost race to append to b
int level = randomLevel();
if (level > 0)
insertIndex(z, level);
return null;
}
}
}
/**
* Return a random level for inserting a new node. Hardwired to k=1, p=0.5,
* max 31.
*
* This uses a cheap pseudo-random function that according to
* http://home1.gte.net/deleyd/random/random4.html was used in Turbo Pascal.
* It seems the fastest usable one here. The low bits are apparently not very
* random (the original used only upper 16 bits) so we traverse from highest
* bit down (i.e., test sign), thus hardly ever use lower bits.
*/
private int randomLevel() {
int level = 0;
int r = randomSeed;
randomSeed = r * 134775813 + 1;
if (r < 0) {
while ((r <<= 1) > 0)
++level;
}
return level;
}
/**
* Create and add index nodes for given node.
*
* @param z
* the node
* @param level
* the level of the index
*/
private void insertIndex(Node<K, V> z, int level) {
HeadIndex<K, V> h = head;
int max = h.level;
if (level <= max) {
Index<K, V> idx = null;
for (int i = 1; i <= level; ++i)
idx = new Index<K, V>(z, idx, null);
addIndex(idx, h, level);
} else { // Add a new level
/*
* To reduce interference by other threads checking for empty levels in
* tryReduceLevel, new levels are added with initialized right pointers.
* Which in turn requires keeping levels in an array to access them while
* creating new head index nodes from the opposite direction.
*/
level = max + 1;
Index<K, V>[] idxs = (Index<K, V>[]) new Index[level + 1];
Index<K, V> idx = null;
for (int i = 1; i <= level; ++i)
idxs[i] = idx = new Index<K, V>(z, idx, null);
HeadIndex<K, V> oldh;
int k;
for (;;) {
oldh = head;
int oldLevel = oldh.level;
if (level <= oldLevel) { // lost race to add level
k = level;
break;
}
HeadIndex<K, V> newh = oldh;
Node<K, V> oldbase = oldh.node;
for (int j = oldLevel + 1; j <= level; ++j)
newh = new HeadIndex<K, V>(oldbase, newh, idxs[j], j);
if (casHead(oldh, newh)) {
k = oldLevel;
break;
}
}
addIndex(idxs[k], oldh, k);
}
}
/**
* Add given index nodes from given level down to 1.
*
* @param idx
* the topmost index node being inserted
* @param h
* the value of head to use to insert. This must be snapshotted by
* callers to provide correct insertion level
* @param indexLevel
* the level of the index
*/
private void addIndex(Index<K, V> idx, HeadIndex<K, V> h, int indexLevel) {
// Track next level to insert in case of retries
int insertionLevel = indexLevel;
Comparable<K> key = comparable(idx.key);
// Similar to findPredecessor, but adding index nodes along
// path to key.
for (;;) {
Index<K, V> q = h;
Index<K, V> t = idx;
int j = h.level;
for (;;) {
Index<K, V> r = q.right;
if (r != null) {
// compare before deletion check avoids needing recheck
int c = key.rupareTo(r.key);
if (r.indexesDeletedNode()) {
if (q.unlink(r))
continue;
else
break;
}
if (c > 0) {
q = r;
continue;
}
}
if (j == insertionLevel) {
// Don"t insert index if node already deleted
if (t.indexesDeletedNode()) {
findNode(key); // cleans up
return;
}
if (!q.link(r, t))
break; // restart
if (--insertionLevel == 0) {
// need final deletion check before return
if (t.indexesDeletedNode())
findNode(key);
return;
}
}
if (j > insertionLevel && j <= indexLevel)
t = t.down;
q = q.down;
--j;
}
}
}
/* ---------------- Deletion -------------- */
/**
* Main deletion method. Locates node, nulls value, appends a deletion marker,
* unlinks predecessor, removes associated index nodes, and possibly reduces
* head index level.
*
* Index nodes are cleared out simply by calling findPredecessor. which
* unlinks indexes to deleted nodes found along path to key, which will
* include the indexes to this node. This is done unconditionally. We can"t
* check beforehand whether there are index nodes because it might be the case
* that some or all indexes hadn"t been inserted yet for this node during
* initial search for it, and we"d like to ensure lack of garbage retention,
* so must call to be sure.
*
* @param okey
* the key
* @param value
* if non-null, the value that must be associated with key
* @return the node, or null if not found
*/
private V doRemove(Object okey, Object value) {
Comparable<K> key = comparable(okey);
for (;;) {
Node<K, V> b = findPredecessor(key);
Node<K, V> n = b.next;
for (;;) {
if (n == null)
return null;
Node<K, V> f = n.next;
if (n != b.next) // inconsistent read
break;
Object v = n.value;
if (v == null) { // n is deleted
n.helpDelete(b, f);
break;
}
if (v == n || b.value == null) // b is deleted
break;
int c = key.rupareTo(n.key);
if (c < 0)
return null;
if (c > 0) {
b = n;
n = f;
continue;
}
if (value != null && !value.equals(v))
return null;
if (!n.casValue(v, null))
break;
if (!n.appendMarker(f) || !b.casNext(n, f))
findNode(key); // Retry via findNode
else {
findPredecessor(key); // Clean index
if (head.right == null)
tryReduceLevel();
}
return (V) v;
}
}
}
/**
* Possibly reduce head level if it has no nodes. This method can (rarely)
* make mistakes, in which case levels can disappear even though they are
* about to contain index nodes. This impacts performance, not correctness. To
* minimize mistakes as well as to reduce hysteresis, the level is reduced by
* one only if the topmost three levels look empty. Also, if the removed level
* looks non-empty after CAS, we try to change it back quick before anyone
* notices our mistake! (This trick works pretty well because this method will
* practically never make mistakes unless current thread stalls immediately
* before first CAS, in which case it is very unlikely to stall again
* immediately afterwards, so will recover.)
*
* We put up with all this rather than just let levels grow because otherwise,
* even a small map that has undergone a large number of insertions and
* removals will have a lot of levels, slowing down access more than would an
* occasional unwanted reduction.
*/
private void tryReduceLevel() {
HeadIndex<K, V> h = head;
HeadIndex<K, V> d;
HeadIndex<K, V> e;
if (h.level > 3 && (d = (HeadIndex<K, V>) h.down) != null
&& (e = (HeadIndex<K, V>) d.down) != null && e.right == null && d.right == null
&& h.right == null && casHead(h, d) && // try to set
h.right != null) // recheck
casHead(d, h); // try to backout
}
/**
* Version of remove with boolean return. Needed by view classes
*/
boolean removep(Object key) {
return doRemove(key, null) != null;
}
/* ---------------- Finding and removing first element -------------- */
/**
* Specialized variant of findNode to get first valid node
*
* @return first node or null if empty
*/
Node<K, V> findFirst() {
for (;;) {
Node<K, V> b = head.node;
Node<K, V> n = b.next;
if (n == null)
return null;
if (n.value != null)
return n;
n.helpDelete(b, n.next);
}
}
/**
* Remove first entry; return either its key or a snapshot.
*
* @param keyOnly
* if true return key, else return SnapshotEntry (This is a little
* ugly, but avoids code duplication.)
* @return null if empty, first key if keyOnly true, else key,value entry
*/
Object doRemoveFirst(boolean keyOnly) {
for (;;) {
Node<K, V> b = head.node;
Node<K, V> n = b.next;
if (n == null)
return null;
Node<K, V> f = n.next;
if (n != b.next)
continue;
Object v = n.value;
if (v == null) {
n.helpDelete(b, f);
continue;
}
if (!n.casValue(v, null))
continue;
if (!n.appendMarker(f) || !b.casNext(n, f))
findFirst(); // retry
clearIndexToFirst();
K key = n.key;
return (keyOnly) ? key : new SnapshotEntry<K, V>(key, (V) v);
}
}
/**
* Clear out index nodes associated with deleted first entry. Needed by
* doRemoveFirst
*/
private void clearIndexToFirst() {
for (;;) {
Index<K, V> q = head;
for (;;) {
Index<K, V> r = q.right;
if (r != null && r.indexesDeletedNode() && !q.unlink(r))
break;
if ((q = q.down) == null) {
if (head.right == null)
tryReduceLevel();
return;
}
}
}
}
/**
* Remove first entry; return key or null if empty.
*/
K pollFirstKey() {
return (K) doRemoveFirst(true);
}
/* ---------------- Finding and removing last element -------------- */
/**
* Specialized version of find to get last valid node
*
* @return last node or null if empty
*/
Node<K, V> findLast() {
/*
* findPredecessor can"t be used to traverse index level because this
* doesn"t use comparisons. So traversals of both levels are folded
* together.
*/
Index<K, V> q = head;
for (;;) {
Index<K, V> d, r;
if ((r = q.right) != null) {
if (r.indexesDeletedNode()) {
q.unlink(r);
q = head; // restart
} else
q = r;
} else if ((d = q.down) != null) {
q = d;
} else {
Node<K, V> b = q.node;
Node<K, V> n = b.next;
for (;;) {
if (n == null)
return (b.isBaseHeader()) ? null : b;
Node<K, V> f = n.next; // inconsistent read
if (n != b.next)
break;
Object v = n.value;
if (v == null) { // n is deleted
n.helpDelete(b, f);
break;
}
if (v == n || b.value == null) // b is deleted
break;
b = n;
n = f;
}
q = head; // restart
}
}
}
/**
* Specialized version of doRemove for last entry.
*
* @param keyOnly
* if true return key, else return SnapshotEntry
* @return null if empty, last key if keyOnly true, else key,value entry
*/
Object doRemoveLast(boolean keyOnly) {
for (;;) {
Node<K, V> b = findPredecessorOfLast();
Node<K, V> n = b.next;
if (n == null) {
if (b.isBaseHeader()) // empty
return null;
else
continue; // all b"s successors are deleted; retry
}
for (;;) {
Node<K, V> f = n.next;
if (n != b.next) // inconsistent read
break;
Object v = n.value;
if (v == null) { // n is deleted
n.helpDelete(b, f);
break;
}
if (v == n || b.value == null) // b is deleted
break;
if (f != null) {
b = n;
n = f;
continue;
}
if (!n.casValue(v, null))
break;
K key = n.key;
Comparable<K> ck = comparable(key);
if (!n.appendMarker(f) || !b.casNext(n, f))
findNode(ck); // Retry via findNode
else {
findPredecessor(ck); // Clean index
if (head.right == null)
tryReduceLevel();
}
return (keyOnly) ? key : new SnapshotEntry<K, V>(key, (V) v);
}
}
}
/**
* Specialized variant of findPredecessor to get predecessor of last valid
* node. Needed by doRemoveLast. It is possible that all successors of
* returned node will have been deleted upon return, in which case this method
* can be retried.
*
* @return likely predecessor of last node.
*/
private Node<K, V> findPredecessorOfLast() {
for (;;) {
Index<K, V> q = head;
for (;;) {
Index<K, V> d, r;
if ((r = q.right) != null) {
if (r.indexesDeletedNode()) {
q.unlink(r);
break; // must restart
}
// proceed as far across as possible without overshooting
if (r.node.next != null) {
q = r;
continue;
}
}
if ((d = q.down) != null)
q = d;
else
return q.node;
}
}
}
/**
* Remove last entry; return key or null if empty.
*/
K pollLastKey() {
return (K) doRemoveLast(true);
}
/* ---------------- Relational operations -------------- */
// Control values OR"ed as arguments to findNear
private static final int EQ = 1;
private static final int LT = 2;
private static final int GT = 0; // Actually checked as !LT
/**
* Utility for ceiling, floor, lower, higher methods.
*
* @param kkey
* the key
* @param rel
* the relation -- OR"ed combination of EQ, LT, GT
* @return nearest node fitting relation, or null if no such
*/
Node<K, V> findNear(K kkey, int rel) {
Comparable<K> key = comparable(kkey);
for (;;) {
Node<K, V> b = findPredecessor(key);
Node<K, V> n = b.next;
for (;;) {
if (n == null)
return ((rel & LT) == 0 || b.isBaseHeader()) ? null : b;
Node<K, V> f = n.next;
if (n != b.next) // inconsistent read
break;
Object v = n.value;
if (v == null) { // n is deleted
n.helpDelete(b, f);
break;
}
if (v == n || b.value == null) // b is deleted
break;
int c = key.rupareTo(n.key);
if ((c == 0 && (rel & EQ) != 0) || (c < 0 && (rel & LT) == 0))
return n;
if (c <= 0 && (rel & LT) != 0)
return (b.isBaseHeader()) ? null : b;
b = n;
n = f;
}
}
}
/**
* Return SnapshotEntry for results of findNear.
*
* @param kkey
* the key
* @param rel
* the relation -- OR"ed combination of EQ, LT, GT
* @return Entry fitting relation, or null if no such
*/
SnapshotEntry<K, V> getNear(K kkey, int rel) {
for (;;) {
Node<K, V> n = findNear(kkey, rel);
if (n == null)
return null;
SnapshotEntry<K, V> e = n.createSnapshot();
if (e != null)
return e;
}
}
/**
* Return ceiling, or first node if key is <tt>null</tt>
*/
Node<K, V> findCeiling(K key) {
return (key == null) ? findFirst() : findNear(key, GT | EQ);
}
/**
* Return lower node, or last node if key is <tt>null</tt>
*/
Node<K, V> findLower(K key) {
return (key == null) ? findLast() : findNear(key, LT);
}
/**
* Return SnapshotEntry or key for results of findNear ofter screening to
* ensure result is in given range. Needed by submaps.
*
* @param kkey
* the key
* @param rel
* the relation -- OR"ed combination of EQ, LT, GT
* @param least
* minimum allowed key value
* @param fence
* key greater than maximum allowed key value
* @param keyOnly
* if true return key, else return SnapshotEntry
* @return Key or Entry fitting relation, or <tt>null</tt> if no such
*/
Object getNear(K kkey, int rel, K least, K fence, boolean keyOnly) {
K key = kkey;
// Don"t return keys less than least
if ((rel & LT) == 0) {
if (compare(key, least) < 0) {
key = least;
rel = rel | EQ;
}
}
for (;;) {
Node<K, V> n = findNear(key, rel);
if (n == null || !inHalfOpenRange(n.key, least, fence))
return null;
K k = n.key;
V v = n.getValidValue();
if (v != null)
return keyOnly ? k : new SnapshotEntry<K, V>(k, v);
}
}
/**
* Find and remove least element of subrange.
*
* @param least
* minimum allowed key value
* @param fence
* key greater than maximum allowed key value
* @param keyOnly
* if true return key, else return SnapshotEntry
* @return least Key or Entry, or <tt>null</tt> if no such
*/
Object removeFirstEntryOfSubrange(K least, K fence, boolean keyOnly) {
for (;;) {
Node<K, V> n = findCeiling(least);
if (n == null)
return null;
K k = n.key;
if (fence != null && compare(k, fence) >= 0)
return null;
V v = doRemove(k, null);
if (v != null)
return (keyOnly) ? k : new SnapshotEntry<K, V>(k, v);
}
}
/**
* Find and remove greatest element of subrange.
*
* @param least
* minimum allowed key value
* @param fence
* key greater than maximum allowed key value
* @param keyOnly
* if true return key, else return SnapshotEntry
* @return least Key or Entry, or <tt>null</tt> if no such
*/
Object removeLastEntryOfSubrange(K least, K fence, boolean keyOnly) {
for (;;) {
Node<K, V> n = findLower(fence);
if (n == null)
return null;
K k = n.key;
if (least != null && compare(k, least) < 0)
return null;
V v = doRemove(k, null);
if (v != null)
return (keyOnly) ? k : new SnapshotEntry<K, V>(k, v);
}
}
/* ---------------- Constructors -------------- */
/**
* Constructs a new empty map, sorted according to the keys" natural order.
*/
public ConcurrentSkipListMap() {
this.ruparator = null;
initialize();
}
/**
* Constructs a new empty map, sorted according to the given comparator.
*
* @param c
* the comparator that will be used to sort this map. A <tt>null</tt>
* value indicates that the keys" <i>natural ordering</i> should be
* used.
*/
public ConcurrentSkipListMap(Comparator<? super K> c) {
this.ruparator = c;
initialize();
}
/**
* Constructs a new map containing the same mappings as the given map, sorted
* according to the keys" <i>natural order</i>.
*
* @param m
* the map whose mappings are to be placed in this map.
* @throws ClassCastException
* if the keys in m are not Comparable, or are not mutually
* comparable.
* @throws NullPointerException
* if the specified map is <tt>null</tt>.
*/
public ConcurrentSkipListMap(Map<? extends K, ? extends V> m) {
this.ruparator = null;
initialize();
putAll(m);
}
/**
* Constructs a new map containing the same mappings as the given
* <tt>SortedMap</tt>, sorted according to the same ordering.
*
* @param m
* the sorted map whose mappings are to be placed in this map, and
* whose comparator is to be used to sort this map.
* @throws NullPointerException
* if the specified sorted map is <tt>null</tt>.
*/
public ConcurrentSkipListMap(SortedMap<K, ? extends V> m) {
this.ruparator = m.ruparator();
initialize();
buildFromSorted(m);
}
/**
* Returns a shallow copy of this <tt>Map</tt> instance. (The keys and
* values themselves are not cloned.)
*
* @return a shallow copy of this Map.
*/
public Object clone() {
ConcurrentSkipListMap<K, V> clone = null;
try {
clone = (ConcurrentSkipListMap<K, V>) super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
clone.initialize();
clone.buildFromSorted(this);
return clone;
}
/**
* Streamlined bulk insertion to initialize from elements of given sorted map.
* Call only from constructor or clone method.
*/
private void buildFromSorted(SortedMap<K, ? extends V> map) {
if (map == null)
throw new NullPointerException();
HeadIndex<K, V> h = head;
Node<K, V> basepred = h.node;
// Track the current rightmost node at each level. Uses an
// ArrayList to avoid committing to initial or maximum level.
ArrayList<Index<K, V>> preds = new ArrayList<Index<K, V>>();
// initialize
for (int i = 0; i <= h.level; ++i)
preds.add(null);
Index<K, V> q = h;
for (int i = h.level; i > 0; --i) {
preds.set(i, q);
q = q.down;
}
Iterator<? extends Map.Entry<? extends K, ? extends V>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<? extends K, ? extends V> e = it.next();
int j = randomLevel();
if (j > h.level)
j = h.level + 1;
K k = e.getKey();
V v = e.getValue();
if (k == null || v == null)
throw new NullPointerException();
Node<K, V> z = new Node<K, V>(k, v, null);
basepred.next = z;
basepred = z;
if (j > 0) {
Index<K, V> idx = null;
for (int i = 1; i <= j; ++i) {
idx = new Index<K, V>(z, idx, null);
if (i > h.level)
h = new HeadIndex<K, V>(h.node, h, idx, i);
if (i < preds.size()) {
preds.get(i).right = idx;
preds.set(i, idx);
} else
preds.add(idx);
}
}
}
head = h;
}
/* ---------------- Serialization -------------- */
/**
* Save the state of the <tt>Map</tt> instance to a stream.
*
* @serialData The key (Object) and value (Object) for each key-value mapping
* represented by the Map, followed by <tt>null</tt>. The
* key-value mappings are emitted in key-order (as determined by
* the Comparator, or by the keys" natural ordering if no
* Comparator).
*/
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
// Write out the Comparator and any hidden stuff
s.defaultWriteObject();
// Write out keys and values (alternating)
for (Node<K, V> n = findFirst(); n != null; n = n.next) {
V v = n.getValidValue();
if (v != null) {
s.writeObject(n.key);
s.writeObject(v);
}
}
s.writeObject(null);
}
/**
* Reconstitute the <tt>Map</tt> instance from a stream.
*/
private void readObject(final java.io.ObjectInputStream s) throws java.io.IOException,
ClassNotFoundException {
// Read in the Comparator and any hidden stuff
s.defaultReadObject();
// Reset transients
initialize();
/*
* This is nearly identical to buildFromSorted, but is distinct because
* readObject calls can"t be nicely adapted as the kind of iterator needed
* by buildFromSorted. (They can be, but doing so requires type cheats
* and/or creation of adaptor classes.) It is simpler to just adapt the
* code.
*/
HeadIndex<K, V> h = head;
Node<K, V> basepred = h.node;
ArrayList<Index<K, V>> preds = new ArrayList<Index<K, V>>();
for (int i = 0; i <= h.level; ++i)
preds.add(null);
Index<K, V> q = h;
for (int i = h.level; i > 0; --i) {
preds.set(i, q);
q = q.down;
}
for (;;) {
Object k = s.readObject();
if (k == null)
break;
Object v = s.readObject();
if (v == null)
throw new NullPointerException();
K key = (K) k;
V val = (V) v;
int j = randomLevel();
if (j > h.level)
j = h.level + 1;
Node<K, V> z = new Node<K, V>(key, val, null);
basepred.next = z;
basepred = z;
if (j > 0) {
Index<K, V> idx = null;
for (int i = 1; i <= j; ++i) {
idx = new Index<K, V>(z, idx, null);
if (i > h.level)
h = new HeadIndex<K, V>(h.node, h, idx, i);
if (i < preds.size()) {
preds.get(i).right = idx;
preds.set(i, idx);
} else
preds.add(idx);
}
}
}
head = h;
}
/* ------ Map API methods ------ */
/**
* Returns <tt>true</tt> if this map contains a mapping for the specified
* key.
*
* @param key
* key whose presence in this map is to be tested.
* @return <tt>true</tt> if this map contains a mapping for the specified
* key.
* @throws ClassCastException
* if the key cannot be compared with the keys currently in the map.
* @throws NullPointerException
* if the key is <tt>null</tt>.
*/
public boolean containsKey(Object key) {
return doGet(key) != null;
}
/**
* Returns the value to which this map maps the specified key. Returns
* <tt>null</tt> if the map contains no mapping for this key.
*
* @param key
* key whose associated value is to be returned.
* @return the value to which this map maps the specified key, or
* <tt>null</tt> if the map contains no mapping for the key.
* @throws ClassCastException
* if the key cannot be compared with the keys currently in the map.
* @throws NullPointerException
* if the key is <tt>null</tt>.
*/
public V get(Object key) {
return doGet(key);
}
/**
* Associates the specified value with the specified key in this map. If the
* map previously contained a mapping for this key, the old value is replaced.
*
* @param key
* key with which the specified value is to be associated.
* @param value
* value to be associated with the specified key.
*
* @return previous value associated with specified key, or <tt>null</tt> if
* there was no mapping for key.
* @throws ClassCastException
* if the key cannot be compared with the keys currently in the map.
* @throws NullPointerException
* if the key or value are <tt>null</tt>.
*/
public V put(K key, V value) {
if (value == null)
throw new NullPointerException();
return doPut(key, value, false);
}
/**
* Removes the mapping for this key from this Map if present.
*
* @param key
* key for which mapping should be removed
* @return previous value associated with specified key, or <tt>null</tt> if
* there was no mapping for key.
*
* @throws ClassCastException
* if the key cannot be compared with the keys currently in the map.
* @throws NullPointerException
* if the key is <tt>null</tt>.
*/
public V remove(Object key) {
return doRemove(key, null);
}
/**
* Returns <tt>true</tt> if this map maps one or more keys to the specified
* value. This operation requires time linear in the Map size.
*
* @param value
* value whose presence in this Map is to be tested.
* @return <tt>true</tt> if a mapping to <tt>value</tt> exists;
* <tt>false</tt> otherwise.
* @throws NullPointerException
* if the value is <tt>null</tt>.
*/
public boolean containsValue(Object value) {
if (value == null)
throw new NullPointerException();
for (Node<K, V> n = findFirst(); n != null; n = n.next) {
V v = n.getValidValue();
if (v != null && value.equals(v))
return true;
}
return false;
}
/**
* Returns the number of elements in this map. If this map contains more than
* <tt>Integer.MAX_VALUE</tt> elements, it returns
* <tt>Integer.MAX_VALUE</tt>.
*
* <p>
* Beware that, unlike in most collections, this method is <em>NOT</em> a
* constant-time operation. Because of the asynchronous nature of these maps,
* determining the current number of elements requires traversing them all to
* count them. Additionally, it is possible for the size to change during
* execution of this method, in which case the returned result will be
* inaccurate. Thus, this method is typically not very useful in concurrent
* applications.
*
* @return the number of elements in this map.
*/
public int size() {
long count = 0;
for (Node<K, V> n = findFirst(); n != null; n = n.next) {
if (n.getValidValue() != null)
++count;
}
return (count >= Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) count;
}
/**
* Returns <tt>true</tt> if this map contains no key-value mappings.
*
* @return <tt>true</tt> if this map contains no key-value mappings.
*/
public boolean isEmpty() {
return findFirst() == null;
}
/**
* Removes all mappings from this map.
*/
public void clear() {
initialize();
}
/**
* Returns a set view of the keys contained in this map. The set is backed by
* the map, so changes to the map are reflected in the set, and vice-versa.
* The set supports element removal, which removes the corresponding mapping
* from this map, via the <tt>Iterator.remove</tt>, <tt>Set.remove</tt>,
* <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt>
* operations. It does not support the <tt>add</tt> or <tt>addAll</tt>
* operations. The view"s <tt>iterator</tt> is a "weakly consistent"
* iterator that will never throw
* {@link java.util.ConcurrentModificationException}, and guarantees to
* traverse elements as they existed upon construction of the iterator, and
* may (but is not guaranteed to) reflect any modifications subsequent to
* construction.
*
* @return a set view of the keys contained in this map.
*/
public Set<K> keySet() {
/*
* Note: Lazy intialization works here and for other views because view
* classes are stateless/immutable so it doesn"t matter wrt correctness if
* more than one is created (which will only rarely happen). Even so, the
* following idiom conservatively ensures that the method returns the one it
* created if it does so, not one created by another racing thread.
*/
KeySet ks = keySet;
return (ks != null) ? ks : (keySet = new KeySet());
}
/**
* Returns a set view of the keys contained in this map in descending order.
* The set is backed by the map, so changes to the map are reflected in the
* set, and vice-versa. The set supports element removal, which removes the
* corresponding mapping from this map, via the <tt>Iterator.remove</tt>,
* <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt>, and
* <tt>clear</tt> operations. It does not support the <tt>add</tt> or
* <tt>addAll</tt> operations. The view"s <tt>iterator</tt> is a "weakly
* consistent" iterator that will never throw
* {@link java.util.ConcurrentModificationException}, and guarantees to
* traverse elements as they existed upon construction of the iterator, and
* may (but is not guaranteed to) reflect any modifications subsequent to
* construction.
*
* @return a set view of the keys contained in this map.
*/
public Set<K> descendingKeySet() {
/*
* Note: Lazy intialization works here and for other views because view
* classes are stateless/immutable so it doesn"t matter wrt correctness if
* more than one is created (which will only rarely happen). Even so, the
* following idiom conservatively ensures that the method returns the one it
* created if it does so, not one created by another racing thread.
*/
DescendingKeySet ks = descendingKeySet;
return (ks != null) ? ks : (descendingKeySet = new DescendingKeySet());
}
/**
* Returns a collection view of the values contained in this map. The
* collection is backed by the map, so changes to the map are reflected in the
* collection, and vice-versa. The collection supports element removal, which
* removes the corresponding mapping from this map, via the
* <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
* <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt>
* operations. It does not support the <tt>add</tt> or <tt>addAll</tt>
* operations. The view"s <tt>iterator</tt> is a "weakly consistent"
* iterator that will never throw {@link
* java.util.ConcurrentModificationException}, and guarantees to traverse
* elements as they existed upon construction of the iterator, and may (but is
* not guaranteed to) reflect any modifications subsequent to construction.
*
* @return a collection view of the values contained in this map.
*/
public Collection<V> values() {
Values vs = values;
return (vs != null) ? vs : (values = new Values());
}
/**
* Returns a collection view of the mappings contained in this map. Each
* element in the returned collection is a <tt>Map.Entry</tt>. The
* collection is backed by the map, so changes to the map are reflected in the
* collection, and vice-versa. The collection supports element removal, which
* removes the corresponding mapping from the map, via the
* <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
* <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt>
* operations. It does not support the <tt>add</tt> or <tt>addAll</tt>
* operations. The view"s <tt>iterator</tt> is a "weakly consistent"
* iterator that will never throw {@link
* java.util.ConcurrentModificationException}, and guarantees to traverse
* elements as they existed upon construction of the iterator, and may (but is
* not guaranteed to) reflect any modifications subsequent to construction.
* The <tt>Map.Entry</tt> elements returned by <tt>iterator.next()</tt> do
* <em>not</em> support the <tt>setValue</tt> operation.
*
* @return a collection view of the mappings contained in this map.
*/
public Set<Map.Entry<K, V>> entrySet() {
EntrySet es = entrySet;
return (es != null) ? es : (entrySet = new EntrySet());
}
/**
* Returns a collection view of the mappings contained in this map, in
* descending order. Each element in the returned collection is a
* <tt>Map.Entry</tt>. The collection is backed by the map, so changes to
* the map are reflected in the collection, and vice-versa. The collection
* supports element removal, which removes the corresponding mapping from the
* map, via the <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
* <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt>
* operations. It does not support the <tt>add</tt> or <tt>addAll</tt>
* operations. The view"s <tt>iterator</tt> is a "weakly consistent"
* iterator that will never throw {@link
* java.util.ConcurrentModificationException}, and guarantees to traverse
* elements as they existed upon construction of the iterator, and may (but is
* not guaranteed to) reflect any modifications subsequent to construction.
* The <tt>Map.Entry</tt> elements returned by <tt>iterator.next()</tt> do
* <em>not</em> support the <tt>setValue</tt> operation.
*
* @return a collection view of the mappings contained in this map.
*/
public Set<Map.Entry<K, V>> descendingEntrySet() {
DescendingEntrySet es = descendingEntrySet;
return (es != null) ? es : (descendingEntrySet = new DescendingEntrySet());
}
/* ---------------- AbstractMap Overrides -------------- */
/**
* Compares the specified object with this map for equality. Returns
* <tt>true</tt> if the given object is also a map and the two maps
* represent the same mappings. More formally, two maps <tt>t1</tt> and
* <tt>t2</tt> represent the same mappings if
* <tt>t1.keySet().equals(t2.keySet())</tt> and for every key <tt>k</tt>
* in <tt>t1.keySet()</tt>, <tt> (t1.get(k)==null ?
* t2.get(k)==null : t1.get(k).equals(t2.get(k))) </tt>.
* This operation may return misleading results if either map is concurrently
* modified during execution of this method.
*
* @param o
* object to be compared for equality with this map.
* @return <tt>true</tt> if the specified object is equal to this map.
*/
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Map))
return false;
Map<K, V> t = (Map<K, V>) o;
try {
return (containsAllMappings(this, t) && containsAllMappings(t, this));
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
}
/**
* Helper for equals -- check for containment, avoiding nulls.
*/
static <K, V> boolean containsAllMappings(Map<K, V> a, Map<K, V> b) {
Iterator<Entry<K, V>> it = b.entrySet().iterator();
while (it.hasNext()) {
Entry<K, V> e = it.next();
Object k = e.getKey();
Object v = e.getValue();
if (k == null || v == null || !v.equals(a.get(k)))
return false;
}
return true;
}
/* ------ ConcurrentMap API methods ------ */
/**
* If the specified key is not already associated with a value, associate it
* with the given value. This is equivalent to
*
* <pre>
* if (!map.containsKey(key))
* return map.put(key, value);
* else
* return map.get(key);
* </pre>
*
* except that the action is performed atomically.
*
* @param key
* key with which the specified value is to be associated.
* @param value
* value to be associated with the specified key.
* @return previous value associated with specified key, or <tt>null</tt> if
* there was no mapping for key.
*
* @throws ClassCastException
* if the key cannot be compared with the keys currently in the map.
* @throws NullPointerException
* if the key or value are <tt>null</tt>.
*/
public V putIfAbsent(K key, V value) {
if (value == null)
throw new NullPointerException();
return doPut(key, value, true);
}
/**
* Remove entry for key only if currently mapped to given value. Acts as
*
* <pre>
* if ((map.containsKey(key) && map.get(key).equals(value)) {
* map.remove(key);
* return true;
* } else return false;
* </pre>
*
* except that the action is performed atomically.
*
* @param key
* key with which the specified value is associated.
* @param value
* value associated with the specified key.
* @return true if the value was removed, false otherwise
* @throws ClassCastException
* if the key cannot be compared with the keys currently in the map.
* @throws NullPointerException
* if the key or value are <tt>null</tt>.
*/
public boolean remove(Object key, Object value) {
if (value == null)
throw new NullPointerException();
return doRemove(key, value) != null;
}
/**
* Replace entry for key only if currently mapped to given value. Acts as
*
* <pre>
* if ((map.containsKey(key) && map.get(key).equals(oldValue)) {
* map.put(key, newValue);
* return true;
* } else return false;
* </pre>
*
* except that the action is performed atomically.
*
* @param key
* key with which the specified value is associated.
* @param oldValue
* value expected to be associated with the specified key.
* @param newValue
* value to be associated with the specified key.
* @return true if the value was replaced
* @throws ClassCastException
* if the key cannot be compared with the keys currently in the map.
* @throws NullPointerException
* if key, oldValue or newValue are <tt>null</tt>.
*/
public boolean replace(K key, V oldValue, V newValue) {
if (oldValue == null || newValue == null)
throw new NullPointerException();
Comparable<K> k = comparable(key);
for (;;) {
Node<K, V> n = findNode(k);
if (n == null)
return false;
Object v = n.value;
if (v != null) {
if (!oldValue.equals(v))
return false;
if (n.casValue(v, newValue))
return true;
}
}
}
/**
* Replace entry for key only if currently mapped to some value. Acts as
*
* <pre>
* if ((map.containsKey(key)) {
* return map.put(key, value);
* } else return null;
* </pre>
*
* except that the action is performed atomically.
*
* @param key
* key with which the specified value is associated.
* @param value
* value to be associated with the specified key.
* @return previous value associated with specified key, or <tt>null</tt> if
* there was no mapping for key.
* @throws ClassCastException
* if the key cannot be compared with the keys currently in the map.
* @throws NullPointerException
* if the key or value are <tt>null</tt>.
*/
public V replace(K key, V value) {
if (value == null)
throw new NullPointerException();
Comparable<K> k = comparable(key);
for (;;) {
Node<K, V> n = findNode(k);
if (n == null)
return null;
Object v = n.value;
if (v != null && n.casValue(v, value))
return (V) v;
}
}
/* ------ SortedMap API methods ------ */
/**
* Returns the comparator used to order this map, or <tt>null</tt> if this
* map uses its keys" natural order.
*
* @return the comparator associated with this map, or <tt>null</tt> if it
* uses its keys" natural sort method.
*/
public Comparator<? super K> comparator() {
return comparator;
}
/**
* Returns the first (lowest) key currently in this map.
*
* @return the first (lowest) key currently in this map.
* @throws NoSuchElementException
* Map is empty.
*/
public K firstKey() {
Node<K, V> n = findFirst();
if (n == null)
throw new NoSuchElementException();
return n.key;
}
/**
* Returns the last (highest) key currently in this map.
*
* @return the last (highest) key currently in this map.
* @throws NoSuchElementException
* Map is empty.
*/
public K lastKey() {
Node<K, V> n = findLast();
if (n == null)
throw new NoSuchElementException();
return n.key;
}
/**
* Returns a view of the portion of this map whose keys range from
* <tt>fromKey</tt>, inclusive, to <tt>toKey</tt>, exclusive. (If
* <tt>fromKey</tt> and <tt>toKey</tt> are equal, the returned sorted map
* is empty.) The returned sorted map is backed by this map, so changes in the
* returned sorted map are reflected in this map, and vice-versa.
*
* @param fromKey
* low endpoint (inclusive) of the subMap.
* @param toKey
* high endpoint (exclusive) of the subMap.
*
* @return a view of the portion of this map whose keys range from
* <tt>fromKey</tt>, inclusive, to <tt>toKey</tt>, exclusive.
*
* @throws ClassCastException
* if <tt>fromKey</tt> and <tt>toKey</tt> cannot be compared to
* one another using this map"s comparator (or, if the map has no
* comparator, using natural ordering).
* @throws IllegalArgumentException
* if <tt>fromKey</tt> is greater than <tt>toKey</tt>.
* @throws NullPointerException
* if <tt>fromKey</tt> or <tt>toKey</tt> is <tt>null</tt>.
*/
public ConcurrentNavigableMap<K, V> subMap(K fromKey, K toKey) {
if (fromKey == null || toKey == null)
throw new NullPointerException();
return new ConcurrentSkipListSubMap(this, fromKey, toKey);
}
/**
* Returns a view of the portion of this map whose keys are strictly less than
* <tt>toKey</tt>. The returned sorted map is backed by this map, so
* changes in the returned sorted map are reflected in this map, and
* vice-versa.
*
* @param toKey
* high endpoint (exclusive) of the headMap.
* @return a view of the portion of this map whose keys are strictly less than
* <tt>toKey</tt>.
*
* @throws ClassCastException
* if <tt>toKey</tt> is not compatible with this map"s comparator
* (or, if the map has no comparator, if <tt>toKey</tt> does not
* implement <tt>Comparable</tt>).
* @throws NullPointerException
* if <tt>toKey</tt> is <tt>null</tt>.
*/
public ConcurrentNavigableMap<K, V> headMap(K toKey) {
if (toKey == null)
throw new NullPointerException();
return new ConcurrentSkipListSubMap(this, null, toKey);
}
/**
* Returns a view of the portion of this map whose keys are greater than or
* equal to <tt>fromKey</tt>. The returned sorted map is backed by this
* map, so changes in the returned sorted map are reflected in this map, and
* vice-versa.
*
* @param fromKey
* low endpoint (inclusive) of the tailMap.
* @return a view of the portion of this map whose keys are greater than or
* equal to <tt>fromKey</tt>.
* @throws ClassCastException
* if <tt>fromKey</tt> is not compatible with this map"s
* comparator (or, if the map has no comparator, if <tt>fromKey</tt>
* does not implement <tt>Comparable</tt>).
* @throws NullPointerException
* if <tt>fromKey</tt> is <tt>null</tt>.
*/
public ConcurrentNavigableMap<K, V> tailMap(K fromKey) {
if (fromKey == null)
throw new NullPointerException();
return new ConcurrentSkipListSubMap(this, fromKey, null);
}
/* ---------------- Relational operations -------------- */
/**
* Returns a key-value mapping associated with the least key greater than or
* equal to the given key, or <tt>null</tt> if there is no such entry. The
* returned entry does <em>not</em> support the <tt>Entry.setValue</tt>
* method.
*
* @param key
* the key.
* @return an Entry associated with ceiling of given key, or <tt>null</tt>
* if there is no such Entry.
* @throws ClassCastException
* if key cannot be compared with the keys currently in the map.
* @throws NullPointerException
* if key is <tt>null</tt>.
*/
public Map.Entry<K, V> ceilingEntry(K key) {
return getNear(key, GT | EQ);
}
/**
* Returns least key greater than or equal to the given key, or <tt>null</tt>
* if there is no such key.
*
* @param key
* the key.
* @return the ceiling key, or <tt>null</tt> if there is no such key.
* @throws ClassCastException
* if key cannot be compared with the keys currently in the map.
* @throws NullPointerException
* if key is <tt>null</tt>.
*/
public K ceilingKey(K key) {
Node<K, V> n = findNear(key, GT | EQ);
return (n == null) ? null : n.key;
}
/**
* Returns a key-value mapping associated with the greatest key strictly less
* than the given key, or <tt>null</tt> if there is no such entry. The
* returned entry does <em>not</em> support the <tt>Entry.setValue</tt>
* method.
*
* @param key
* the key.
* @return an Entry with greatest key less than the given key, or
* <tt>null</tt> if there is no such Entry.
* @throws ClassCastException
* if key cannot be compared with the keys currently in the map.
* @throws NullPointerException
* if key is <tt>null</tt>.
*/
public Map.Entry<K, V> lowerEntry(K key) {
return getNear(key, LT);
}
/**
* Returns the greatest key strictly less than the given key, or <tt>null</tt>
* if there is no such key.
*
* @param key
* the key.
* @return the greatest key less than the given key, or <tt>null</tt> if
* there is no such key.
* @throws ClassCastException
* if key cannot be compared with the keys currently in the map.
* @throws NullPointerException
* if key is <tt>null</tt>.
*/
public K lowerKey(K key) {
Node<K, V> n = findNear(key, LT);
return (n == null) ? null : n.key;
}
/**
* Returns a key-value mapping associated with the greatest key less than or
* equal to the given key, or <tt>null</tt> if there is no such entry. The
* returned entry does <em>not</em> support the <tt>Entry.setValue</tt>
* method.
*
* @param key
* the key.
* @return an Entry associated with floor of given key, or <tt>null</tt> if
* there is no such Entry.
* @throws ClassCastException
* if key cannot be compared with the keys currently in the map.
* @throws NullPointerException
* if key is <tt>null</tt>.
*/
public Map.Entry<K, V> floorEntry(K key) {
return getNear(key, LT | EQ);
}
/**
* Returns the greatest key less than or equal to the given key, or
* <tt>null</tt> if there is no such key.
*
* @param key
* the key.
* @return the floor of given key, or <tt>null</tt> if there is no such key.
* @throws ClassCastException
* if key cannot be compared with the keys currently in the map.
* @throws NullPointerException
* if key is <tt>null</tt>.
*/
public K floorKey(K key) {
Node<K, V> n = findNear(key, LT | EQ);
return (n == null) ? null : n.key;
}
/**
* Returns a key-value mapping associated with the least key strictly greater
* than the given key, or <tt>null</tt> if there is no such entry. The
* returned entry does <em>not</em> support the <tt>Entry.setValue</tt>
* method.
*
* @param key
* the key.
* @return an Entry with least key greater than the given key, or
* <tt>null</tt> if there is no such Entry.
* @throws ClassCastException
* if key cannot be compared with the keys currently in the map.
* @throws NullPointerException
* if key is <tt>null</tt>.
*/
public Map.Entry<K, V> higherEntry(K key) {
return getNear(key, GT);
}
/**
* Returns the least key strictly greater than the given key, or <tt>null</tt>
* if there is no such key.
*
* @param key
* the key.
* @return the least key greater than the given key, or <tt>null</tt> if
* there is no such key.
* @throws ClassCastException
* if key cannot be compared with the keys currently in the map.
* @throws NullPointerException
* if key is <tt>null</tt>.
*/
public K higherKey(K key) {
Node<K, V> n = findNear(key, GT);
return (n == null) ? null : n.key;
}
/**
* Returns a key-value mapping associated with the least key in this map, or
* <tt>null</tt> if the map is empty. The returned entry does <em>not</em>
* support the <tt>Entry.setValue</tt> method.
*
* @return an Entry with least key, or <tt>null</tt> if the map is empty.
*/
public Map.Entry<K, V> firstEntry() {
for (;;) {
Node<K, V> n = findFirst();
if (n == null)
return null;
SnapshotEntry<K, V> e = n.createSnapshot();
if (e != null)
return e;
}
}
/**
* Returns a key-value mapping associated with the greatest key in this map,
* or <tt>null</tt> if the map is empty. The returned entry does
* <em>not</em> support the <tt>Entry.setValue</tt> method.
*
* @return an Entry with greatest key, or <tt>null</tt> if the map is empty.
*/
public Map.Entry<K, V> lastEntry() {
for (;;) {
Node<K, V> n = findLast();
if (n == null)
return null;
SnapshotEntry<K, V> e = n.createSnapshot();
if (e != null)
return e;
}
}
/**
* Removes and returns a key-value mapping associated with the least key in
* this map, or <tt>null</tt> if the map is empty. The returned entry does
* <em>not</em> support the <tt>Entry.setValue</tt> method.
*
* @return the removed first entry of this map, or <tt>null</tt> if the map
* is empty.
*/
public Map.Entry<K, V> pollFirstEntry() {
return (SnapshotEntry<K, V>) doRemoveFirst(false);
}
/**
* Removes and returns a key-value mapping associated with the greatest key in
* this map, or <tt>null</tt> if the map is empty. The returned entry does
* <em>not</em> support the <tt>Entry.setValue</tt> method.
*
* @return the removed last entry of this map, or <tt>null</tt> if the map
* is empty.
*/
public Map.Entry<K, V> pollLastEntry() {
return (SnapshotEntry<K, V>) doRemoveLast(false);
}
/* ---------------- Iterators -------------- */
/**
* Base of ten kinds of iterator classes: ascending: {map, submap} X {key,
* value, entry} descending: {map, submap} X {key, entry}
*/
abstract class Iter {
/** the last node returned by next() */
Node<K, V> last;
/** the next node to return from next(); */
Node<K, V> next;
/** Cache of next value field to maintain weak consistency */
Object nextValue;
Iter() {
}
public final boolean hasNext() {
return next != null;
}
/** initialize ascending iterator for entire range */
final void initAscending() {
for (;;) {
next = findFirst();
if (next == null)
break;
nextValue = next.value;
if (nextValue != null && nextValue != next)
break;
}
}
/**
* initialize ascending iterator starting at given least key, or first node
* if least is <tt>null</tt>, but not greater or equal to fence, or end
* if fence is <tt>null</tt>.
*/
final void initAscending(K least, K fence) {
for (;;) {
next = findCeiling(least);
if (next == null)
break;
nextValue = next.value;
if (nextValue != null && nextValue != next) {
if (fence != null && compare(fence, next.key) <= 0) {
next = null;
nextValue = null;
}
break;
}
}
}
/** advance next to higher entry */
final void ascend() {
if ((last = next) == null)
throw new NoSuchElementException();
for (;;) {
next = next.next;
if (next == null)
break;
nextValue = next.value;
if (nextValue != null && nextValue != next)
break;
}
}
/**
* Version of ascend for submaps to stop at fence
*/
final void ascend(K fence) {
if ((last = next) == null)
throw new NoSuchElementException();
for (;;) {
next = next.next;
if (next == null)
break;
nextValue = next.value;
if (nextValue != null && nextValue != next) {
if (fence != null && compare(fence, next.key) <= 0) {
next = null;
nextValue = null;
}
break;
}
}
}
/** initialize descending iterator for entire range */
final void initDescending() {
for (;;) {
next = findLast();
if (next == null)
break;
nextValue = next.value;
if (nextValue != null && nextValue != next)
break;
}
}
/**
* initialize descending iterator starting at key less than or equal to
* given fence key, or last node if fence is <tt>null</tt>, but not less
* than least, or beginning if lest is <tt>null</tt>.
*/
final void initDescending(K least, K fence) {
for (;;) {
next = findLower(fence);
if (next == null)
break;
nextValue = next.value;
if (nextValue != null && nextValue != next) {
if (least != null && compare(least, next.key) > 0) {
next = null;
nextValue = null;
}
break;
}
}
}
/** advance next to lower entry */
final void descend() {
if ((last = next) == null)
throw new NoSuchElementException();
K k = last.key;
for (;;) {
next = findNear(k, LT);
if (next == null)
break;
nextValue = next.value;
if (nextValue != null && nextValue != next)
break;
}
}
/**
* Version of descend for submaps to stop at least
*/
final void descend(K least) {
if ((last = next) == null)
throw new NoSuchElementException();
K k = last.key;
for (;;) {
next = findNear(k, LT);
if (next == null)
break;
nextValue = next.value;
if (nextValue != null && nextValue != next) {
if (least != null && compare(least, next.key) > 0) {
next = null;
nextValue = null;
}
break;
}
}
}
public void remove() {
Node<K, V> l = last;
if (l == null)
throw new IllegalStateException();
// It would not be worth all of the overhead to directly
// unlink from here. Using remove is fast enough.
ConcurrentSkipListMap.this.remove(l.key);
}
}
final class ValueIterator extends Iter implements Iterator<V> {
ValueIterator() {
initAscending();
}
public V next() {
Object v = nextValue;
ascend();
return (V) v;
}
}
final class KeyIterator extends Iter implements Iterator<K> {
KeyIterator() {
initAscending();
}
public K next() {
Node<K, V> n = next;
ascend();
return n.key;
}
}
class SubMapValueIterator extends Iter implements Iterator<V> {
final K fence;
SubMapValueIterator(K least, K fence) {
initAscending(least, fence);
this.fence = fence;
}
public V next() {
Object v = nextValue;
ascend(fence);
return (V) v;
}
}
final class SubMapKeyIterator extends Iter implements Iterator<K> {
final K fence;
SubMapKeyIterator(K least, K fence) {
initAscending(least, fence);
this.fence = fence;
}
public K next() {
Node<K, V> n = next;
ascend(fence);
return n.key;
}
}
final class DescendingKeyIterator extends Iter implements Iterator<K> {
DescendingKeyIterator() {
initDescending();
}
public K next() {
Node<K, V> n = next;
descend();
return n.key;
}
}
final class DescendingSubMapKeyIterator extends Iter implements Iterator<K> {
final K least;
DescendingSubMapKeyIterator(K least, K fence) {
initDescending(least, fence);
this.least = least;
}
public K next() {
Node<K, V> n = next;
descend(least);
return n.key;
}
}
/**
* Entry iterators use the same trick as in ConcurrentHashMap and elsewhere of
* using the iterator itself to represent entries, thus avoiding having to
* create entry objects in next().
*/
abstract class EntryIter extends Iter implements Map.Entry<K, V> {
/** Cache of last value returned */
Object lastValue;
EntryIter() {
}
public K getKey() {
Node<K, V> l = last;
if (l == null)
throw new IllegalStateException();
return l.key;
}
public V getValue() {
Object v = lastValue;
if (last == null || v == null)
throw new IllegalStateException();
return (V) v;
}
public V setValue(V value) {
throw new UnsupportedOperationException();
}
public boolean equals(Object o) {
// If not acting as entry, just use default.
if (last == null)
return super.equals(o);
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry) o;
return (getKey().equals(e.getKey()) && getValue().equals(e.getValue()));
}
public int hashCode() {
// If not acting as entry, just use default.
if (last == null)
return super.hashCode();
return getKey().hashCode() ^ getValue().hashCode();
}
public String toString() {
// If not acting as entry, just use default.
if (last == null)
return super.toString();
return getKey() + "=" + getValue();
}
}
final class EntryIterator extends EntryIter implements Iterator<Map.Entry<K, V>> {
EntryIterator() {
initAscending();
}
public Map.Entry<K, V> next() {
lastValue = nextValue;
ascend();
return this;
}
}
final class SubMapEntryIterator extends EntryIter implements Iterator<Map.Entry<K, V>> {
final K fence;
SubMapEntryIterator(K least, K fence) {
initAscending(least, fence);
this.fence = fence;
}
public Map.Entry<K, V> next() {
lastValue = nextValue;
ascend(fence);
return this;
}
}
final class DescendingEntryIterator extends EntryIter implements Iterator<Map.Entry<K, V>> {
DescendingEntryIterator() {
initDescending();
}
public Map.Entry<K, V> next() {
lastValue = nextValue;
descend();
return this;
}
}
final class DescendingSubMapEntryIterator extends EntryIter implements Iterator<Map.Entry<K, V>> {
final K least;
DescendingSubMapEntryIterator(K least, K fence) {
initDescending(least, fence);
this.least = least;
}
public Map.Entry<K, V> next() {
lastValue = nextValue;
descend(least);
return this;
}
}
// Factory methods for iterators needed by submaps and/or
// ConcurrentSkipListSet
Iterator<K> keyIterator() {
return new KeyIterator();
}
Iterator<K> descendingKeyIterator() {
return new DescendingKeyIterator();
}
SubMapEntryIterator subMapEntryIterator(K least, K fence) {
return new SubMapEntryIterator(least, fence);
}
DescendingSubMapEntryIterator descendingSubMapEntryIterator(K least, K fence) {
return new DescendingSubMapEntryIterator(least, fence);
}
SubMapKeyIterator subMapKeyIterator(K least, K fence) {
return new SubMapKeyIterator(least, fence);
}
DescendingSubMapKeyIterator descendingSubMapKeyIterator(K least, K fence) {
return new DescendingSubMapKeyIterator(least, fence);
}
SubMapValueIterator subMapValueIterator(K least, K fence) {
return new SubMapValueIterator(least, fence);
}
/* ---------------- Views -------------- */
class KeySet extends AbstractSet<K> {
public Iterator<K> iterator() {
return new KeyIterator();
}
public boolean isEmpty() {
return ConcurrentSkipListMap.this.isEmpty();
}
public int size() {
return ConcurrentSkipListMap.this.size();
}
public boolean contains(Object o) {
return ConcurrentSkipListMap.this.containsKey(o);
}
public boolean remove(Object o) {
return ConcurrentSkipListMap.this.removep(o);
}
public void clear() {
ConcurrentSkipListMap.this.clear();
}
public Object[] toArray() {
Collection<K> c = new ArrayList<K>();
for (Iterator<K> i = iterator(); i.hasNext();)
c.add(i.next());
return c.toArray();
}
public <T> T[] toArray(T[] a) {
Collection<K> c = new ArrayList<K>();
for (Iterator<K> i = iterator(); i.hasNext();)
c.add(i.next());
return c.toArray(a);
}
}
class DescendingKeySet extends KeySet {
public Iterator<K> iterator() {
return new DescendingKeyIterator();
}
}
final class Values extends AbstractCollection<V> {
public Iterator<V> iterator() {
return new ValueIterator();
}
public boolean isEmpty() {
return ConcurrentSkipListMap.this.isEmpty();
}
public int size() {
return ConcurrentSkipListMap.this.size();
}
public boolean contains(Object o) {
return ConcurrentSkipListMap.this.containsValue(o);
}
public void clear() {
ConcurrentSkipListMap.this.clear();
}
public Object[] toArray() {
Collection<V> c = new ArrayList<V>();
for (Iterator<V> i = iterator(); i.hasNext();)
c.add(i.next());
return c.toArray();
}
public <T> T[] toArray(T[] a) {
Collection<V> c = new ArrayList<V>();
for (Iterator<V> i = iterator(); i.hasNext();)
c.add(i.next());
return c.toArray(a);
}
}
class EntrySet extends AbstractSet<Map.Entry<K, V>> {
public Iterator<Map.Entry<K, V>> iterator() {
return new EntryIterator();
}
public boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<K, V> e = (Map.Entry<K, V>) o;
V v = ConcurrentSkipListMap.this.get(e.getKey());
return v != null && v.equals(e.getValue());
}
public boolean remove(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<K, V> e = (Map.Entry<K, V>) o;
return ConcurrentSkipListMap.this.remove(e.getKey(), e.getValue());
}
public boolean isEmpty() {
return ConcurrentSkipListMap.this.isEmpty();
}
public int size() {
return ConcurrentSkipListMap.this.size();
}
public void clear() {
ConcurrentSkipListMap.this.clear();
}
public Object[] toArray() {
Collection<Map.Entry<K, V>> c = new ArrayList<Map.Entry<K, V>>();
for (Map.Entry e : this)
c.add(new SnapshotEntry(e.getKey(), e.getValue()));
return c.toArray();
}
public <T> T[] toArray(T[] a) {
Collection<Map.Entry<K, V>> c = new ArrayList<Map.Entry<K, V>>();
for (Map.Entry e : this)
c.add(new SnapshotEntry(e.getKey(), e.getValue()));
return c.toArray(a);
}
}
class DescendingEntrySet extends EntrySet {
public Iterator<Map.Entry<K, V>> iterator() {
return new DescendingEntryIterator();
}
}
/**
* Submaps returned by {@link ConcurrentSkipListMap} submap operations
* represent a subrange of mappings of their underlying maps. Instances of
* this class support all methods of their underlying maps, differing in that
* mappings outside their range are ignored, and attempts to add mappings
* outside their ranges result in {@link IllegalArgumentException}. Instances
* of this class are constructed only using the <tt>subMap</tt>,
* <tt>headMap</tt>, and <tt>tailMap</tt> methods of their underlying
* maps.
*/
static class ConcurrentSkipListSubMap<K, V> extends AbstractMap<K, V> implements
ConcurrentNavigableMap<K, V>, java.io.Serializable {
private static final long serialVersionUID = -7647078645895051609L;
/** Underlying map */
private final ConcurrentSkipListMap<K, V> m;
/** lower bound key, or null if from start */
private final K least;
/** upper fence key, or null if to end */
private final K fence;
// Lazily initialized view holders
private transient Set<K> keySetView;
private transient Set<Map.Entry<K, V>> entrySetView;
private transient Collection<V> valuesView;
private transient Set<K> descendingKeySetView;
private transient Set<Map.Entry<K, V>> descendingEntrySetView;
/**
* Creates a new submap.
*
* @param least
* inclusive least value, or <tt>null</tt> if from start
* @param fence
* exclusive upper bound or <tt>null</tt> if to end
* @throws IllegalArgumentException
* if least and fence nonnull and least greater than fence
*/
ConcurrentSkipListSubMap(ConcurrentSkipListMap<K, V> map, K least, K fence) {
if (least != null && fence != null && map.rupare(least, fence) > 0)
throw new IllegalArgumentException("inconsistent range");
this.m = map;
this.least = least;
this.fence = fence;
}
/* ---------------- Utilities -------------- */
boolean inHalfOpenRange(K key) {
return m.inHalfOpenRange(key, least, fence);
}
boolean inOpenRange(K key) {
return m.inOpenRange(key, least, fence);
}
ConcurrentSkipListMap.Node<K, V> firstNode() {
return m.findCeiling(least);
}
ConcurrentSkipListMap.Node<K, V> lastNode() {
return m.findLower(fence);
}
boolean isBeforeEnd(ConcurrentSkipListMap.Node<K, V> n) {
return (n != null && (fence == null || n.key == null || // pass by markers
// and headers
m.rupare(fence, n.key) > 0));
}
void checkKey(K key) throws IllegalArgumentException {
if (!inHalfOpenRange(key))
throw new IllegalArgumentException("key out of range");
}
/**
* Returns underlying map. Needed by ConcurrentSkipListSet
*
* @return the backing map
*/
ConcurrentSkipListMap<K, V> getMap() {
return m;
}
/**
* Returns least key. Needed by ConcurrentSkipListSet
*
* @return least key or <tt>null</tt> if from start
*/
K getLeast() {
return least;
}
/**
* Returns fence key. Needed by ConcurrentSkipListSet
*
* @return fence key or <tt>null</tt> of to end
*/
K getFence() {
return fence;
}
/* ---------------- Map API methods -------------- */
public boolean containsKey(Object key) {
K k = (K) key;
return inHalfOpenRange(k) && m.containsKey(k);
}
public V get(Object key) {
K k = (K) key;
return ((!inHalfOpenRange(k)) ? null : m.get(k));
}
public V put(K key, V value) {
checkKey(key);
return m.put(key, value);
}
public V remove(Object key) {
K k = (K) key;
return (!inHalfOpenRange(k)) ? null : m.remove(k);
}
public int size() {
long count = 0;
for (ConcurrentSkipListMap.Node<K, V> n = firstNode(); isBeforeEnd(n); n = n.next) {
if (n.getValidValue() != null)
++count;
}
return count >= Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) count;
}
public boolean isEmpty() {
return !isBeforeEnd(firstNode());
}
public boolean containsValue(Object value) {
if (value == null)
throw new NullPointerException();
for (ConcurrentSkipListMap.Node<K, V> n = firstNode(); isBeforeEnd(n); n = n.next) {
V v = n.getValidValue();
if (v != null && value.equals(v))
return true;
}
return false;
}
public void clear() {
for (ConcurrentSkipListMap.Node<K, V> n = firstNode(); isBeforeEnd(n); n = n.next) {
if (n.getValidValue() != null)
m.remove(n.key);
}
}
/* ---------------- ConcurrentMap API methods -------------- */
public V putIfAbsent(K key, V value) {
checkKey(key);
return m.putIfAbsent(key, value);
}
public boolean remove(Object key, Object value) {
K k = (K) key;
return inHalfOpenRange(k) && m.remove(k, value);
}
public boolean replace(K key, V oldValue, V newValue) {
checkKey(key);
return m.replace(key, oldValue, newValue);
}
public V replace(K key, V value) {
checkKey(key);
return m.replace(key, value);
}
/* ---------------- SortedMap API methods -------------- */
public Comparator<? super K> comparator() {
return m.ruparator();
}
public K firstKey() {
ConcurrentSkipListMap.Node<K, V> n = firstNode();
if (isBeforeEnd(n))
return n.key;
else
throw new NoSuchElementException();
}
public K lastKey() {
ConcurrentSkipListMap.Node<K, V> n = lastNode();
if (n != null) {
K last = n.key;
if (inHalfOpenRange(last))
return last;
}
throw new NoSuchElementException();
}
public ConcurrentNavigableMap<K, V> subMap(K fromKey, K toKey) {
if (fromKey == null || toKey == null)
throw new NullPointerException();
if (!inOpenRange(fromKey) || !inOpenRange(toKey))
throw new IllegalArgumentException("key out of range");
return new ConcurrentSkipListSubMap(m, fromKey, toKey);
}
public ConcurrentNavigableMap<K, V> headMap(K toKey) {
if (toKey == null)
throw new NullPointerException();
if (!inOpenRange(toKey))
throw new IllegalArgumentException("key out of range");
return new ConcurrentSkipListSubMap(m, least, toKey);
}
public ConcurrentNavigableMap<K, V> tailMap(K fromKey) {
if (fromKey == null)
throw new NullPointerException();
if (!inOpenRange(fromKey))
throw new IllegalArgumentException("key out of range");
return new ConcurrentSkipListSubMap(m, fromKey, fence);
}
/* ---------------- Relational methods -------------- */
public Map.Entry<K, V> ceilingEntry(K key) {
return (SnapshotEntry<K, V>) m.getNear(key, m.GT | m.EQ, least, fence, false);
}
public K ceilingKey(K key) {
return (K) m.getNear(key, m.GT | m.EQ, least, fence, true);
}
public Map.Entry<K, V> lowerEntry(K key) {
return (SnapshotEntry<K, V>) m.getNear(key, m.LT, least, fence, false);
}
public K lowerKey(K key) {
return (K) m.getNear(key, m.LT, least, fence, true);
}
public Map.Entry<K, V> floorEntry(K key) {
return (SnapshotEntry<K, V>) m.getNear(key, m.LT | m.EQ, least, fence, false);
}
public K floorKey(K key) {
return (K) m.getNear(key, m.LT | m.EQ, least, fence, true);
}
public Map.Entry<K, V> higherEntry(K key) {
return (SnapshotEntry<K, V>) m.getNear(key, m.GT, least, fence, false);
}
public K higherKey(K key) {
return (K) m.getNear(key, m.GT, least, fence, true);
}
public Map.Entry<K, V> firstEntry() {
for (;;) {
ConcurrentSkipListMap.Node<K, V> n = firstNode();
if (!isBeforeEnd(n))
return null;
Map.Entry<K, V> e = n.createSnapshot();
if (e != null)
return e;
}
}
public Map.Entry<K, V> lastEntry() {
for (;;) {
ConcurrentSkipListMap.Node<K, V> n = lastNode();
if (n == null || !inHalfOpenRange(n.key))
return null;
Map.Entry<K, V> e = n.createSnapshot();
if (e != null)
return e;
}
}
public Map.Entry<K, V> pollFirstEntry() {
return (SnapshotEntry<K, V>) m.removeFirstEntryOfSubrange(least, fence, false);
}
public Map.Entry<K, V> pollLastEntry() {
return (SnapshotEntry<K, V>) m.removeLastEntryOfSubrange(least, fence, false);
}
/* ---------------- Submap Views -------------- */
public Set<K> keySet() {
Set<K> ks = keySetView;
return (ks != null) ? ks : (keySetView = new KeySetView());
}
class KeySetView extends AbstractSet<K> {
public Iterator<K> iterator() {
return m.subMapKeyIterator(least, fence);
}
public int size() {
return ConcurrentSkipListSubMap.this.size();
}
public boolean isEmpty() {
return ConcurrentSkipListSubMap.this.isEmpty();
}
public boolean contains(Object k) {
return ConcurrentSkipListSubMap.this.containsKey(k);
}
public Object[] toArray() {
Collection<K> c = new ArrayList<K>();
for (Iterator<K> i = iterator(); i.hasNext();)
c.add(i.next());
return c.toArray();
}
public <T> T[] toArray(T[] a) {
Collection<K> c = new ArrayList<K>();
for (Iterator<K> i = iterator(); i.hasNext();)
c.add(i.next());
return c.toArray(a);
}
}
public Set<K> descendingKeySet() {
Set<K> ks = descendingKeySetView;
return (ks != null) ? ks : (descendingKeySetView = new DescendingKeySetView());
}
class DescendingKeySetView extends KeySetView {
public Iterator<K> iterator() {
return m.descendingSubMapKeyIterator(least, fence);
}
}
public Collection<V> values() {
Collection<V> vs = valuesView;
return (vs != null) ? vs : (valuesView = new ValuesView());
}
class ValuesView extends AbstractCollection<V> {
public Iterator<V> iterator() {
return m.subMapValueIterator(least, fence);
}
public int size() {
return ConcurrentSkipListSubMap.this.size();
}
public boolean isEmpty() {
return ConcurrentSkipListSubMap.this.isEmpty();
}
public boolean contains(Object v) {
return ConcurrentSkipListSubMap.this.containsValue(v);
}
public Object[] toArray() {
Collection<V> c = new ArrayList<V>();
for (Iterator<V> i = iterator(); i.hasNext();)
c.add(i.next());
return c.toArray();
}
public <T> T[] toArray(T[] a) {
Collection<V> c = new ArrayList<V>();
for (Iterator<V> i = iterator(); i.hasNext();)
c.add(i.next());
return c.toArray(a);
}
}
public Set<Map.Entry<K, V>> entrySet() {
Set<Map.Entry<K, V>> es = entrySetView;
return (es != null) ? es : (entrySetView = new EntrySetView());
}
class EntrySetView extends AbstractSet<Map.Entry<K, V>> {
public Iterator<Map.Entry<K, V>> iterator() {
return m.subMapEntryIterator(least, fence);
}
public int size() {
return ConcurrentSkipListSubMap.this.size();
}
public boolean isEmpty() {
return ConcurrentSkipListSubMap.this.isEmpty();
}
public boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<K, V> e = (Map.Entry<K, V>) o;
K key = e.getKey();
if (!inHalfOpenRange(key))
return false;
V v = m.get(key);
return v != null && v.equals(e.getValue());
}
public boolean remove(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<K, V> e = (Map.Entry<K, V>) o;
K key = e.getKey();
if (!inHalfOpenRange(key))
return false;
return m.remove(key, e.getValue());
}
public Object[] toArray() {
Collection<Map.Entry<K, V>> c = new ArrayList<Map.Entry<K, V>>();
for (Map.Entry e : this)
c.add(new SnapshotEntry(e.getKey(), e.getValue()));
return c.toArray();
}
public <T> T[] toArray(T[] a) {
Collection<Map.Entry<K, V>> c = new ArrayList<Map.Entry<K, V>>();
for (Map.Entry e : this)
c.add(new SnapshotEntry(e.getKey(), e.getValue()));
return c.toArray(a);
}
}
public Set<Map.Entry<K, V>> descendingEntrySet() {
Set<Map.Entry<K, V>> es = descendingEntrySetView;
return (es != null) ? es : (descendingEntrySetView = new DescendingEntrySetView());
}
class DescendingEntrySetView extends EntrySetView {
public Iterator<Map.Entry<K, V>> iterator() {
return m.descendingSubMapEntryIterator(least, fence);
}
}
}
}
Copy On Write Map
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* A thread-safe version of {@link Map} in which all operations that change the
* Map are implemented by making a new copy of the underlying Map.
*
* While the creation of a new Map can be expensive, this class is designed for
* cases in which the primary function is to read data from the Map, not to
* modify the Map. Therefore the operations that do not cause a change to this
* class happen quickly and concurrently.
*
* @author The Apache MINA Project (dev@mina.apache.org)
* @version $Rev$, $Date$
*/
public class CopyOnWriteMap<K, V> implements Map<K, V>, Cloneable {
private volatile Map<K, V> internalMap;
/**
* Creates a new instance of CopyOnWriteMap.
*
*/
public CopyOnWriteMap() {
internalMap = new HashMap<K, V>();
}
/**
* Creates a new instance of CopyOnWriteMap with the specified initial size
*
* @param initialCapacity
* The initial size of the Map.
*/
public CopyOnWriteMap(int initialCapacity) {
internalMap = new HashMap<K, V>(initialCapacity);
}
/**
* Creates a new instance of CopyOnWriteMap in which the
* initial data being held by this map is contained in
* the supplied map.
*
* @param data
* A Map containing the initial contents to be placed into
* this class.
*/
public CopyOnWriteMap(Map<K, V> data) {
internalMap = new HashMap<K, V>(data);
}
/**
* Adds the provided key and value to this map.
*
* @see java.util.Map#put(java.lang.Object, java.lang.Object)
*/
public V put(K key, V value) {
synchronized (this) {
Map<K, V> newMap = new HashMap<K, V>(internalMap);
V val = newMap.put(key, value);
internalMap = newMap;
return val;
}
}
/**
* Removed the value and key from this map based on the
* provided key.
*
* @see java.util.Map#remove(java.lang.Object)
*/
public V remove(Object key) {
synchronized (this) {
Map<K, V> newMap = new HashMap<K, V>(internalMap);
V val = newMap.remove(key);
internalMap = newMap;
return val;
}
}
/**
* Inserts all the keys and values contained in the
* provided map to this map.
*
* @see java.util.Map#putAll(java.util.Map)
*/
public void putAll(Map<? extends K, ? extends V> newData) {
synchronized (this) {
Map<K, V> newMap = new HashMap<K, V>(internalMap);
newMap.putAll(newData);
internalMap = newMap;
}
}
/**
* Removes all entries in this map.
*
* @see java.util.Map#clear()
*/
public void clear() {
synchronized (this) {
internalMap = new HashMap<K, V>();
}
}
//
// Below are methods that do not modify
// the internal Maps
/**
* Returns the number of key/value pairs in this map.
*
* @see java.util.Map#size()
*/
public int size() {
return internalMap.size();
}
/**
* Returns true if this map is empty, otherwise false.
*
* @see java.util.Map#isEmpty()
*/
public boolean isEmpty() {
return internalMap.isEmpty();
}
/**
* Returns true if this map contains the provided key, otherwise
* this method return false.
*
* @see java.util.Map#containsKey(java.lang.Object)
*/
public boolean containsKey(Object key) {
return internalMap.containsKey(key);
}
/**
* Returns true if this map contains the provided value, otherwise
* this method returns false.
*
* @see java.util.Map#containsValue(java.lang.Object)
*/
public boolean containsValue(Object value) {
return internalMap.containsValue(value);
}
/**
* Returns the value associated with the provided key from this
* map.
*
* @see java.util.Map#get(java.lang.Object)
*/
public V get(Object key) {
return internalMap.get(key);
}
/**
* This method will return a read-only {@link Set}.
*/
public Set<K> keySet() {
return internalMap.keySet();
}
/**
* This method will return a read-only {@link Collection}.
*/
public Collection<V> values() {
return internalMap.values();
}
/**
* This method will return a read-only {@link Set}.
*/
public Set<Entry<K, V>> entrySet() {
return internalMap.entrySet();
}
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
}
}
Expiring Map
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* A map with expiration. This class contains a worker thread that will
* periodically check this class in order to determine if any objects
* should be removed based on the provided time-to-live value.
*
* @author The Apache MINA Project (dev@mina.apache.org)
* @version $Rev: 662890 $, $Date: 2008-06-03 23:14:21 +0200 (Tue, 03 Jun 2008) $
*/
public class ExpiringMap<K, V> implements Map<K, V> {
/**
* The default value, 60
*/
public static final int DEFAULT_TIME_TO_LIVE = 60;
/**
* The default value, 1
*/
public static final int DEFAULT_EXPIRATION_INTERVAL = 1;
private static volatile int expirerCount = 1;
private final ConcurrentHashMap<K, ExpiringObject> delegate;
private final CopyOnWriteArrayList<ExpirationListener<V>> expirationListeners;
private final Expirer expirer;
/**
* Creates a new instance of ExpiringMap using the default values
* DEFAULT_TIME_TO_LIVE and DEFAULT_EXPIRATION_INTERVAL
*
*/
public ExpiringMap() {
this(DEFAULT_TIME_TO_LIVE, DEFAULT_EXPIRATION_INTERVAL);
}
/**
* Creates a new instance of ExpiringMap using the supplied
* time-to-live value and the default value for DEFAULT_EXPIRATION_INTERVAL
*
* @param timeToLive
* The time-to-live value (seconds)
*/
public ExpiringMap(int timeToLive) {
this(timeToLive, DEFAULT_EXPIRATION_INTERVAL);
}
/**
* Creates a new instance of ExpiringMap using the supplied values and
* a {@link ConcurrentHashMap} for the internal data structure.
*
* @param timeToLive
* The time-to-live value (seconds)
* @param expirationInterval
* The time between checks to see if a value should be removed (seconds)
*/
public ExpiringMap(int timeToLive, int expirationInterval) {
this(new ConcurrentHashMap<K, ExpiringObject>(),
new CopyOnWriteArrayList<ExpirationListener<V>>(), timeToLive,
expirationInterval);
}
private ExpiringMap(ConcurrentHashMap<K, ExpiringObject> delegate,
CopyOnWriteArrayList<ExpirationListener<V>> expirationListeners,
int timeToLive, int expirationInterval) {
this.delegate = delegate;
this.expirationListeners = expirationListeners;
this.expirer = new Expirer();
expirer.setTimeToLive(timeToLive);
expirer.setExpirationInterval(expirationInterval);
}
public V put(K key, V value) {
ExpiringObject answer = delegate.put(key, new ExpiringObject(key,
value, System.currentTimeMillis()));
if (answer == null) {
return null;
}
return answer.getValue();
}
public V get(Object key) {
ExpiringObject object = delegate.get(key);
if (object != null) {
object.setLastAccessTime(System.currentTimeMillis());
return object.getValue();
}
return null;
}
public V remove(Object key) {
ExpiringObject answer = delegate.remove(key);
if (answer == null) {
return null;
}
return answer.getValue();
}
public boolean containsKey(Object key) {
return delegate.containsKey(key);
}
public boolean containsValue(Object value) {
return delegate.containsValue(value);
}
public int size() {
return delegate.size();
}
public boolean isEmpty() {
return delegate.isEmpty();
}
public void clear() {
delegate.clear();
}
@Override
public int hashCode() {
return delegate.hashCode();
}
public Set<K> keySet() {
return delegate.keySet();
}
@Override
public boolean equals(Object obj) {
return delegate.equals(obj);
}
public void putAll(Map<? extends K, ? extends V> inMap) {
for (Entry<? extends K, ? extends V> e : inMap.entrySet()) {
this.put(e.getKey(), e.getValue());
}
}
public Collection<V> values() {
throw new UnsupportedOperationException();
}
public Set<Map.Entry<K, V>> entrySet() {
throw new UnsupportedOperationException();
}
public void addExpirationListener(ExpirationListener<V> listener) {
expirationListeners.add(listener);
}
public void removeExpirationListener(
ExpirationListener<V> listener) {
expirationListeners.remove(listener);
}
public Expirer getExpirer() {
return expirer;
}
public int getExpirationInterval() {
return expirer.getExpirationInterval();
}
public int getTimeToLive() {
return expirer.getTimeToLive();
}
public void setExpirationInterval(int expirationInterval) {
expirer.setExpirationInterval(expirationInterval);
}
public void setTimeToLive(int timeToLive) {
expirer.setTimeToLive(timeToLive);
}
private class ExpiringObject {
private K key;
private V value;
private long lastAccessTime;
private final ReadWriteLock lastAccessTimeLock = new ReentrantReadWriteLock();
ExpiringObject(K key, V value, long lastAccessTime) {
if (value == null) {
throw new IllegalArgumentException(
"An expiring object cannot be null.");
}
this.key = key;
this.value = value;
this.lastAccessTime = lastAccessTime;
}
public long getLastAccessTime() {
lastAccessTimeLock.readLock().lock();
try {
return lastAccessTime;
} finally {
lastAccessTimeLock.readLock().unlock();
}
}
public void setLastAccessTime(long lastAccessTime) {
lastAccessTimeLock.writeLock().lock();
try {
this.lastAccessTime = lastAccessTime;
} finally {
lastAccessTimeLock.writeLock().unlock();
}
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
@Override
public boolean equals(Object obj) {
return value.equals(obj);
}
@Override
public int hashCode() {
return value.hashCode();
}
}
/**
* A Thread that monitors an {@link ExpiringMap} and will remove
* elements that have passed the threshold.
*
*/
public class Expirer implements Runnable {
private final ReadWriteLock stateLock = new ReentrantReadWriteLock();
private long timeToLiveMillis;
private long expirationIntervalMillis;
private boolean running = false;
private final Thread expirerThread;
/**
* Creates a new instance of Expirer.
*
*/
public Expirer() {
expirerThread = new Thread(this, "ExpiringMapExpirer-"
+ expirerCount++);
expirerThread.setDaemon(true);
}
public void run() {
while (running) {
processExpires();
try {
Thread.sleep(expirationIntervalMillis);
} catch (InterruptedException e) {
}
}
}
private void processExpires() {
long timeNow = System.currentTimeMillis();
for (ExpiringObject o : delegate.values()) {
if (timeToLiveMillis <= 0) {
continue;
}
long timeIdle = timeNow - o.getLastAccessTime();
if (timeIdle >= timeToLiveMillis) {
delegate.remove(o.getKey());
for (ExpirationListener<V> listener : expirationListeners) {
listener.expired(o.getValue());
}
}
}
}
/**
* Kick off this thread which will look for old objects and remove them.
*
*/
public void startExpiring() {
stateLock.writeLock().lock();
try {
if (!running) {
running = true;
expirerThread.start();
}
} finally {
stateLock.writeLock().unlock();
}
}
/**
* If this thread has not started, then start it.
* Otherwise just return;
*/
public void startExpiringIfNotStarted() {
stateLock.readLock().lock();
try {
if (running) {
return;
}
} finally {
stateLock.readLock().unlock();
}
stateLock.writeLock().lock();
try {
if (!running) {
running = true;
expirerThread.start();
}
} finally {
stateLock.writeLock().unlock();
}
}
/**
* Stop the thread from monitoring the map.
*/
public void stopExpiring() {
stateLock.writeLock().lock();
try {
if (running) {
running = false;
expirerThread.interrupt();
}
} finally {
stateLock.writeLock().unlock();
}
}
/**
* Checks to see if the thread is running
*
* @return
* If the thread is running, true. Otherwise false.
*/
public boolean isRunning() {
stateLock.readLock().lock();
try {
return running;
} finally {
stateLock.readLock().unlock();
}
}
/**
* Returns the Time-to-live value.
*
* @return
* The time-to-live (seconds)
*/
public int getTimeToLive() {
stateLock.readLock().lock();
try {
return (int) timeToLiveMillis / 1000;
} finally {
stateLock.readLock().unlock();
}
}
/**
* Update the value for the time-to-live
*
* @param timeToLive
* The time-to-live (seconds)
*/
public void setTimeToLive(long timeToLive) {
stateLock.writeLock().lock();
try {
this.timeToLiveMillis = timeToLive * 1000;
} finally {
stateLock.writeLock().unlock();
}
}
/**
* Get the interval in which an object will live in the map before
* it is removed.
*
* @return
* The time in seconds.
*/
public int getExpirationInterval() {
stateLock.readLock().lock();
try {
return (int) expirationIntervalMillis / 1000;
} finally {
stateLock.readLock().unlock();
}
}
/**
* Set the interval in which an object will live in the map before
* it is removed.
*
* @param expirationInterval
* The time in seconds
*/
public void setExpirationInterval(long expirationInterval) {
stateLock.writeLock().lock();
try {
this.expirationIntervalMillis = expirationInterval * 1000;
} finally {
stateLock.writeLock().unlock();
}
}
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
/**
* A listener for expired object events.
*
* @author The Apache MINA Project (dev@mina.apache.org)
* @version $Rev: 589932 $, $Date: 2007-10-30 02:50:39 +0100 (Tue, 30 Oct 2007) $
* TODO Make this a inner interface of ExpiringMap
*/
interface ExpirationListener<E> {
void expired(E expiredObject);
}
Hash map using String values as keys mapped to primitive int values.
/*
* Copyright (c) 2000-2005, Dennis M. Sosnoski. 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 of JiBX 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.
*/
/**
* Hash map using <code>String</code> values as keys mapped to primitive
* <code>int</code> values. This implementation is unsynchronized in order to
* provide the best possible performance for typical usage scenarios, so
* explicit synchronization must be implemented by a wrapper class or directly
* by the application in cases where instances are modified in a multithreaded
* environment. The map implementation is not very efficient when resizing, but
* works well when the size of the map is known in advance.
*
* @author Dennis M. Sosnoski
* @version 1.1
*/
public class StringIntHashMap
{
/** Default value returned when key not found in table. */
public static final int DEFAULT_NOT_FOUND = Integer.MIN_VALUE;
/** Default fill fraction allowed before growing table. */
protected static final double DEFAULT_FILL = 0.3d;
/** Minimum size used for hash table. */
protected static final int MINIMUM_SIZE = 31;
/** Fill fraction allowed for this hash table. */
protected final double m_fillFraction;
/** Number of entries present in table. */
protected int m_entryCount;
/** Entries allowed before growing table. */
protected int m_entryLimit;
/** Size of array used for keys. */
protected int m_arraySize;
/** Offset added (modulo table size) to slot number on collision. */
protected int m_hitOffset;
/** Array of key table slots. */
protected String[] m_keyTable;
/** Array of value table slots. */
protected int[] m_valueTable;
/** Value returned when key not found in table. */
protected int m_notFoundValue;
/**
* Constructor with full specification.
*
* @param count number of values to assume in initial sizing of table
* @param fill fraction full allowed for table before growing
* @param miss value returned when key not found in table
*/
public StringIntHashMap(int count, double fill, int miss) {
// check the passed in fill fraction
if (fill <= 0.0d || fill >= 1.0d) {
throw new IllegalArgumentException("fill value out of range");
}
m_fillFraction = fill;
// compute initial table size (ensuring odd)
m_arraySize = Math.max((int)(count / m_fillFraction), MINIMUM_SIZE);
m_arraySize += (m_arraySize + 1) % 2;
// initialize the table information
m_entryLimit = (int)(m_arraySize * m_fillFraction);
m_hitOffset = m_arraySize / 2;
m_keyTable = new String[m_arraySize];
m_valueTable = new int[m_arraySize];
m_notFoundValue = miss;
}
/**
* Constructor with size and fill fraction specified. Uses default hash
* technique and value returned when key not found in table.
*
* @param count number of values to assume in initial sizing of table
* @param fill fraction full allowed for table before growing
*/
public StringIntHashMap(int count, double fill) {
this(count, fill, DEFAULT_NOT_FOUND);
}
/**
* Constructor with only size supplied. Uses default hash technique and
* values for fill fraction and value returned when key not found in table.
*
* @param count number of values to assume in initial sizing of table
*/
public StringIntHashMap(int count) {
this(count, DEFAULT_FILL);
}
/**
* Default constructor.
*/
public StringIntHashMap() {
this(0, DEFAULT_FILL);
}
/**
* Copy (clone) constructor.
*
* @param base instance being copied
*/
public StringIntHashMap(StringIntHashMap base) {
// copy the basic occupancy information
m_fillFraction = base.m_fillFraction;
m_entryCount = base.m_entryCount;
m_entryLimit = base.m_entryLimit;
m_arraySize = base.m_arraySize;
m_hitOffset = base.m_hitOffset;
m_notFoundValue = base.m_notFoundValue;
// copy table of items
m_keyTable = new String[m_arraySize];
System.arraycopy(base.m_keyTable, 0, m_keyTable, 0, m_arraySize);
m_valueTable = new int[m_arraySize];
System.arraycopy(base.m_valueTable, 0, m_valueTable, 0, m_arraySize);
}
/**
* Step the slot number for an entry. Adds the collision offset (modulo
* the table size) to the slot number.
*
* @param slot slot number to be stepped
* @return stepped slot number
*/
private final int stepSlot(int slot) {
return (slot + m_hitOffset) % m_arraySize;
}
/**
* Find free slot number for entry. Starts at the slot based directly
* on the hashed key value. If this slot is already occupied, it adds
* the collision offset (modulo the table size) to the slot number and
* checks that slot, repeating until an unused slot is found.
*
* @param slot initial slot computed from key
* @return slot at which entry was added
*/
private final int freeSlot(int slot) {
while (m_keyTable[slot] != null) {
slot = stepSlot(slot);
}
return slot;
}
/**
* Standard base slot computation for a key.
*
* @param key key value to be computed
* @return base slot for key
*/
private final int standardSlot(Object key) {
return (key.hashCode() & Integer.MAX_VALUE) % m_arraySize;
}
/**
* Standard find key in table. This method may be used directly for key
* lookup using either the <code>hashCode()</code> method defined for the
* key objects or the <code>System.identityHashCode()</code> method, and
* either the <code>equals()</code> method defined for the key objects or
* the <code>==</code> operator, as selected by the hash technique
* constructor parameter. To implement a hash class based on some other
* methods of hashing and/or equality testing, define a separate method in
* the subclass with a different name and use that method instead. This
* avoids the overhead caused by overrides of a very heavily used method.
*
* @param key to be found in table
* @return index of matching key, or <code>-index-1</code> of slot to be
* used for inserting key in table if not already present (always negative)
*/
private int standardFind(Object key) {
// find the starting point for searching table
int slot = standardSlot(key);
// scan through table to find target key
while (m_keyTable[slot] != null) {
// check if we have a match on target key
if (m_keyTable[slot].equals(key)) {
return slot;
} else {
slot = stepSlot(slot);
}
}
return -slot-1;
}
/**
* Reinsert an entry into the hash map. This is used when the table is being
* directly modified, and does not adjust the count present or check the
* table capacity.
*
* @param slot position of entry to be reinserted into hash map
* @return <code>true</code> if the slot number used by the entry has has
* changed, <code>false</code> if not
*/
private boolean reinsert(int slot) {
String key = m_keyTable[slot];
m_keyTable[slot] = null;
return assignSlot(key, m_valueTable[slot]) != slot;
}
/**
* Internal remove pair from the table. Removes the pair from the table
* by setting the key entry to <code>null</code> and adjusting the count
* present, then chains through the table to reinsert any other pairs
* which may have collided with the removed pair. If the associated value
* is an object reference, it should be set to <code>null</code> before
* this method is called.
*
* @param slot index number of pair to be removed
*/
protected void internalRemove(int slot) {
// delete pair from table
m_keyTable[slot] = null;
m_entryCount--;
while (m_keyTable[(slot = stepSlot(slot))] != null) {
// reinsert current entry in table to fill holes
reinsert(slot);
}
}
/**
* Restructure the table. This is used when the table is increasing or
* decreasing in size, and works directly with the old table representation
* arrays. It inserts pairs from the old arrays directly into the table
* without adjusting the count present or checking the table size.
*
* @param keys array of keys
* @param values array of values
*/
private void restructure(String[] keys, int[] values) {
for (int i = 0; i < keys.length; i++) {
if (keys[i] != null) {
assignSlot(keys[i], values[i]);
}
}
}
/**
* Assign slot for entry. Starts at the slot found by the hashed key value.
* If this slot is already occupied, it steps the slot number and checks the
* resulting slot, repeating until an unused slot is found. This method does
* not check for duplicate keys, so it should only be used for internal
* reordering of the tables.
*
* @param key to be added to table
* @param value associated value for key
* @return slot at which entry was added
*/
private int assignSlot(String key, int value) {
int offset = freeSlot(standardSlot(key));
m_keyTable[offset] = key;
m_valueTable[offset] = value;
return offset;
}
/**
* Add an entry to the table. If the key is already present in the table,
* this replaces the existing value associated with the key.
*
* @param key key to be added to table (non- <code>null</code>)
* @param value associated value for key
* @return value previously associated with key, or reserved not found value
* if key not previously present in table
*/
public int add(String key, int value) {
// first validate the parameters
if (key == null) {
throw new IllegalArgumentException("null key not supported");
} else if (value == m_notFoundValue) {
throw new IllegalArgumentException(
"value matching not found return not supported");
} else {
// check space available
int min = m_entryCount + 1;
if (min > m_entryLimit) {
// find the array size required
int size = m_arraySize;
int limit = m_entryLimit;
while (limit < min) {
size = size * 2 + 1;
limit = (int) (size * m_fillFraction);
}
// set parameters for new array size
m_arraySize = size;
m_entryLimit = limit;
m_hitOffset = size / 2;
// restructure for larger arrays
String[] keys = m_keyTable;
m_keyTable = new String[m_arraySize];
int[] values = m_valueTable;
m_valueTable = new int[m_arraySize];
restructure(keys, values);
}
// find slot of table
int offset = standardFind(key);
if (offset >= 0) {
// replace existing value for key
int prior = m_valueTable[offset];
m_valueTable[offset] = value;
return prior;
} else {
// add new pair to table
m_entryCount++;
offset = -offset - 1;
m_keyTable[offset] = key;
m_valueTable[offset] = value;
return m_notFoundValue;
}
}
}
/**
* Check if an entry is present in the table. This method is supplied to
* support the use of values matching the reserved not found value.
*
* @param key key for entry to be found
* @return <code>true</code> if key found in table, <code>false</code>
* if not
*/
public final boolean containsKey(String key) {
return standardFind(key) >= 0;
}
/**
* Find an entry in the table.
*
* @param key key for entry to be returned
* @return value for key, or reserved not found value if key not found
*/
public final int get(String key) {
int slot = standardFind(key);
if (slot >= 0) {
return m_valueTable[slot];
} else {
return m_notFoundValue;
}
}
/**
* Remove an entry from the table. If multiple entries are present with the
* same key value, only the first one found will be removed.
*
* @param key key to be removed from table
* @return value associated with removed key, or reserved not found value if
* key not found in table
*/
public int remove(String key) {
int slot = standardFind(key);
if (slot >= 0) {
int value = m_valueTable[slot];
internalRemove(slot);
return value;
} else {
return m_notFoundValue;
}
}
/**
* Construct a copy of the table.
*
* @return shallow copy of table
*/
public Object clone() {
return new StringIntHashMap(this);
}
}
HashNMap stores multiple values by a single key value. Values can be retrieved using a direct query or by creating an enumeration over the stored elements.
/*
* JCommon : a free general purpose class library for the Java(tm) platform
*
*
* (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
*
* Project Info: http://www.jfree.org/jcommon/index.html
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
* [Java is a trademark or registered trademark of Sun Microsystems, Inc.
* in the United States and other countries.]
*
* -------------
* HashNMap.java
* -------------
* (C)opyright 2002-2005, by Thomas Morgner and Contributors.
*
* Original Author: Thomas Morgner;
* Contributor(s): David Gilbert (for Object Refinery Limited);
*
* $Id: HashNMap.java,v 1.7 2005/10/18 13:24:19 mungady Exp $
*
* Changes
* -------
* 20-May-2002 : Initial version
* 10-Dec-2002 : Minor Javadoc updates (DG);
* 29-Jul-2004 : Replaced "enum" variable name (reserved word in JDK 1.5) (DG);
* 12-Mar-2005 : Some performance improvements, this implementation is no
* longer forced to use ArrayLists, add/put behaviour changed to
* fit the common behaviour of collections.
*
*/
import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* The HashNMap can be used to store multiple values by a single key value. The
* values stored can be retrieved using a direct query or by creating an
* enumeration over the stored elements.
*
* @author Thomas Morgner
*/
public class HashNMap implements Serializable, Cloneable {
/** Serialization support. */
private static final long serialVersionUID = -670924844536074826L;
/**
* An helper class to implement an empty iterator. This iterator will always
* return false when <code>hasNext</code> is called.
*/
private static final class EmptyIterator implements Iterator {
/**
* DefaultConstructor.
*/
private EmptyIterator() {
super();
}
/**
* Returns <tt>true</tt> if the iteration has more elements. (In other
* words, returns <tt>true</tt> if <tt>next</tt> would return an element
* rather than throwing an exception.)
*
* @return <tt>true</tt> if the iterator has more elements.
*/
public boolean hasNext() {
return false;
}
/**
* Returns the next element in the iteration.
*
* @return the next element in the iteration.
* @throws NoSuchElementException
* iteration has no more elements.
*/
public Object next() {
throw new NoSuchElementException("This iterator is empty.");
}
/**
* Removes from the underlying collection the last element returned by the
* iterator (optional operation). This method can be called only once per
* call to <tt>next</tt>. The behavior of an iterator is unspecified if
* the underlying collection is modified while the iteration is in progress
* in any way other than by calling this method.
*
* @throws UnsupportedOperationException
* if the <tt>remove</tt> operation is not supported by this
* Iterator.
* @throws IllegalStateException
* if the <tt>next</tt> method has not yet been called, or the
* <tt>remove</tt> method has already been called after the last
* call to the <tt>next</tt> method.
*/
public void remove() {
throw new UnsupportedOperationException("This iterator is empty, no remove supported.");
}
}
/**
* A singleton instance of the empty iterator. This object can be safely
* shared.
*/
private static final Iterator EMPTY_ITERATOR = new EmptyIterator();
/**
* The underlying storage.
*/
private HashMap table;
/**
* An empty array.
*/
private static final Object[] EMPTY_ARRAY = new Object[0];
/**
* Default constructor.
*/
public HashNMap() {
this.table = new HashMap();
}
/**
* Returns a new empty list.
*
* @return A new empty list.
*/
protected List createList() {
return new ArrayList();
}
/**
* Inserts a new key/value pair into the map. If such a pair already exists,
* it gets replaced with the given values.
*
* @param key
* the key.
* @param val
* the value.
* @return A boolean.
*/
public boolean put(final Object key, final Object val) {
final List v = (List) this.table.get(key);
if (v == null) {
final List newList = createList();
newList.add(val);
this.table.put(key, newList);
return true;
} else {
v.clear();
return v.add(val);
}
}
/**
* Adds a new key/value pair into this map. If the key is not yet in the map,
* it gets added to the map and the call is equal to put(Object,Object).
*
* @param key
* the key.
* @param val
* the value.
* @return true, if the value has been added, false otherwise
*/
public boolean add(final Object key, final Object val) {
final List v = (List) this.table.get(key);
if (v == null) {
put(key, val);
return true;
} else {
return v.add(val);
}
}
/**
* Retrieves the first value registered for an key or null if there was no
* such key in the list.
*
* @param key
* the key.
* @return the value.
*/
public Object getFirst(final Object key) {
return get(key, 0);
}
/**
* Retrieves the n-th value registered for an key or null if there was no such
* key in the list. An index out of bounds exception is thrown if there are
* less than n elements registered to this key.
*
* @param key
* the key.
* @param n
* the index.
* @return the object.
*/
public Object get(final Object key, final int n) {
final List v = (List) this.table.get(key);
if (v == null) {
return null;
}
return v.get(n);
}
/**
* Returns an iterator over all elements registered to the given key.
*
* @param key
* the key.
* @return an iterator.
*/
public Iterator getAll(final Object key) {
final List v = (List) this.table.get(key);
if (v == null) {
return EMPTY_ITERATOR;
}
return v.iterator();
}
/**
* Returns all registered keys as an enumeration.
*
* @return an enumeration of the keys.
*/
public Iterator keys() {
return this.table.keySet().iterator();
}
/**
* Returns all registered keys as set.
*
* @return a set of keys.
*/
public Set keySet() {
return this.table.keySet();
}
/**
* Removes the key/value pair from the map. If the removed entry was the last
* entry for this key, the key gets also removed.
*
* @param key
* the key.
* @param value
* the value.
* @return true, if removing the element was successfull, false otherwise.
*/
public boolean remove(final Object key, final Object value) {
final List v = (List) this.table.get(key);
if (v == null) {
return false;
}
if (!v.remove(value)) {
return false;
}
if (v.size() == 0) {
this.table.remove(key);
}
return true;
}
/**
* Removes all elements for the given key.
*
* @param key
* the key.
*/
public void removeAll(final Object key) {
this.table.remove(key);
}
/**
* Clears all keys and values of this map.
*/
public void clear() {
this.table.clear();
}
/**
* Tests whether this map contains the given key.
*
* @param key
* the key.
* @return true if the key is contained in the map
*/
public boolean containsKey(final Object key) {
return this.table.containsKey(key);
}
/**
* Tests whether this map contains the given value.
*
* @param value
* the value.
* @return true if the value is registered in the map for an key.
*/
public boolean containsValue(final Object value) {
final Iterator e = this.table.values().iterator();
boolean found = false;
while (e.hasNext() && !found) {
final List v = (List) e.next();
found = v.contains(value);
}
return found;
}
/**
* Tests whether this map contains the given value.
*
* @param value
* the value.
* @param key
* the key under which to find the value
* @return true if the value is registered in the map for an key.
*/
public boolean containsValue(final Object key, final Object value) {
final List v = (List) this.table.get(key);
if (v == null) {
return false;
}
return v.contains(value);
}
/**
* Tests whether this map contains the given key or value.
*
* @param value
* the value.
* @return true if the key or value is contained in the map
*/
public boolean contains(final Object value) {
if (containsKey(value)) {
return true;
}
return containsValue(value);
}
/**
* Returns a clone of the specified object, if it can be cloned, otherwise
* throws a CloneNotSupportedException.
*
* @param object
* the object to clone (<code>null</code> not permitted).
* @return A clone of the specified object.
* @throws CloneNotSupportedException
* if the object cannot be cloned.
*/
public static Object clone(final Object object) throws CloneNotSupportedException {
if (object == null) {
throw new IllegalArgumentException("Null "object" argument.");
}
else {
try {
final Method method = object.getClass().getMethod("clone", (Class[]) null);
if (Modifier.isPublic(method.getModifiers())) {
return method.invoke(object, (Object[]) null);
}
} catch (Exception e) {
}
}
throw new CloneNotSupportedException("Failed to clone.");
}
/**
* Creates a deep copy of this HashNMap.
*
* @return a clone.
* @throws CloneNotSupportedException
* this should never happen.
*/
public Object clone() throws CloneNotSupportedException {
final HashNMap map = (HashNMap) super.clone();
map.table = new HashMap();
final Iterator iterator = keys();
while (iterator.hasNext()) {
final Object key = iterator.next();
final List list = (List) map.table.get(key);
if (list != null) {
map.table.put(key,clone(list));
}
}
return map;
}
/**
* Returns the contents for the given key as object array. If there were no
* objects registered with that key, an empty object array is returned.
*
* @param key
* the key.
* @param data
* the object array to receive the contents.
* @return the contents.
*/
public Object[] toArray(final Object key, final Object[] data) {
if (key == null) {
throw new NullPointerException("Key must not be null.");
}
final List list = (List) this.table.get(key);
if (list != null) {
return list.toArray(data);
}
if (data.length > 0) {
data[0] = null;
}
return data;
}
/**
* Returns the contents for the given key as object array. If there were no
* objects registered with that key, an empty object array is returned.
*
* @param key
* the key.
* @return the contents.
*/
public Object[] toArray(final Object key) {
if (key == null) {
throw new NullPointerException("Key must not be null.");
}
final List list = (List) this.table.get(key);
if (list != null) {
return list.toArray();
}
return EMPTY_ARRAY;
}
/**
* Returns the number of elements registered with the given key.
*
* @param key
* the key.
* @return the number of element for this key, or 0 if there are no elements
* registered.
*/
public int getValueCount(final Object key) {
if (key == null) {
throw new NullPointerException("Key must not be null.");
}
final List list = (List) this.table.get(key);
if (list != null) {
return list.size();
}
return 0;
}
}
Integer Map
/*
StatCvs - CVS statistics generation
Copyright (C) 2002 Lukasz Pekacki <lukasz@pekacki.de>
http://statcvs.sf.net/
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
$Name: $
Created on $Date: 2008/04/02 11:52:02 $
*/
import java.util.ArrayList;
import java.util.Collections;
import java.util.ruparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/**
* Utility class for storing a map from <code>Object</code>s to
* <code>int</code>s.
* This class makes it easy to sort by key or value, and provides
* useful features like {@link #sum()}, {@link #max()}, and
* percent calculation.
* <p>
* The keys must be comparable, for example <code>String</code>s.
* <p>
* Behaviour for <code>null</code> keys is unspecified.
*
* @author Richard Cyganiak
* @version $Id: IntegerMap.java,v 1.16 2008/04/02 11:52:02 benoitx Exp $
*/
public class IntegerMap {
private final Map map = new TreeMap();
private final Comparator comparator = new SortByValueComparator(map);
private int sum = 0;
private int max = 0;
/**
* Puts a value into the map, overwriting any previous value
* for the same key.
*
* @param key an <code>Object</code> which is used as key.
* @param value the <code>int</code> value to be stored at this key.
*/
public void put(final Object key, final int value) {
max = Math.max(max, value);
sum -= get(key);
sum += value;
map.put(key, new Integer(value));
}
/**
* Gets a value from the map. Returns the value which was
* stored in the map at the same key before. If no value was
* stored for this key, 0 will be returned.
*
* @param key an <code>Object</code> which is used as key.
* @return the value for this key
*/
public int get(final Object key) {
final Integer result = (Integer) map.get(key);
if (result == null) {
return 0;
}
return result.intValue();
}
/**
* Same as {@link #get(Object)}, but returns an <code>Integer</code>,
* not an <code>int</code>.
*
* @param key the key to get the value for
* @return the value wrapped in an <code>Integer</code> object
*/
public Integer getInteger(final Object key) {
return (Integer) map.get(key);
}
/**
* Gets the value stored at a key as a percentage of all values
* in the map.
*
* @param key the key to get the value for
* @return the value as a percentage of the sum of all values
*/
public double getPercent(final Object key) {
return (double) get(key) * 100 / sum;
}
/**
* Gets the value stored at a key as a percentage of the maximum
* value in the map. For the maximum value, this will return
* 100.0. For a value half as large as the maximum value, this
* will return 50.0.
*
* @param key the key to get the value for
* @return the value as a percentage of largest value in the map
*/
public double getPercentOfMaximum(final Object key) {
return get(key) * 100 / max;
}
/**
* Adds an <code>int</code> to the value stored at a key.
* If no value was stored before at this key, the <code>int</code>
* will be stored there.
*
* @param key the key to whose value <code>addValue</code> should be added
* @param addValue the <code>int</code> to be added
*/
public void addInt(final Object key, final int addValue) {
put(key, addValue + get(key));
}
/**
* Same as <code>addInt(key, 1)</code>
*
* @param key the key whose value should be increased
*/
public void inc(final Object key) {
addInt(key, 1);
}
/**
* Same as <code>addInt(key, -1)</code>
*
* @param key the key whose value should be decreased
*/
public void dec(final Object key) {
addInt(key, -1);
}
/**
* Deletes a value from the map. This is different from
* <code>put(key, 0)</code>. Removing will reduce
* the size of the map, putting 0 will not.
*
* @param key the key that should be removed
*/
public void remove(final Object key) {
sum -= get(key);
map.remove(key);
}
/**
* Returns <code>true</code> if the map contains a value
* for this key.
*
* @param key the key to check for
* @return <code>true</code> if the key is in the map
*/
public boolean contains(final Object key) {
return map.containsKey(key);
}
/**
* Returns the number of key-value pairs stored in the map.
*
* @return the number of key-value pairs stored in the map
*/
public int size() {
return map.size();
}
/**
* Returns a set view of the keys. The set will be in
* ascending key order.
*
* @return a <code>Set</code> view of all keys
*/
public Set keySet() {
return map.keySet();
}
/**
* Returns an iterator on the keys, sorted by key ascending.
*
* @return an iterator on the keys
*/
public Iterator iteratorSortedByKey() {
return map.keySet().iterator();
}
/**
* Returns an iterator on the keys, sorted by values ascending.
*
* @return an iterator on the keys
*/
public Iterator iteratorSortedByValue() {
final List keys = new ArrayList(map.keySet());
Collections.sort(keys, comparator);
return keys.iterator();
}
/**
* Returns an iterator on the keys, sorted by values descending.
*
* @return an iterator on the keys
*/
public Iterator iteratorSortedByValueReverse() {
final List keys = new ArrayList(map.keySet());
Collections.sort(keys, comparator);
Collections.reverse(keys);
return keys.iterator();
}
/**
* Returns the sum of all values in the map.
*
* @return the sum of all values in the map
*/
public int sum() {
return sum;
}
/**
* Returns the average of all values in the map.
*
* @return the average of all values in the map
*/
public double average() {
return (double) sum() / size();
}
/**
* Returns the maximum value in the map.
*
* @return the maximum value in the map.
*/
public int max() {
return max;
}
/**
* Private utility class for comparing of map entries by value.
*/
private static class SortByValueComparator implements Comparator {
private final Map mapToBeSorted;
public SortByValueComparator(final Map map) {
this.mapToBeSorted = map;
}
public int compare(final Object o1, final Object o2) {
final int i1 = ((Integer) this.mapToBeSorted.get(o1)).intValue();
final int i2 = ((Integer) this.mapToBeSorted.get(o2)).intValue();
if (i1 < i2) {
return -1;
} else if (i1 > i2) {
return 1;
}
return 0;
}
}
}
Int HashMap
/*
* $Id: ArrayListStack.java 4448 2006-02-14 20:54:57Z jonathanlocke $ $Revision:
* 4448 $ $Date: 2006-02-14 21:54:57 +0100 (di, 14 feb 2006) $
*
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
import java.io.IOException;
import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* This is a int hashmap that has the exact same features and interface as a
* normal Map except that the key is directly an integer. So no hash is
* calculated or key object is stored.
*
* @author jcompagner
*/
public class IntHashMap implements Cloneable, Serializable
{
transient volatile Set keySet = null;
transient volatile Collection values = null;
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 16;
/**
* The maximum capacity, used if a higher value is implicitly specified by
* either of the constructors with arguments. MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* The table, resized as necessary. Length MUST Always be a power of two.
*/
transient Entry[] table;
/**
* The number of key-value mappings contained in this identity hash map.
*/
transient int size;
/**
* The next size value at which to resize (capacity * load factor).
*
* @serial
*/
int threshold;
/**
* The load factor for the hash table.
*
* @serial
*/
final float loadFactor;
/**
* The number of times this HashMap has been structurally modified
* Structural modifications are those that change the number of mappings in
* the HashMap or otherwise modify its internal structure (e.g., rehash).
* This field is used to make iterators on Collection-views of the HashMap
* fail-fast. (See ConcurrentModificationException).
*/
transient volatile int modCount;
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and load factor.
*
* @param initialCapacity
* The initial capacity.
* @param loadFactor
* The load factor.
* @throws IllegalArgumentException
* if the initial capacity is negative or the load factor is
* nonpositive.
*/
public IntHashMap(int initialCapacity, float loadFactor)
{
if (initialCapacity < 0)
{
throw new IllegalArgumentException("Illegal initial capacity: " + //$NON-NLS-1$
initialCapacity);
}
if (initialCapacity > MAXIMUM_CAPACITY)
{
initialCapacity = MAXIMUM_CAPACITY;
}
if (loadFactor <= 0 || Float.isNaN(loadFactor))
{
throw new IllegalArgumentException("Illegal load factor: " + //$NON-NLS-1$
loadFactor);
}
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
{
capacity <<= 1;
}
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
table = new Entry[capacity];
init();
}
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and the default load factor (0.75).
*
* @param initialCapacity
* the initial capacity.
* @throws IllegalArgumentException
* if the initial capacity is negative.
*/
public IntHashMap(int initialCapacity)
{
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
*/
public IntHashMap()
{
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}
// internal utilities
/**
* Initialization hook for subclasses. This method is called in all
* constructors and pseudo-constructors (clone, readObject) after HashMap
* has been initialized but before any entries have been inserted. (In the
* absence of this method, readObject would require explicit knowledge of
* subclasses.)
*/
void init()
{
}
/**
* Returns index for hash code h.
*
* @param h
* @param length
* @return The index for the hash integer for the given length
*/
static int indexFor(int h, int length)
{
return h & (length - 1);
}
/**
* Returns the number of key-value mappings in this map.
*
* @return the number of key-value mappings in this map.
*/
public int size()
{
return size;
}
/**
* Returns <tt>true</tt> if this map contains no key-value mappings.
*
* @return <tt>true</tt> if this map contains no key-value mappings.
*/
public boolean isEmpty()
{
return size == 0;
}
/**
* Returns the value to which the specified key is mapped in this identity
* hash map, or <tt>null</tt> if the map contains no mapping for this key.
* A return value of <tt>null</tt> does not <i>necessarily</i> indicate
* that the map contains no mapping for the key; it is also possible that
* the map explicitly maps the key to <tt>null</tt>. The
* <tt>containsKey</tt> method may be used to distinguish these two cases.
*
* @param key
* the key whose associated value is to be returned.
* @return the value to which this map maps the specified key, or
* <tt>null</tt> if the map contains no mapping for this key.
* @see #put(int, Object)
*/
public Object get(int key)
{
int i = indexFor(key, table.length);
Entry e = table[i];
while (true)
{
if (e == null)
{
return e;
}
if (key == e.key)
{
return e.value;
}
e = e.next;
}
}
/**
* Returns <tt>true</tt> if this map contains a mapping for the specified
* key.
*
* @param key
* The key whose presence in this map is to be tested
* @return <tt>true</tt> if this map contains a mapping for the specified
* key.
*/
public boolean containsKey(int key)
{
int i = indexFor(key, table.length);
Entry e = table[i];
while (e != null)
{
if (key == e.key)
{
return true;
}
e = e.next;
}
return false;
}
/**
* Returns the entry associated with the specified key in the HashMap.
* Returns null if the HashMap contains no mapping for this key.
*
* @param key
* @return The Entry object for the given hash key
*/
Entry getEntry(int key)
{
int i = indexFor(key, table.length);
Entry e = table[i];
while (e != null && !(key == e.key))
{
e = e.next;
}
return e;
}
/**
* Associates the specified value with the specified key in this map. If the
* map previously contained a mapping for this key, the old value is
* replaced.
*
* @param key
* key with which the specified value is to be associated.
* @param value
* value to be associated with the specified key.
* @return previous value associated with specified key, or <tt>null</tt>
* if there was no mapping for key. A <tt>null</tt> return can
* also indicate that the HashMap previously associated
* <tt>null</tt> with the specified key.
*/
public Object put(int key, Object value)
{
int i = indexFor(key, table.length);
for (Entry e = table[i]; e != null; e = e.next)
{
if (key == e.key)
{
Object oldValue = e.value;
e.value = value;
return oldValue;
}
}
modCount++;
addEntry(key, value, i);
return null;
}
/**
* This method is used instead of put by constructors and pseudoconstructors
* (clone, readObject). It does not resize the table, check for
* comodification, etc. It calls createEntry rather than addEntry.
*
* @param key
* @param value
*/
private void putForCreate(int key, Object value)
{
int i = indexFor(key, table.length);
/**
* Look for preexisting entry for key. This will never happen for clone
* or deserialize. It will only happen for construction if the input Map
* is a sorted map whose ordering is inconsistent w/ equals.
*/
for (Entry e = table[i]; e != null; e = e.next)
{
if (key == e.key)
{
e.value = value;
return;
}
}
createEntry(key, value, i);
}
void putAllForCreate(IntHashMap m)
{
for (Iterator i = m.entrySet().iterator(); i.hasNext();)
{
Entry e = (Entry)i.next();
putForCreate(e.getKey(), e.getValue());
}
}
/**
* Rehashes the contents of this map into a new array with a larger
* capacity. This method is called automatically when the number of keys in
* this map reaches its threshold.
*
* If current capacity is MAXIMUM_CAPACITY, this method does not resize the
* map, but but sets threshold to Integer.MAX_VALUE. This has the effect of
* preventing future calls.
*
* @param newCapacity
* the new capacity, MUST be a power of two; must be greater than
* current capacity unless current capacity is MAXIMUM_CAPACITY
* (in which case value is irrelevant).
*/
void resize(int newCapacity)
{
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY)
{
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
/**
* Transfer all entries from current table to newTable.
*
* @param newTable
*/
void transfer(Entry[] newTable)
{
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++)
{
Entry e = src[j];
if (e != null)
{
src[j] = null;
do
{
Entry next = e.next;
int i = indexFor(e.key, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
while (e != null);
}
}
}
/**
* Copies all of the mappings from the specified map to this map These
* mappings will replace any mappings that this map had for any of the keys
* currently in the specified map.
*
* @param m
* mappings to be stored in this map.
* @throws NullPointerException
* if the specified map is null.
*/
public void putAll(IntHashMap m)
{
int numKeysToBeAdded = m.size();
if (numKeysToBeAdded == 0)
{
return;
}
/*
* Expand the map if the map if the number of mappings to be added is
* greater than or equal to threshold. This is conservative; the obvious
* condition is (m.size() + size) >= threshold, but this condition could
* result in a map with twice the appropriate capacity, if the keys to
* be added overlap with the keys already in this map. By using the
* conservative calculation, we subject ourself to at most one extra
* resize.
*/
if (numKeysToBeAdded > threshold)
{
int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);
if (targetCapacity > MAXIMUM_CAPACITY)
{
targetCapacity = MAXIMUM_CAPACITY;
}
int newCapacity = table.length;
while (newCapacity < targetCapacity)
{
newCapacity <<= 1;
}
if (newCapacity > table.length)
{
resize(newCapacity);
}
}
for (Iterator i = m.entrySet().iterator(); i.hasNext();)
{
Entry e = (Entry)i.next();
put(e.getKey(), e.getValue());
}
}
/**
* Removes the mapping for this key from this map if present.
*
* @param key
* key whose mapping is to be removed from the map.
* @return previous value associated with specified key, or <tt>null</tt>
* if there was no mapping for key. A <tt>null</tt> return can
* also indicate that the map previously associated <tt>null</tt>
* with the specified key.
*/
public Object remove(int key)
{
Entry e = removeEntryForKey(key);
return (e == null ? e : e.value);
}
/**
* Removes and returns the entry associated with the specified key in the
* HashMap. Returns null if the HashMap contains no mapping for this key.
*
* @param key
* @return The Entry object that was removed
*/
Entry removeEntryForKey(int key)
{
int i = indexFor(key, table.length);
Entry prev = table[i];
Entry e = prev;
while (e != null)
{
Entry next = e.next;
if (key == e.key)
{
modCount++;
size--;
if (prev == e)
{
table[i] = next;
}
else
{
prev.next = next;
}
return e;
}
prev = e;
e = next;
}
return e;
}
/**
* Special version of remove for EntrySet.
*
* @param o
* @return The entry that was removed
*/
Entry removeMapping(Object o)
{
if (!(o instanceof Entry))
{
return null;
}
Entry entry = (Entry)o;
int key = entry.getKey();
int i = indexFor(key, table.length);
Entry prev = table[i];
Entry e = prev;
while (e != null)
{
Entry next = e.next;
if (e.key == key && e.equals(entry))
{
modCount++;
size--;
if (prev == e)
{
table[i] = next;
}
else
{
prev.next = next;
}
return e;
}
prev = e;
e = next;
}
return e;
}
/**
* Removes all mappings from this map.
*/
public void clear()
{
modCount++;
Entry tab[] = table;
for (int i = 0; i < tab.length; i++)
{
tab[i] = null;
}
size = 0;
}
/**
* Returns <tt>true</tt> if this map maps one or more keys to the
* specified value.
*
* @param value
* value whose presence in this map is to be tested.
* @return <tt>true</tt> if this map maps one or more keys to the
* specified value.
*/
public boolean containsValue(Object value)
{
if (value == null)
{
return containsNullValue();
}
Entry tab[] = table;
for (int i = 0; i < tab.length; i++)
{
for (Entry e = tab[i]; e != null; e = e.next)
{
if (value.equals(e.value))
{
return true;
}
}
}
return false;
}
/**
* Special-case code for containsValue with null argument
*
* @return boolean true if there is a null value in this map
*/
private boolean containsNullValue()
{
Entry tab[] = table;
for (int i = 0; i < tab.length; i++)
{
for (Entry e = tab[i]; e != null; e = e.next)
{
if (e.value == null)
{
return true;
}
}
}
return false;
}
/**
* Returns a shallow copy of this <tt>HashMap</tt> instance: the keys and
* values themselves are not cloned.
*
* @return a shallow copy of this map.
*/
public Object clone() throws CloneNotSupportedException
{
IntHashMap result = null;
try
{
result = (IntHashMap)super.clone();
result.table = new Entry[table.length];
result.entrySet = null;
result.modCount = 0;
result.size = 0;
result.init();
result.putAllForCreate(this);
}
catch (CloneNotSupportedException e)
{
// assert false;
}
return result;
}
/**
* @author jcompagner
*/
public static class Entry
{
final int key;
Object value;
Entry next;
/**
* Create new entry.
*
* @param k
* @param v
* @param n
*/
Entry(int k, Object v, Entry n)
{
value = v;
next = n;
key = k;
}
/**
* @return The int key of this entry
*/
public int getKey()
{
return key;
}
/**
* @return Gets the value object of this entry
*/
public Object getValue()
{
return value;
}
/**
* @param newValue
* @return The previous value
*/
public Object setValue(Object newValue)
{
Object oldValue = value;
value = newValue;
return oldValue;
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object o)
{
if (!(o instanceof Entry))
{
return false;
}
Entry e = (Entry)o;
int k1 = getKey();
int k2 = e.getKey();
if (k1 == k2)
{
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
{
return true;
}
}
return false;
}
/**
* @see java.lang.Object#hashCode()
*/
public int hashCode()
{
return key ^ (value == null ? 0 : value.hashCode());
}
/**
* @see java.lang.Object#toString()
*/
public String toString()
{
return getKey() + "=" + getValue(); //$NON-NLS-1$
}
}
/**
* Add a new entry with the specified key, value and hash code to the
* specified bucket. It is the responsibility of this method to resize the
* table if appropriate.
*
* Subclass overrides this to alter the behavior of put method.
*
* @param key
* @param value
* @param bucketIndex
*/
void addEntry(int key, Object value, int bucketIndex)
{
table[bucketIndex] = new Entry(key, value, table[bucketIndex]);
if (size++ >= threshold)
{
resize(2 * table.length);
}
}
/**
* Like addEntry except that this version is used when creating entries as
* part of Map construction or "pseudo-construction" (cloning,
* deserialization). This version needn"t worry about resizing the table.
*
* Subclass overrides this to alter the behavior of HashMap(Map), clone, and
* readObject.
*
* @param key
* @param value
* @param bucketIndex
*/
void createEntry(int key, Object value, int bucketIndex)
{
table[bucketIndex] = new Entry(key, value, table[bucketIndex]);
size++;
}
private abstract class HashIterator implements Iterator
{
Entry next; // next entry to return
int expectedModCount; // For fast-fail
int index; // current slot
Entry current; // current entry
HashIterator()
{
expectedModCount = modCount;
Entry[] t = table;
int i = t.length;
Entry n = null;
if (size != 0)
{ // advance to first entry
while (i > 0 && (n = t[--i]) == null)
{
/* NoOp*/ ;
}
}
next = n;
index = i;
}
/**
* @see java.util.Iterator#hasNext()
*/
public boolean hasNext()
{
return next != null;
}
Entry nextEntry()
{
if (modCount != expectedModCount)
{
throw new ConcurrentModificationException();
}
Entry e = next;
if (e == null)
{
throw new NoSuchElementException();
}
Entry n = e.next;
Entry[] t = table;
int i = index;
while (n == null && i > 0)
{
n = t[--i];
}
index = i;
next = n;
return current = e;
}
/**
* @see java.util.Iterator#remove()
*/
public void remove()
{
if (current == null)
{
throw new IllegalStateException();
}
if (modCount != expectedModCount)
{
throw new ConcurrentModificationException();
}
int k = current.key;
current = null;
IntHashMap.this.removeEntryForKey(k);
expectedModCount = modCount;
}
}
private class ValueIterator extends HashIterator
{
/**
* @see java.util.Iterator#next()
*/
public Object next()
{
return nextEntry().value;
}
}
private class KeyIterator extends HashIterator
{
/**
* @see java.util.Iterator#next()
*/
public Object next()
{
return new Integer(nextEntry().getKey());
}
}
private class EntryIterator extends HashIterator
{
/**
* @see java.util.Iterator#next()
*/
public Object next()
{
return nextEntry();
}
}
// Subclass overrides these to alter behavior of views" iterator() method
Iterator newKeyIterator()
{
return new KeyIterator();
}
Iterator newValueIterator()
{
return new ValueIterator();
}
Iterator newEntryIterator()
{
return new EntryIterator();
}
// Views
private transient Set entrySet = null;
/**
* Returns a set view of the keys contained in this map. The set is backed
* by the map, so changes to the map are reflected in the set, and
* vice-versa. The set supports element removal, which removes the
* corresponding mapping from this map, via the <tt>Iterator.remove</tt>,
* <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt>, and
* <tt>clear</tt> operations. It does not support the <tt>add</tt> or
* <tt>addAll</tt> operations.
*
* @return a set view of the keys contained in this map.
*/
public Set keySet()
{
Set ks = keySet;
return (ks != null ? ks : (keySet = new KeySet()));
}
private class KeySet extends AbstractSet
{
/**
* @see java.util.AbstractCollection#iterator()
*/
public Iterator iterator()
{
return newKeyIterator();
}
/**
* @see java.util.AbstractCollection#size()
*/
public int size()
{
return size;
}
/**
* @see java.util.AbstractCollection#contains(java.lang.Object)
*/
public boolean contains(Object o)
{
if (o instanceof Number)
{
return containsKey(((Number)o).intValue());
}
return false;
}
/**
* @see java.util.AbstractCollection#remove(java.lang.Object)
*/
public boolean remove(Object o)
{
if (o instanceof Number)
{
return IntHashMap.this.removeEntryForKey(((Number)o).intValue()) != null;
}
return false;
}
/**
* @see java.util.AbstractCollection#clear()
*/
public void clear()
{
IntHashMap.this.clear();
}
}
/**
* Returns a collection view of the values contained in this map. The
* collection is backed by the map, so changes to the map are reflected in
* the collection, and vice-versa. The collection supports element removal,
* which removes the corresponding mapping from this map, via the
* <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
* <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt>
* operations. It does not support the <tt>add</tt> or <tt>addAll</tt>
* operations.
*
* @return a collection view of the values contained in this map.
*/
public Collection values()
{
Collection vs = values;
return (vs != null ? vs : (values = new Values()));
}
private class Values extends AbstractCollection
{
/**
* @see java.util.AbstractCollection#iterator()
*/
public Iterator iterator()
{
return newValueIterator();
}
/**
* @see java.util.AbstractCollection#size()
*/
public int size()
{
return size;
}
/**
* @see java.util.AbstractCollection#contains(java.lang.Object)
*/
public boolean contains(Object o)
{
return containsValue(o);
}
/**
* @see java.util.AbstractCollection#clear()
*/
public void clear()
{
IntHashMap.this.clear();
}
}
/**
* Returns a collection view of the mappings contained in this map. Each
* element in the returned collection is a <tt>Map.Entry</tt>. The
* collection is backed by the map, so changes to the map are reflected in
* the collection, and vice-versa. The collection supports element removal,
* which removes the corresponding mapping from the map, via the
* <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
* <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt>
* operations. It does not support the <tt>add</tt> or <tt>addAll</tt>
* operations.
*
* @return a collection view of the mappings contained in this map.
* @see Map.Entry
*/
public Set entrySet()
{
Set es = entrySet;
return (es != null ? es : (entrySet = new EntrySet()));
}
private class EntrySet extends AbstractSet
{
/**
* @see java.util.AbstractCollection#iterator()
*/
public Iterator iterator()
{
return newEntryIterator();
}
/**
* @see java.util.AbstractCollection#contains(java.lang.Object)
*/
public boolean contains(Object o)
{
if (!(o instanceof Entry))
{
return false;
}
Entry e = (Entry)o;
Entry candidate = getEntry(e.getKey());
return candidate != null && candidate.equals(e);
}
/**
* @see java.util.AbstractCollection#remove(java.lang.Object)
*/
public boolean remove(Object o)
{
return removeMapping(o) != null;
}
/**
* @see java.util.AbstractCollection#size()
*/
public int size()
{
return size;
}
/**
* @see java.util.AbstractCollection#clear()
*/
public void clear()
{
IntHashMap.this.clear();
}
}
/**
* Save the state of the <tt>HashMap</tt> instance to a stream (i.e.,
* serialize it).
*
* @param s
* The ObjectOutputStream
* @throws IOException
*
* @serialData The <i>capacity</i> of the HashMap (the length of the bucket
* array) is emitted (int), followed by the <i>size</i> of the
* HashMap (the number of key-value mappings), followed by the
* key (Object) and value (Object) for each key-value mapping
* represented by the HashMap The key-value mappings are emitted
* in the order that they are returned by
* <tt>entrySet().iterator()</tt>.
*
*/
private void writeObject(java.io.ObjectOutputStream s) throws IOException
{
// Write out the threshold, loadfactor, and any hidden stuff
s.defaultWriteObject();
// Write out number of buckets
s.writeInt(table.length);
// Write out size (number of Mappings)
s.writeInt(size);
// Write out keys and values (alternating)
for (Iterator i = entrySet().iterator(); i.hasNext();)
{
Entry e = (Entry)i.next();
s.writeInt(e.getKey());
s.writeObject(e.getValue());
}
}
private static final long serialVersionUID = 362498820763181265L;
/**
* Reconstitute the <tt>HashMap</tt> instance from a stream (i.e.,
* deserialize it).
*
* @param s
* @throws IOException
* @throws ClassNotFoundException
*/
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException
{
// Read in the threshold, loadfactor, and any hidden stuff
s.defaultReadObject();
// Read in number of buckets and allocate the bucket array;
int numBuckets = s.readInt();
table = new Entry[numBuckets];
init(); // Give subclass a chance to do its thing.
// Read in size (number of Mappings)
int size = s.readInt();
// Read the keys and values, and put the mappings in the HashMap
for (int i = 0; i < size; i++)
{
int key = s.readInt();
Object value = s.readObject();
putForCreate(key, value);
}
}
// These methods are used when serializing HashSets
int capacity()
{
return table.length;
}
float loadFactor()
{
return loadFactor;
}
}
Int HashMap from jodd.org
// Copyright (c) 2003-2009, Jodd Team (jodd.org). All Rights Reserved.
import java.io.IOException;
import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* A Map that accepts int or Integer keys only. The implementation is based on
* <code>java.util.HashMap</code>. IntHashMap is about 25% faster.
*
* @see java.util.HashMap
*/
public class IntHashMap extends AbstractMap implements Cloneable, Serializable {
/**
* The hash table data.
*/
private transient Entry table[];
/**
* The total number of mappings in the hash table.
*/
private transient int count;
/**
* The table is rehashed when its size exceeds this threshold. (The value of
* this field is (int)(capacity * loadFactor).)
*/
private int threshold;
/**
* The load factor for the hashtable.
*/
private float loadFactor;
/**
* The number of times this IntHashMap has been structurally modified
* Structural modifications are those that change the number of mappings in
* the IntHashMap or otherwise modify its internal structure (e.g., rehash).
* This field is used to make iterators on Collection-views of the IntHashMap
* fail-fast.
*/
private transient int modCount;
/**
* Constructs a new, empty map with the specified initial
* capacity and the specified load factor.
*
* @param initialCapacity
* the initial capacity of the IntHashMap.
* @param loadFactor the load factor of the IntHashMap
*
* @throws IllegalArgumentException
* if the initial capacity is less
* than zero, or if the load factor is non-positive.
*/
public IntHashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0) {
throw new IllegalArgumentException("Illegal Initial Capacity: "+ initialCapacity);
}
if (loadFactor <= 0) {
throw new IllegalArgumentException("Illegal Load factor: "+ loadFactor);
}
if (initialCapacity == 0) {
initialCapacity = 1;
}
this.loadFactor = loadFactor;
table = new Entry[initialCapacity];
threshold = (int)(initialCapacity * loadFactor);
}
/**
* Constructs a new, empty map with the specified initial capacity
* and default load factor, which is 0.75.
*
* @param initialCapacity
* the initial capacity of the IntHashMap.
*
* @throws IllegalArgumentException
* if the initial capacity is less
* than zero.
*/
public IntHashMap(int initialCapacity) {
this(initialCapacity, 0.75f);
}
/**
* Constructs a new, empty map with a default capacity and load
* factor, which is 0.75.
*/
public IntHashMap() {
this(101, 0.75f);
}
/**
* Constructs a new map with the same mappings as the given map. The
* map is created with a capacity of twice the number of mappings in
* the given map or 11 (whichever is greater), and a default load factor,
* which is 0.75.
*/
public IntHashMap(Map t) {
this(Math.max(2 * t.size(), 11), 0.75f);
putAll(t);
}
/**
* Returns the number of key-value mappings in this map.
*
* @return the number of key-value mappings in this map.
*/
@Override
public int size() {
return count;
}
/**
* Returns <code>true</code> if this map contains no key-value mappings.
*
* @return <code>true</code> if this map contains no key-value mappings.
*/
@Override
public boolean isEmpty() {
return count == 0;
}
/**
* Returns <code>true</code> if this map maps one or more keys to the
* specified value.
*
* @param value value whose presence in this map is to be tested.
*
* @return <code>true</code> if this map maps one or more keys to the
* specified value.
*/
@Override
public boolean containsValue(Object value) {
Entry tab[] = table;
if (value == null) {
for (int i = tab.length; i-- > 0 ;) {
for (Entry e = tab[i] ; e != null ; e = e.next) {
if (e.value == null) {
return true;
}
}
}
} else {
for (int i = tab.length; i-- > 0 ;) {
for (Entry e = tab[i]; e != null; e = e.next) {
if (value.equals(e.value)) {
return true;
}
}
}
}
return false;
}
/**
* Returns <code>true</code> if this map contains a mapping for the specified
* key.
*
* @param key key whose presence in this Map is to be tested.
*
* @return <code>true</code> if this map contains a mapping for the specified
* key.
*/
@Override
public boolean containsKey(Object key) {
if (key instanceof Number) {
return containsKey( ((Number)key).intValue() );
} else {
return false;
}
}
/**
* Returns <code>true</code> if this map contains a mapping for the specified
* key.
*
* @param key key whose presence in this Map is to be tested.
*
* @return <code>true</code> if this map contains a mapping for the specified
* key.
*/
public boolean containsKey(int key) {
Entry tab[] = table;
int index = (key & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index]; e != null; e = e.next) {
if (e.key == key) {
return true;
}
}
return false;
}
/**
* Returns the value to which this map maps the specified key. Returns
* <code>null</code> if the map contains no mapping for this key. A return
* value of <code>null</code> does not <i>necessarily</i> indicate that the
* map contains no mapping for the key; it"s also possible that the map
* explicitly maps the key to <code>null</code>. The <code>containsKey</code>
* operation may be used to distinguish these two cases.
*
* @param key key whose associated value is to be returned.
*
* @return the value to which this map maps the specified key.
*/
@Override
public Object get(Object key) {
if (key instanceof Number) {
return get( ((Number)key).intValue() );
} else {
return null;
}
}
/**
* Returns the value to which this map maps the specified key. Returns
* <code>null</code> if the map contains no mapping for this key. A return
* value of <code>null</code> does not <i>necessarily</i> indicate that the
* map contains no mapping for the key; it"s also possible that the map
* explicitly maps the key to <code>null</code>. The <code>containsKey</code>
* operation may be used to distinguish these two cases.
*
* @param key key whose associated value is to be returned.
*
* @return the value to which this map maps the specified key.
*/
public Object get(int key) {
Entry tab[] = table;
int index = (key & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index]; e != null; e = e.next) {
if (e.key == key) {
return e.value;
}
}
return null;
}
/**
* Rehashes the contents of this map into a new <code>IntHashMap</code>
* instance with a larger capacity. This method is called automatically when
* the number of keys in this map exceeds its capacity and load factor.
*/
private void rehash() {
int oldCapacity = table.length;
Entry oldMap[] = table;
int newCapacity = (oldCapacity << 1) + 1;
Entry newMap[] = new Entry[newCapacity];
modCount++;
threshold = (int)(newCapacity * loadFactor);
table = newMap;
for (int i = oldCapacity ; i-- > 0 ;) {
for (Entry old = oldMap[i] ; old != null ; ) {
Entry e = old;
old = old.next;
int index = (e.key & 0x7FFFFFFF) % newCapacity;
e.next = newMap[index];
newMap[index] = e;
}
}
}
/**
* Associates the specified value with the specified key in this map. If the
* map previously contained a mapping for this key, the old value is
* replaced.
*
* @param key key with which the specified value is to be associated.
* @param value value to be associated with the specified key.
*
* @return previous value associated with specified key, or <code>null</code> if
* there was no mapping for key. A <code>null</code> return can also indicate
* that the IntHashMap previously associated <code>null</code> with the
* specified key.
*/
@Override
public Object put(Object key, Object value) {
if (key instanceof Number) {
return put( ((Number)key).intValue(), value );
} else {
throw new UnsupportedOperationException
("IntHashMap key must be a number");
}
}
/**
* Associates the specified value with the specified key in this map. If the
* map previously contained a mapping for this key, the old value is
* replaced.
*
* @param key key with which the specified value is to be associated.
* @param value value to be associated with the specified key.
*
* @return previous value associated with specified key, or <code>null</code> if
* there was no mapping for key. A <code>null</code> return can also indicate
* that the IntHashMap previously associated <code>null</code> with the
* specified key.
*/
public Object put(int key, Object value) {
// makes sure the key is not already in the IntHashMap.
Entry tab[] = table;
int index = (key & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index] ; e != null ; e = e.next) {
if (e.key == key) {
Object old = e.value;
e.value = value;
return old;
}
}
modCount++;
if (count >= threshold) {
// rehash the table if the threshold is exceeded
rehash();
tab = table;
index = (key & 0x7FFFFFFF) % tab.length;
}
// creates the new entry.
tab[index] = new Entry(key, value, tab[index]);
count++;
return null;
}
/**
* Removes the mapping for this key from this map if present.
*
* @param key key whose mapping is to be removed from the map.
*
* @return previous value associated with specified key, or <code>null</code> if
* there was no mapping for key. A <code>null</code> return can also indicate
* that the map previously associated <code>null</code> with the specified
* key.
*/
@Override
public Object remove(Object key) {
if (key instanceof Number) {
return remove( ((Number)key).intValue() );
} else {
return null;
}
}
/**
* Removes the mapping for this key from this map if present.
*
* @param key key whose mapping is to be removed from the map.
*
* @return previous value associated with specified key, or <code>null</code> if
* there was no mapping for key. A <code>null</code> return can also indicate
* that the map previously associated <code>null</code> with the specified
* key.
*/
public Object remove(int key) {
Entry tab[] = table;
int index = (key & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index], prev = null; e != null;
prev = e, e = e.next) {
if (e.key == key) {
modCount++;
if (prev != null) {
prev.next = e.next;
} else {
tab[index] = e.next;
}
count--;
Object oldValue = e.value;
e.value = null;
return oldValue;
}
}
return null;
}
/**
* Copies all of the mappings from the specified map to this one.
* These mappings replace any mappings that this map had for any of the
* keys currently in the specified Map.
*
* @param t Mappings to be stored in this map.
*/
@Override
public void putAll(Map t) {
for (Object o : t.entrySet()) {
Map.Entry e = (Map.Entry) o;
put(e.getKey(), e.getValue());
}
}
/**
* Removes all mappings from this map.
*/
@Override
public void clear() {
Entry tab[] = table;
modCount++;
for (int index = tab.length; --index >= 0; ) {
tab[index] = null;
}
count = 0;
}
/**
* Returns a shallow copy of this <code>IntHashMap</code> instance: the keys and
* values themselves are not cloned.
*
* @return a shallow copy of this map.
*/
@Override
public Object clone() {
try {
IntHashMap t = (IntHashMap)super.clone();
t.table = new Entry[table.length];
for (int i = table.length ; i-- > 0 ; ) {
t.table[i] = (table[i] != null)
? (Entry)table[i].clone() : null;
}
t.keySet = null;
t.entrySet = null;
t.values = null;
t.modCount = 0;
return t;
} catch (CloneNotSupportedException e) {
// this shouldn"t happen, since we are Cloneable
throw new InternalError();
}
}
// views
private transient Set keySet;
private transient Set entrySet;
private transient Collection values;
/**
* Returns a set view of the keys contained in this map. The set is backed by
* the map, so changes to the map are reflected in the set, and vice-versa.
* The set supports element removal, which removes the corresponding mapping
* from this map, via the <code>Iterator.remove</code>,
* <code>Set.remove</code>, <code>removeAll</code>, <code>retainAll</code>,
* and <code>clear</code> operations. It does not support the
* <code>add</code> or <code>addAll</code> operations.
*
* @return a set view of the keys contained in this map.
*/
@Override
public Set keySet() {
if (keySet == null) {
keySet = new AbstractSet() {
@Override
public Iterator iterator() {
return new IntHashIterator(KEYS);
}
@Override
public int size() {
return count;
}
@Override
public boolean contains(Object o) {
return containsKey(o);
}
@Override
public boolean remove(Object o) {
return IntHashMap.this.remove(o) != null;
}
@Override
public void clear() {
IntHashMap.this.clear();
}
};
}
return keySet;
}
/**
* Returns a collection view of the values contained in this map. The
* collection is backed by the map, so changes to the map are reflected in
* the collection, and vice-versa. The collection supports element removal,
* which removes the corresponding mapping from this map, via the
* <code>Iterator.remove</code>, <code>Collection.remove</code>,
* <code>removeAll</code>, <code>retainAll</code>, and <code>clear</code>
* operations. It does not support the <code>add</code> or
* <code>addAll</code> operations.
*
* @return a collection view of the values contained in this map.
*/
@Override
public Collection values() {
if (values==null) {
values = new AbstractCollection() {
@Override
public Iterator iterator() {
return new IntHashIterator(VALUES);
}
@Override
public int size() {
return count;
}
@Override
public boolean contains(Object o) {
return containsValue(o);
}
@Override
public void clear() {
IntHashMap.this.clear();
}
};
}
return values;
}
/**
* Returns a collection view of the mappings contained in this map. Each
* element in the returned collection is a <code>Map.Entry</code>. The
* collection is backed by the map, so changes to the map are reflected in
* the collection, and vice-versa. The collection supports element removal,
* which removes the corresponding mapping from the map, via the
* <code>Iterator.remove</code>, <code>Collection.remove</code>,
* <code>removeAll</code>, <code>retainAll</code>, and <code>clear</code>
* operations. It does not support the <code>add</code> or
* <code>addAll</code> operations.
*
* @return a collection view of the mappings contained in this map.
* @see java.util.Map.Entry
*/
@Override
public Set entrySet() {
if (entrySet==null) {
entrySet = new AbstractSet() {
@Override
public Iterator iterator() {
return new IntHashIterator(ENTRIES);
}
@Override
public boolean contains(Object o) {
if (!(o instanceof Map.Entry)) {
return false;
}
Map.Entry entry = (Map.Entry)o;
Object key = entry.getKey();
Entry tab[] = table;
int hash = (key==null ? 0 : key.hashCode());
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index]; e != null; e = e.next) {
if (e.key == hash && e.equals(entry)) {
return true;
}
}
return false;
}
@Override
public boolean remove(Object o) {
if (!(o instanceof Map.Entry)) {
return false;
}
Map.Entry entry = (Map.Entry)o;
Object key = entry.getKey();
Entry tab[] = table;
int hash = (key==null ? 0 : key.hashCode());
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index], prev = null; e != null;
prev = e, e = e.next) {
if (e.key == hash && e.equals(entry)) {
modCount++;
if (prev != null) {
prev.next = e.next;
} else {
tab[index] = e.next;
}
count--;
e.value = null;
return true;
}
}
return false;
}
@Override
public int size() {
return count;
}
@Override
public void clear() {
IntHashMap.this.clear();
}
};
}
return entrySet;
}
/**
* IntHashMap collision list entry.
*/
private static class Entry implements Map.Entry, Cloneable {
int key;
Object value;
Entry next;
private Integer objectKey;
Entry(int key, Object value, Entry next) {
this.key = key;
this.value = value;
this.next = next;
}
@Override
protected Object clone() {
return new Entry(key, value,
(next==null ? null : (Entry)next.clone()));
}
// Map.Entry Ops
public Object getKey() {
return(objectKey != null) ? objectKey :
(objectKey = new Integer(key));
}
public Object getValue() {
return value;
}
public Object setValue(Object value) {
Object oldValue = this.value;
this.value = value;
return oldValue;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Map.Entry)) {
return false;
}
Map.Entry e = (Map.Entry)o;
return(getKey().equals(e.getKey())) &&
(value==null ? e.getValue()==null : value.equals(e.getValue()));
}
@Override
public int hashCode() {
return key ^ (value==null ? 0 : value.hashCode());
}
@Override
public String toString() {
return Integer.toString(key) + "=" + value;
}
}
// types of Iterators
private static final int KEYS = 0;
private static final int VALUES = 1;
private static final int ENTRIES = 2;
private class IntHashIterator implements Iterator {
Entry[] table = IntHashMap.this.table;
int index = table.length;
Entry entry;
Entry lastReturned;
int type;
/**
* The modCount value that the iterator believes that the backing
* List should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
private int expectedModCount = modCount;
IntHashIterator(int type) {
this.type = type;
}
public boolean hasNext() {
while (entry == null && index > 0) {
entry = table[--index];
}
return entry != null;
}
public Object next() {
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
while (entry == null && index > 0) {
entry = table[--index];
}
if (entry != null) {
Entry e = lastReturned = entry;
entry = e.next;
return type == KEYS ? e.getKey() :
(type == VALUES ? e.value : e);
}
throw new NoSuchElementException();
}
public void remove() {
if (lastReturned == null) {
throw new IllegalStateException();
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
Entry[] tab = IntHashMap.this.table;
int ndx = (lastReturned.key & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[ndx], prev = null; e != null;
prev = e, e = e.next) {
if (e == lastReturned) {
modCount++;
expectedModCount++;
if (prev == null) {
tab[ndx] = e.next;
} else {
prev.next = e.next;
}
count--;
lastReturned = null;
return;
}
}
throw new ConcurrentModificationException();
}
}
/**
* Save the state of the <code>IntHashMap</code> instance to a stream (i.e.,
* serialize it).
* <p>
* Context The <i>capacity</i> of the IntHashMap (the length of the bucket
* array) is emitted (int), followed by the <i>size</i> of the IntHashMap
* (the number of key-value mappings), followed by the key (Object) and value
* (Object) for each key-value mapping represented by the IntHashMap The
* key-value mappings are emitted in no particular order.
*
* @exception IOException
*/
private void writeObject(java.io.ObjectOutputStream s) throws IOException {
// write out the threshold, loadfactor, and any hidden stuff
s.defaultWriteObject();
// write out number of buckets
s.writeInt(table.length);
// write out size (number of Mappings)
s.writeInt(count);
// write out keys and values (alternating)
for (int index = table.length-1; index >= 0; index--) {
Entry entry = table[index];
while (entry != null) {
s.writeInt(entry.key);
s.writeObject(entry.value);
entry = entry.next;
}
}
}
/**
* Reconstitutes the <code>IntHashMap</code> instance from a stream (i.e.,
* deserialize it).
*
* @exception IOException
* @exception ClassNotFoundException
*/
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
// read in the threshold, loadfactor, and any hidden stuff
s.defaultReadObject();
// read in number of buckets and allocate the bucket array;
int numBuckets = s.readInt();
table = new Entry[numBuckets];
// read in size (number of Mappings)
int size = s.readInt();
// read the keys and values, and put the mappings in the IntHashMap
for (int i=0; i<size; i++) {
int key = s.readInt();
Object value = s.readObject();
put(key, value);
}
}
int capacity() {
return table.length;
}
float loadFactor() {
return loadFactor;
}
}
Int Int Map
/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
*
* This program and the accompanying materials are made available under
* the terms of the Common Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/cpl-v10.html
*
* $Id: IntIntMap.java,v 1.1.1.1 2004/05/09 16:57:53 vlad_r Exp $
*/
// ----------------------------------------------------------------------------
/**
*
* MT-safety: an instance of this class is <I>not</I> safe for access from
* multiple concurrent threads [even if access is done by a single thread at a
* time]. The caller is expected to synchronize externally on an instance [the
* implementation does not do internal synchronization for the sake of efficiency].
* java.util.ConcurrentModificationException is not supported either.
*
* @author Vlad Roubtsov, (C) 2001
*/
public
final class IntIntMap
{
// public: ................................................................
// TODO: optimize key comparisons using key.hash == entry.key.hash condition
/**
* Equivalent to <CODE>IntObjectMap(11, 0.75F)</CODE>.
*/
public IntIntMap ()
{
this (11, 0.75F);
}
/**
* Equivalent to <CODE>IntObjectMap(capacity, 0.75F)</CODE>.
*/
public IntIntMap (final int initialCapacity)
{
this (initialCapacity, 0.75F);
}
/**
* Constructs an IntObjectMap with specified initial capacity and load factor.
*
* @param initialCapacity initial number of hash buckets in the table [may not be negative, 0 is equivalent to 1].
* @param loadFactor the load factor to use to determine rehashing points [must be in (0.0, 1.0] range].
*/
public IntIntMap (int initialCapacity, final float loadFactor)
{
if (initialCapacity < 0) throw new IllegalArgumentException ("negative input: initialCapacity [" + initialCapacity + "]");
if ((loadFactor <= 0.0) || (loadFactor >= 1.0 + 1.0E-6))
throw new IllegalArgumentException ("loadFactor not in (0.0, 1.0] range: " + loadFactor);
if (initialCapacity == 0) initialCapacity = 1;
m_loadFactor = loadFactor > 1.0 ? 1.0F : loadFactor;
m_sizeThreshold = (int) (initialCapacity * loadFactor);
m_buckets = new Entry [initialCapacity];
}
/**
* Overrides Object.toString() for debug purposes.
*/
public String toString ()
{
final StringBuffer s = new StringBuffer ();
debugDump (s);
return s.toString ();
}
/**
* Returns the number of key-value mappings in this map.
*/
public int size ()
{
return m_size;
}
public boolean contains (final int key)
{
// index into the corresponding hash bucket:
final Entry [] buckets = m_buckets;
final int bucketIndex = (key & 0x7FFFFFFF) % buckets.length;
// traverse the singly-linked list of entries in the bucket:
for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
{
if (key == entry.m_key) return true;
}
return false;
}
/**
* Returns the value that is mapped to a given "key". Returns
* false if this key has never been mapped.
*
* @param key mapping key
* @param out holder for the found value [must be at least of size 1]
*
* @return "true" if this key was mapped to an existing value
*/
public boolean get (final int key, final int [] out)
{
// index into the corresponding hash bucket:
final Entry [] buckets = m_buckets;
final int bucketIndex = (key & 0x7FFFFFFF) % buckets.length;
// traverse the singly-linked list of entries in the bucket:
for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
{
if (key == entry.m_key)
{
out [0] = entry.m_value;
return true;
}
}
return false;
}
public boolean get (final int key, final int [] out, final int index)
{
// index into the corresponding hash bucket:
final Entry [] buckets = m_buckets;
final int bucketIndex = (key & 0x7FFFFFFF) % buckets.length;
// traverse the singly-linked list of entries in the bucket:
for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
{
if (key == entry.m_key)
{
out [index] = entry.m_value;
return true;
}
}
return false;
}
public int [] keys ()
{
final int [] result = new int [m_size];
int scan = 0;
for (int b = 0; b < m_buckets.length; ++ b)
{
for (Entry entry = m_buckets [b]; entry != null; entry = entry.m_next)
{
result [scan ++] = entry.m_key;
}
}
return result;
}
/**
* Updates the table to map "key" to "value". Any existing mapping is overwritten.
*
* @param key mapping key
* @param value mapping value
*/
public void put (final int key, final int value)
{
Entry currentKeyEntry = null;
// detect if "key" is already in the table [in which case, set "currentKeyEntry" to point to its entry]:
// index into the corresponding hash bucket:
int bucketIndex = (key & 0x7FFFFFFF) % m_buckets.length;
// traverse the singly-linked list of entries in the bucket:
Entry [] buckets = m_buckets;
for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
{
if (key == entry.m_key)
{
currentKeyEntry = entry;
break;
}
}
if (currentKeyEntry != null)
{
// replace the current value:
currentKeyEntry.m_value = value;
}
else
{
// add a new entry:
if (m_size >= m_sizeThreshold) rehash ();
buckets = m_buckets;
bucketIndex = (key & 0x7FFFFFFF) % buckets.length;
final Entry bucketListHead = buckets [bucketIndex];
final Entry newEntry = new Entry (key, value, bucketListHead);
buckets [bucketIndex] = newEntry;
++ m_size;
}
}
/**
* Updates the table to map "key" to "value". Any existing mapping is overwritten.
*
* @param key mapping key
*/
public void remove (final int key)
{
// index into the corresponding hash bucket:
final int bucketIndex = (key & 0x7FFFFFFF) % m_buckets.length;
// traverse the singly-linked list of entries in the bucket:
Entry [] buckets = m_buckets;
for (Entry entry = buckets [bucketIndex], prev = entry; entry != null; )
{
final Entry next = entry.m_next;
if (key == entry.m_key)
{
if (prev == entry)
buckets [bucketIndex] = next;
else
prev.m_next = next;
-- m_size;
break;
}
prev = entry;
entry = next;
}
}
// protected: .............................................................
// package: ...............................................................
void debugDump (final StringBuffer out)
{
if (out != null)
{
out.append (super.toString ()); out.append (EOL);
out.append ("size = " + m_size + ", bucket table size = " + m_buckets.length + ", load factor = " + m_loadFactor + EOL);
out.append ("size threshold = " + m_sizeThreshold + EOL);
}
}
// private: ...............................................................
/**
* The structure used for chaining colliding keys.
*/
private static final class Entry
{
Entry (final int key, final int value, final Entry next)
{
m_key = key;
m_value = value;
m_next = next;
}
int m_key;
int m_value;
Entry m_next; // singly-linked list link
} // end of nested class
/**
* Re-hashes the table into a new array of buckets.
*/
private void rehash ()
{
// TODO: it is possible to run this method twice, first time using the 2*k+1 prime sequencer for newBucketCount
// and then with that value reduced to actually shrink capacity. As it is right now, the bucket table can
// only grow in size
final Entry [] buckets = m_buckets;
final int newBucketCount = (m_buckets.length << 1) + 1;
final Entry [] newBuckets = new Entry [newBucketCount];
// rehash all entry chains in every bucket:
for (int b = 0; b < buckets.length; ++ b)
{
for (Entry entry = buckets [b]; entry != null; )
{
final Entry next = entry.m_next; // remember next pointer because we are going to reuse this entry
final int entryKeyHash = entry.m_key & 0x7FFFFFFF;
// index into the corresponding new hash bucket:
final int newBucketIndex = entryKeyHash % newBucketCount;
final Entry bucketListHead = newBuckets [newBucketIndex];
entry.m_next = bucketListHead;
newBuckets [newBucketIndex] = entry;
entry = next;
}
}
m_sizeThreshold = (int) (newBucketCount * m_loadFactor);
m_buckets = newBuckets;
}
private final float m_loadFactor; // determines the setting of m_sizeThreshold
private Entry [] m_buckets; // table of buckets
private int m_size; // number of keys in the table, not cleared as of last check
private int m_sizeThreshold; // size threshold for rehashing
private static final String EOL = System.getProperty ("line.separator", "\n");
} // end of class
// ----------------------------------------------------------------------------
IntMap provides a simple hashmap from keys to integers
/*
* Copyright (c) 2001-2008 Caucho Technology, Inc. All rights reserved.
*
* The Apache Software License, Version 1.1
*
* 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 acknowlegement:
* "This product includes software developed by the
* Caucho Technology (http://www.caucho.ru/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "Burlap", "Resin", and "Caucho" must not be used to
* endorse or promote products derived from this software without prior
* written permission. For written permission, please contact
* info@caucho.ru.
*
* 5. Products derived from this software may not be called "Resin"
* nor may "Resin" appear in their names without prior written
* permission of Caucho Technology.
*
* 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 CAUCHO TECHNOLOGY 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.
*
* @author Scott Ferguson
*/
/**
* The IntMap provides a simple hashmap from keys to integers. The API is
* an abbreviation of the HashMap collection API.
*
* <p>The convenience of IntMap is avoiding all the silly wrapping of
* integers.
*/
public class IdentityIntMap {
/**
* Encoding of a null entry. Since NULL is equal to Integer.MIN_VALUE,
* it"s impossible to distinguish between the two.
*/
public final static int NULL = 0xdeadbeef; // Integer.MIN_VALUE + 1;
private static final Object DELETED = new Object();
private Object []_keys;
private int []_values;
private int _size;
private int _mask;
/**
* Create a new IntMap. Default size is 16.
*/
public IdentityIntMap()
{
_keys = new Object[256];
_values = new int[256];
_mask = _keys.length - 1;
_size = 0;
}
/**
* Clear the hashmap.
*/
public void clear()
{
Object []keys = _keys;
int []values = _values;
for (int i = keys.length - 1; i >= 0; i--) {
keys[i] = null;
values[i] = 0;
}
_size = 0;
}
/**
* Returns the current number of entries in the map.
*/
public int size()
{
return _size;
}
/**
* Puts a new value in the property table with the appropriate flags
*/
public int get(Object key)
{
int mask = _mask;
int hash = System.identityHashCode(key) % mask & mask;
Object []keys = _keys;
while (true) {
Object mapKey = keys[hash];
if (mapKey == null)
return NULL;
else if (mapKey == key)
return _values[hash];
hash = (hash + 1) % mask;
}
}
/**
* Expands the property table
*/
private void resize(int newSize)
{
Object []newKeys = new Object[newSize];
int []newValues = new int[newSize];
int mask = _mask = newKeys.length - 1;
Object []keys = _keys;
int values[] = _values;
for (int i = keys.length - 1; i >= 0; i--) {
Object key = keys[i];
if (key == null || key == DELETED)
continue;
int hash = System.identityHashCode(key) % mask & mask;
while (true) {
if (newKeys[hash] == null) {
newKeys[hash] = key;
newValues[hash] = values[i];
break;
}
hash = (hash + 1) % mask;
}
}
_keys = newKeys;
_values = newValues;
}
/**
* Puts a new value in the property table with the appropriate flags
*/
public int put(Object key, int value)
{
int mask = _mask;
int hash = System.identityHashCode(key) % mask & mask;
Object []keys = _keys;
while (true) {
Object testKey = keys[hash];
if (testKey == null || testKey == DELETED) {
keys[hash] = key;
_values[hash] = value;
_size++;
if (keys.length <= 4 * _size)
resize(4 * keys.length);
return NULL;
}
else if (key != testKey) {
hash = (hash + 1) % mask;
continue;
}
else {
int old = _values[hash];
_values[hash] = value;
return old;
}
}
}
/**
* Deletes the entry. Returns true if successful.
*/
public int remove(Object key)
{
int mask = _mask;
int hash = System.identityHashCode(key) % mask & mask;
while (true) {
Object mapKey = _keys[hash];
if (mapKey == null)
return NULL;
else if (mapKey == key) {
_keys[hash] = DELETED;
_size--;
return _values[hash];
}
hash = (hash + 1) % mask;
}
}
public String toString()
{
StringBuffer sbuf = new StringBuffer();
sbuf.append("IntMap[");
boolean isFirst = true;
for (int i = 0; i <= _mask; i++) {
if (_keys[i] != null && _keys[i] != DELETED) {
if (! isFirst)
sbuf.append(", ");
isFirst = false;
sbuf.append(_keys[i]);
sbuf.append(":");
sbuf.append(_values[i]);
}
}
sbuf.append("]");
return sbuf.toString();
}
}
Int Object HashMap
/*
* Copyright (c) 1998 - 2005 Versant Corporation
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Versant Corporation - initial API and implementation
*/
import java.io.IOException;
import java.io.Serializable;
/**
* Specialized HashMap mapping int to Object. This is a cut and paste of
* java.util.HashMap with the key hardcoded as int and some non-required
* functionality removed.
*/
public final class IntObjectHashMap implements Serializable {
/**
* The default initial capacity - MUST be a power of two.
*/
private static final int DEFAULT_INITIAL_CAPACITY = 16;
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
private static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The load factor used when none specified in constructor.
*/
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* The table, resized as necessary. Length MUST Always be a power of two.
*/
private transient Entry[] table;
/**
* The number of key-value mappings contained in this identity hash map.
*/
private transient int size;
/**
* The next size value at which to resize (capacity * load factor).
*
* @serial
*/
private int threshold;
/**
* The load factor for the hash table.
*
* @serial
*/
private final float loadFactor;
/**
* Constructs an empty <tt>IntObjectHashMap</tt> with the specified initial
* capacity and load factor.
*
* @param initialCapacity The initial capacity.
* @param loadFactor The load factor.
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive.
*/
public IntObjectHashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0) {
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
}
if (initialCapacity > MAXIMUM_CAPACITY) {
initialCapacity = MAXIMUM_CAPACITY;
}
if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
}
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity) {
capacity <<= 1;
}
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
table = new Entry[capacity];
}
/**
* Constructs an empty <tt>IntObjectHashMap</tt> with the specified initial
* capacity and the default load factor (0.75).
*
* @param initialCapacity the initial capacity.
* @throws IllegalArgumentException if the initial capacity is negative.
*/
public IntObjectHashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* Constructs an empty <tt>IntObjectHashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
*/
public IntObjectHashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
table = new Entry[DEFAULT_INITIAL_CAPACITY];
}
/**
* Returns a string representation of this map. The string representation
* consists of a list of key-value mappings in the order returned by the
* map"s <tt>entrySet</tt> view"s iterator, enclosed in braces
* (<tt>"{}"</tt>). Adjacent mappings are separated by the characters
* <tt>", "</tt> (comma and space). Each key-value mapping is rendered as
* the key followed by an equals sign (<tt>"="</tt>) followed by the
* associated value. Keys and values are converted to strings as by
* <tt>String.valueOf(Object)</tt>.<p>
* <p/>
* This implementation creates an empty string buffer, appends a left
* brace, and iterates over the map"s <tt>entrySet</tt> view, appending
* the string representation of each <tt>map.entry</tt> in turn. After
* appending each entry except the last, the string <tt>", "</tt> is
* appended. Finally a right brace is appended. A string is obtained
* from the stringbuffer, and returned.
*
* @return a String representation of this map.
*/
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("{");
for (int i = 0; i < table.length; i++) {
Entry e = table[i];
for (; e != null; e = e.next) {
int key = e.key;
Object value = e.getValue();
buf.append(key + "=" + (value == this ? "(this Map)" : value));
}
}
buf.append("}");
return buf.toString();
}
/**
* Returns the number of key-value mappings in this map.
*
* @return the number of key-value mappings in this map.
*/
public int size() {
return size;
}
/**
* Returns <tt>true</tt> if this map contains no key-value mappings.
*
* @return <tt>true</tt> if this map contains no key-value mappings.
*/
public boolean isEmpty() {
return size == 0;
}
/**
* Returns the value to which the specified key is mapped in this identity
* hash map, or <tt>null</tt> if the map contains no mapping for this key.
* A return value of <tt>null</tt> does not <i>necessarily</i> indicate
* that the map contains no mapping for the key; it is also possible that
* the map explicitly maps the key to <tt>null</tt>. The
* <tt>containsKey</tt> method may be used to distinguish these two cases.
*
* @param key the key whose associated value is to be returned.
* @return the value to which this map maps the specified key, or
* <tt>null</tt> if the map contains no mapping for this key.
*/
public Object get(int key) {
int i = key & (table.length - 1);
Entry e = table[i];
while (true) {
if (e == null) {
return e;
}
if (key == e.key) {
return e.value;
}
e = e.next;
}
}
/**
* Returns <tt>true</tt> if this map contains a mapping for the
* specified key.
*
* @param key The key whose presence in this map is to be tested
* @return <tt>true</tt> if this map contains a mapping for the specified
* key.
*/
public boolean containsKey(int key) {
int i = key & (table.length - 1);
Entry e = table[i];
while (e != null) {
if (key == e.key) {
return true;
}
e = e.next;
}
return false;
}
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for this key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated.
* @param value value to be associated with the specified key.
* @return previous value associated with specified key, or <tt>null</tt>
* if there was no mapping for key. A <tt>null</tt> return can
* also indicate that the IntObjectHashMap previously associated
* <tt>null</tt> with the specified key.
*/
public Object put(int key, Object value) {
int i = key & (table.length - 1);
for (Entry e = table[i]; e != null; e = e.next) {
if (key == e.key) {
Object oldValue = e.value;
e.value = value;
return oldValue;
}
}
addEntry(key, value, i);
return null;
}
/**
* This method is used instead of put by constructors and
* pseudoconstructors (clone, readObject). It does not resize the table,
* check for comodification, etc. It calls createEntry rather than
* addEntry.
*/
private void putForCreate(int key, Object value) {
int i = key & (table.length - 1);
/**
* Look for preexisting entry for key. This will never happen for
* clone or deserialize. It will only happen for construction if the
* input Map is a sorted map whose ordering is inconsistent w/ equals.
*/
for (Entry e = table[i]; e != null; e = e.next) {
if (key == e.key) {
e.value = value;
return;
}
}
createEntry(key, value, i);
}
/**
* Rehashes the contents of this map into a new array with a
* larger capacity. This method is called automatically when the
* number of keys in this map reaches its threshold.
* <p/>
* If current capacity is MAXIMUM_CAPACITY, this method does not
* resize the map, but but sets threshold to Integer.MAX_VALUE.
* This has the effect of preventing future calls.
*
* @param newCapacity the new capacity, MUST be a power of two;
* must be greater than current capacity unless current
* capacity is MAXIMUM_CAPACITY (in which case value
* is irrelevant).
*/
private void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
/**
* Transfer all entries from current table to newTable.
*/
private void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry e = src[j];
if (e != null) {
src[j] = null;
do {
Entry next = e.next;
int i = e.key & (newCapacity - 1);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}
/**
* Removes the mapping for this key from this map if present.
*
* @param key key whose mapping is to be removed from the map.
* @return previous value associated with specified key, or <tt>null</tt>
* if there was no mapping for key. A <tt>null</tt> return can
* also indicate that the map previously associated <tt>null</tt>
* with the specified key.
*/
public Object remove(int key) {
Entry e = removeEntryForKey(key);
return e == null ? e : e.value;
}
/**
* Removes and returns the entry associated with the specified key
* in the IntObjectHashMap. Returns null if the IntObjectHashMap contains no mapping
* for this key.
*/
private Entry removeEntryForKey(int key) {
int i = key & (table.length - 1);
Entry prev = table[i];
Entry e = prev;
while (e != null) {
Entry next = e.next;
if (key == e.key) {
size--;
if (prev == e) {
table[i] = next;
} else {
prev.next = next;
}
return e;
}
prev = e;
e = next;
}
return e;
}
/**
* Removes all mappings from this map.
*/
public void clear() {
Entry tab[] = table;
for (int i = 0; i < tab.length; i++) {
tab[i] = null;
}
size = 0;
}
/**
* Returns <tt>true</tt> if this map maps one or more keys to the
* specified value.
*
* @param value value whose presence in this map is to be tested.
* @return <tt>true</tt> if this map maps one or more keys to the
* specified value.
*/
public boolean containsValue(Object value) {
Entry tab[] = table;
for (int i = 0; i < tab.length; i++) {
for (Entry e = tab[i]; e != null; e = e.next) {
if (value.equals(e.value)) {
return true;
}
}
}
return false;
}
private static class Entry {
final int key;
Object value;
Entry next;
/**
* Create new entry.
*/
public Entry(int k, Object v, Entry n) {
value = v;
next = n;
key = k;
}
public Object getValue() {
return value;
}
public Object setValue(Object newValue) {
Object oldValue = value;
value = newValue;
return oldValue;
}
public boolean equals(Object o) {
if (!(o instanceof Entry)) {
return false;
}
Entry e = (Entry)o;
if (key == e.key) {
if (value == e.value || (value != null && value.equals(e.value))) {
return true;
}
}
return false;
}
public int hashCode() {
return key ^ (value == null ? 0 : value.hashCode());
}
public String toString() {
return key + "=" + getValue();
}
}
/**
* Add a new entry with the specified key, value and hash code to
* the specified bucket. It is the responsibility of this
* method to resize the table if appropriate.
* <p/>
* Subclass overrides this to alter the behavior of put method.
*/
private void addEntry(int key, Object value, int bucketIndex) {
table[bucketIndex] = new Entry(key, value, table[bucketIndex]);
if (size++ >= threshold) {
resize(2 * table.length);
}
}
/**
* Like addEntry except that this version is used when creating entries
* as part of Map construction or "pseudo-construction" (cloning,
* deserialization). This version needn"t worry about resizing the table.
* <p/>
* Subclass overrides this to alter the behavior of IntObjectHashMap(Map),
* clone, and readObject.
*/
private void createEntry(int key, Object value, int bucketIndex) {
table[bucketIndex] = new Entry(key, value, table[bucketIndex]);
size++;
}
/**
* Save the state of the <tt>IntObjectHashMap</tt> instance to a stream (i.e.,
* serialize it).
*
* @serialData The <i>capacity</i> of the IntObjectHashMap (the length of the
* bucket array) is emitted (int), followed by the
* <i>size</i> of the IntObjectHashMap (the number of key-value
* mappings), followed by the key (Object) and value (Object)
* for each key-value mapping represented by the IntObjectHashMap
* The key-value mappings are emitted in the order that they
* are returned by <tt>entrySet().iterator()</tt>.
*/
private void writeObject(java.io.ObjectOutputStream s)
throws IOException {
// Write out the threshold, loadfactor, and any hidden stuff
s.defaultWriteObject();
// Write out number of buckets
s.writeInt(table.length);
// Write out size (number of Mappings)
s.writeInt(size);
// Write out keys and values (alternating)
int c = 0;
for (int i = 0; c < size && i < table.length; i++) {
Entry e = table[i];
for (; e != null; e = e.next, ++c) {
s.writeInt(e.key);
s.writeObject(e.getValue());
}
}
}
/**
* Reconstitute the <tt>IntObjectHashMap</tt> instance from a stream (i.e.,
* deserialize it).
*/
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// Read in the threshold, loadfactor, and any hidden stuff
s.defaultReadObject();
// Read in number of buckets and allocate the bucket array;
int numBuckets = s.readInt();
table = new Entry[numBuckets];
// Read in size (number of Mappings)
int size = s.readInt();
// Read the keys and values, and put the mappings in the IntObjectHashMap
for (int i = 0; i < size; i++) {
int key = s.readInt();
Object value = s.readObject();
putForCreate(key, value);
}
}
}
Int Object HashMap (from CERN)
/*
Copyright � 1999 CERN - European Organization for Nuclear Research.
Permission to use, copy, modify, distribute and sell this software and its documentation for any purpose
is hereby granted without fee, provided that the above copyright notice appear in all copies and
that both that copyright notice and this permission notice appear in supporting documentation.
CERN makes no representations about the suitability of this software for any purpose.
It is provided "as is" without expressed or implied warranty.
*/
import java.util.ArrayList;
import java.util.Arrays;
/**
* Hash map holding (key,value) associations of type <tt>(int-->Object)</tt>;
* Automatically grows and shrinks as needed; Implemented using open addressing
* with double hashing. First see the to get the broad picture.
* <p>
* Note that implementations are not synchronized.
*
* @author wolfgang.hoschek@cern.ch
* @version 1.0, 09/24/99
* @see java.util.HashMap
*/
abstract class AbstractHashMap {
/**
* The number of distinct associations in the map; its "size()".
*/
protected int distinct;
/**
* The table capacity c=table.length always satisfies the invariant
* <tt>c * minLoadFactor <= s <= c * maxLoadFactor</tt>, where s=size()
* is the number of associations currently contained. The term "c *
* minLoadFactor" is called the "lowWaterMark", "c * maxLoadFactor" is
* called the "highWaterMark". In other words, the table capacity (and
* proportionally the memory used by this class) oscillates within these
* constraints. The terms are precomputed and cached to avoid recalculating
* them each time put(..) or removeKey(...) is called.
*/
protected int lowWaterMark;
protected int highWaterMark;
/**
* The minimum load factor for the hashtable.
*/
protected double minLoadFactor;
/**
* The maximum load factor for the hashtable.
*/
protected double maxLoadFactor;
protected static final int defaultCapacity = 277;
protected static final double defaultMinLoadFactor = 0.2;
protected static final double defaultMaxLoadFactor = 0.5;
/**
* Makes this class non instantiable, but still let"s others inherit from
* it.
*/
protected AbstractHashMap() {
}
/**
* Chooses a new prime table capacity optimized for growing that
* (approximately) satisfies the invariant
* <tt>c * minLoadFactor <= size <= c * maxLoadFactor</tt> and has at
* least one FREE slot for the given size.
*/
protected int chooseGrowCapacity(int size, double minLoad, double maxLoad) {
return nextPrime(Math.max(size + 1,
(int) ((4 * size / (3 * minLoad + maxLoad)))));
}
/**
* Returns new high water mark threshold based on current capacity and
* maxLoadFactor.
*
* @return int the new threshold.
*/
protected int chooseHighWaterMark(int capacity, double maxLoad) {
return Math.min(capacity - 2, (int) (capacity * maxLoad)); // makes
// sure
// there is
// always at
// least one
// FREE slot
}
/**
* Returns new low water mark threshold based on current capacity and
* minLoadFactor.
*
* @return int the new threshold.
*/
protected int chooseLowWaterMark(int capacity, double minLoad) {
return (int) (capacity * minLoad);
}
/**
* Chooses a new prime table capacity neither favoring shrinking nor
* growing, that (approximately) satisfies the invariant
* <tt>c * minLoadFactor <= size <= c * maxLoadFactor</tt> and has at
* least one FREE slot for the given size.
*/
protected int chooseMeanCapacity(int size, double minLoad, double maxLoad) {
return nextPrime(Math.max(size + 1,
(int) ((2 * size / (minLoad + maxLoad)))));
}
/**
* Chooses a new prime table capacity optimized for shrinking that
* (approximately) satisfies the invariant
* <tt>c * minLoadFactor <= size <= c * maxLoadFactor</tt> and has at
* least one FREE slot for the given size.
*/
protected int chooseShrinkCapacity(int size, double minLoad, double maxLoad) {
return nextPrime(Math.max(size + 1,
(int) ((4 * size / (minLoad + 3 * maxLoad)))));
}
/**
* Removes all (key,value) associations from the receiver.
*/
public abstract void clear();
/**
* Ensures that the receiver can hold at least the specified number of
* elements without needing to allocate new internal memory. If necessary,
* allocates new internal memory and increases the capacity of the receiver.
* <p>
* This method never need be called; it is for performance tuning only.
* Calling this method before <tt>put()</tt>ing a large number of
* associations boosts performance, because the receiver will grow only once
* instead of potentially many times.
* <p>
* <b>This default implementation does nothing.</b> Override this method if
* necessary.
*
* @param minCapacity
* the desired minimum capacity.
*/
public void ensureCapacity(int minCapacity) {
}
/**
* Returns <tt>true</tt> if the receiver contains no (key,value)
* associations.
*
* @return <tt>true</tt> if the receiver contains no (key,value)
* associations.
*/
public boolean isEmpty() {
return distinct == 0;
}
/**
* Returns a prime number which is <code>>= desiredCapacity</code> and
* very close to <code>desiredCapacity</code> (within 11% if
* <code>desiredCapacity >= 1000</code>).
*
* @param desiredCapacity
* the capacity desired by the user.
* @return the capacity which should be used for a hashtable.
*/
protected int nextPrime(int desiredCapacity) {
return PrimeFinder.nextPrime(desiredCapacity);
}
/**
* Initializes the receiver. You will almost certainly need to override this
* method in subclasses to initialize the hash table.
*
* @param initialCapacity
* the initial capacity of the receiver.
* @param minLoadFactor
* the minLoadFactor of the receiver.
* @param maxLoadFactor
* the maxLoadFactor of the receiver.
* @throws IllegalArgumentException
* if
* <tt>initialCapacity < 0 || (minLoadFactor < 0.0 || minLoadFactor >= 1.0) || (maxLoadFactor <= 0.0 || maxLoadFactor >= 1.0) || (minLoadFactor >= maxLoadFactor)</tt>.
*/
protected void setUp(int initialCapacity, double minLoadFactor,
double maxLoadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException(
"Initial Capacity must not be less than zero: "
+ initialCapacity);
if (minLoadFactor < 0.0 || minLoadFactor >= 1.0)
throw new IllegalArgumentException("Illegal minLoadFactor: "
+ minLoadFactor);
if (maxLoadFactor <= 0.0 || maxLoadFactor >= 1.0)
throw new IllegalArgumentException("Illegal maxLoadFactor: "
+ maxLoadFactor);
if (minLoadFactor >= maxLoadFactor)
throw new IllegalArgumentException("Illegal minLoadFactor: "
+ minLoadFactor + " and maxLoadFactor: " + maxLoadFactor);
}
/**
* Returns the number of (key,value) associations currently contained.
*
* @return the number of (key,value) associations currently contained.
*/
public int size() {
return distinct;
}
/**
* Trims the capacity of the receiver to be the receiver"s current
* size. Releases any superfluous internal memory. An application can use this operation to minimize the
* storage of the receiver.
* <p>
* This default implementation does nothing. Override this method if necessary.
*/
public void trimToSize() {
}
} // end of class AbstractHashMap
/**
* Not of interest for users; only for implementors of hashtables. Used to keep
* hash table capacities prime numbers.
*
* <p>
* Choosing prime numbers as hash table capacities is a good idea to keep them
* working fast, particularly under hash table expansions.
* </p>
*
* <p>
* However, JDK 1.2, JGL 3.1 and many other toolkits do nothing to keep
* capacities prime. This class provides efficient means to choose prime
* capacities.
* </p>
*
* <p>
* Choosing a prime is <tt>O(log 300)</tt> (binary search in a list of 300
* int"s). Memory requirements: 1 KB static memory.
* </p>
*
* This class has been adapted from the corresponding class in the COLT
* library for scientfic computing.
*
* @author wolfgang.hoschek@cern.ch
* @version 1.0, 09/24/99
*/
class PrimeFinder extends Object {
/**
* The largest prime this class can generate; currently equal to
* <tt>Integer.MAX_VALUE</tt>.
*/
public static final int largestPrime = Integer.MAX_VALUE; // yes, it is
// prime.
/**
* The prime number list consists of 11 chunks. Each chunk contains prime
* numbers. A chunk starts with a prime P1. The next element is a prime P2.
* P2 is the smallest prime for which holds: P2 >= 2*P1. The next element is
* P3, for which the same holds with respect to P2, and so on.
*
* Chunks are chosen such that for any desired capacity >= 1000 the list
* includes a prime number <= desired capacity * 1.11 (11%). For any desired
* capacity >= 200 the list includes a prime number <= desired capacity *
* 1.16 (16%). For any desired capacity >= 16 the list includes a prime
* number <= desired capacity * 1.21 (21%).
*
* Therefore, primes can be retrieved which are quite close to any desired
* capacity, which in turn avoids wasting memory. For example, the list
* includes 1039,1117,1201,1277,1361,1439,1523,1597,1759,1907,2081. So if
* you need a prime >= 1040, you will find a prime <= 1040*1.11=1154.
*
* Chunks are chosen such that they are optimized for a hashtable
* growthfactor of 2.0; If your hashtable has such a growthfactor then,
* after initially "rounding to a prime" upon hashtable construction, it
* will later expand to prime capacities such that there exist no better
* primes.
*
* In total these are about 32*10=320 numbers -> 1 KB of static memory
* needed. If you are stingy, then delete every second or fourth chunk.
*/
private static final int[] primeCapacities = {
// chunk #0
largestPrime,
// chunk #1
5, 11, 23, 47, 97, 197, 397, 797, 1597, 3203, 6421, 12853, 25717,
51437, 102877, 205759, 411527, 823117, 1646237, 3292489, 6584983,
13169977, 26339969, 52679969, 105359939, 210719881, 421439783,
842879579, 1685759167,
// chunk #2
433, 877, 1759, 3527, 7057, 14143, 28289, 56591, 113189, 226379,
452759, 905551, 1811107, 3622219, 7244441, 14488931, 28977863,
57955739, 115911563, 231823147, 463646329, 927292699, 1854585413,
// chunk #3
953, 1907, 3821, 7643, 15287, 30577, 61169, 122347, 244703, 489407,
978821, 1957651, 3915341, 7830701, 15661423, 31322867, 62645741,
125291483, 250582987, 501165979, 1002331963, 2004663929,
// chunk #4
1039, 2081, 4177, 8363, 16729, 33461, 66923, 133853, 267713,
535481, 1070981, 2141977, 4283963, 8567929, 17135863, 34271747,
68543509, 137087021, 274174111, 548348231, 1096696463,
// chunk #5
31, 67, 137, 277, 557, 1117, 2237, 4481, 8963, 17929, 35863, 71741,
143483, 286973, 573953, 1147921, 2295859, 4591721, 9183457,
18366923, 36733847, 73467739, 146935499, 293871013, 587742049,
1175484103,
// chunk #6
599, 1201, 2411, 4831, 9677, 19373, 38747, 77509, 155027, 310081,
620171, 1240361, 2480729, 4961459, 9922933, 19845871, 39691759,
79383533, 158767069, 317534141, 635068283, 1270136683,
// chunk #7
311, 631, 1277, 2557, 5119, 10243, 20507, 41017, 82037, 164089,
328213, 656429, 1312867, 2625761, 5251529, 10503061, 21006137,
42012281, 84024581, 168049163, 336098327, 672196673, 1344393353,
// chunk #8
3, 7, 17, 37, 79, 163, 331, 673, 1361, 2729, 5471, 10949, 21911,
43853, 87719, 175447, 350899, 701819, 1403641, 2807303, 5614657,
11229331, 22458671, 44917381, 89834777, 179669557, 359339171,
718678369, 1437356741,
// chunk #9
43, 89, 179, 359, 719, 1439, 2879, 5779, 11579, 23159, 46327,
92657, 185323, 370661, 741337, 1482707, 2965421, 5930887, 11861791,
23723597, 47447201, 94894427, 189788857, 379577741, 759155483,
1518310967,
// chunk #10
379, 761, 1523, 3049, 6101, 12203, 24407, 48817, 97649, 195311,
390647, 781301, 1562611, 3125257, 6250537, 12501169, 25002389,
50004791, 100009607, 200019221, 400038451, 800076929, 1600153859
/*
* // some more chunks for the low range [3..1000] //chunk #11
* 13,29,59,127,257,521,1049,2099,4201,8419,16843,33703,67409,134837,269683,
* 539389,1078787,2157587,4315183,8630387,17260781,34521589,69043189,138086407,
* 276172823,552345671,1104691373,
*
* //chunk #12 19,41,83,167,337,677,
* //1361,2729,5471,10949,21911,43853,87719,175447,350899,
* //701819,1403641,2807303,5614657,11229331,22458671,44917381,89834777,179669557,
* //359339171,718678369,1437356741,
*
* //chunk #13 53,107,223,449,907,1823,3659,7321,14653,29311,58631,117269,
* 234539,469099,938207,1876417,3752839,7505681,15011389,30022781,
* 60045577,120091177,240182359,480364727,960729461,1921458943
*/
};
static { // initializer
// The above prime numbers are formatted for human readability.
// To find numbers fast, we sort them once and for all.
java.util.Arrays.sort(primeCapacities);
}
/**
* Makes this class non instantiable, but still let"s others inherit from
* it.
*/
protected PrimeFinder() {
}
/**
* Returns a prime number which is <code>>= desiredCapacity</code> and
* very close to <code>desiredCapacity</code> (within 11% if
* <code>desiredCapacity >= 1000</code>).
*
* @param desiredCapacity
* the capacity desired by the user.
* @return the capacity which should be used for a hashtable.
*/
public static int nextPrime(int desiredCapacity) {
int i = java.util.Arrays.binarySearch(primeCapacities, desiredCapacity);
if (i < 0) {
// desired capacity not found, choose next prime greater than
// desired capacity
i = -i - 1; // remember the semantics of binarySearch...
}
return primeCapacities[i];
}
} // end of class PrimeFinder
Int Object Map
/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
*
* This program and the accompanying materials are made available under
* the terms of the Common Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/cpl-v10.html
*
* $Id: IntObjectMap.java,v 1.1.1.1 2004/05/09 16:57:53 vlad_r Exp $
*/
import java.io.Serializable;
// ----------------------------------------------------------------------------
/**
*
* MT-safety: an instance of this class is <I>not</I> safe for access from
* multiple concurrent threads [even if access is done by a single thread at a
* time]. The caller is expected to synchronize externally on an instance [the
* implementation does not do internal synchronization for the sake of efficiency].
* java.util.ConcurrentModificationException is not supported either.
*
* @author Vlad Roubtsov, (C) 2001
*/
public
final class IntObjectMap implements Serializable
{
// public: ................................................................
/**
* Equivalent to <CODE>IntObjectMap(11, 0.75F)</CODE>.
*/
public IntObjectMap ()
{
this (11, 0.75F);
}
/**
* Equivalent to <CODE>IntObjectMap(capacity, 0.75F)</CODE>.
*/
public IntObjectMap (final int initialCapacity)
{
this (initialCapacity, 0.75F);
}
/**
* Constructs an IntObjectMap with specified initial capacity and load factor.
*
* @param initialCapacity initial number of hash buckets in the table [may not be negative, 0 is equivalent to 1].
* @param loadFactor the load factor to use to determine rehashing points [must be in (0.0, 1.0] range].
*/
public IntObjectMap (int initialCapacity, final float loadFactor)
{
if (initialCapacity < 0) throw new IllegalArgumentException ("negative input: initialCapacity [" + initialCapacity + "]");
if ((loadFactor <= 0.0) || (loadFactor >= 1.0 + 1.0E-6))
throw new IllegalArgumentException ("loadFactor not in (0.0, 1.0] range: " + loadFactor);
if (initialCapacity == 0) initialCapacity = 1;
m_loadFactor = loadFactor > 1.0 ? 1.0F : loadFactor;
m_sizeThreshold = (int) (initialCapacity * loadFactor);
m_buckets = new Entry [initialCapacity];
}
/**
* Overrides Object.toString() for debug purposes.
*/
public String toString ()
{
final StringBuffer s = new StringBuffer ();
debugDump (s);
return s.toString ();
}
/**
* Returns the number of key-value mappings in this map.
*/
public int size ()
{
return m_size;
}
public boolean contains (final int key)
{
// index into the corresponding hash bucket:
final Entry [] buckets = m_buckets;
final int bucketIndex = (key & 0x7FFFFFFF) % buckets.length;
// traverse the singly-linked list of entries in the bucket:
for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
{
if (key == entry.m_key)
return true;
}
return false;
}
/**
* Returns the value that is mapped to a given "key". Returns
* null if (a) this key has never been mapped or (b) it has been
* mapped to a null value.
*
* @param key mapping key
*
* @return Object value mapping for "key" [can be null].
*/
public Object get (final int key)
{
// index into the corresponding hash bucket:
final Entry [] buckets = m_buckets;
final int bucketIndex = (key & 0x7FFFFFFF) % buckets.length;
// traverse the singly-linked list of entries in the bucket:
for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
{
if (key == entry.m_key)
return entry.m_value;
}
return null;
}
public int [] keys ()
{
if (m_size == 0)
return new int[0];
else
{
final int [] result = new int [m_size];
int scan = 0;
for (int b = 0; b < m_buckets.length; ++ b)
{
for (Entry entry = m_buckets [b]; entry != null; entry = entry.m_next)
{
result [scan ++] = entry.m_key;
}
}
return result;
}
}
/**
* Updates the table to map "key" to "value". Any existing mapping is overwritten.
*
* @param key mapping key
* @param value mapping value [can be null].
*
* @return Object previous value mapping for "key" [can be null]
*/
public Object put (final int key, final Object value)
{
Entry currentKeyEntry = null;
// detect if "key" is already in the table [in which case, set "currentKeyEntry" to point to its entry]:
// index into the corresponding hash bucket:
int bucketIndex = (key & 0x7FFFFFFF) % m_buckets.length;
// traverse the singly-linked list of entries in the bucket:
Entry [] buckets = m_buckets;
for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
{
if (key == entry.m_key)
{
currentKeyEntry = entry;
break;
}
}
if (currentKeyEntry != null)
{
// replace the current value:
final Object currentKeyValue = currentKeyEntry.m_value;
currentKeyEntry.m_value = value;
return currentKeyValue;
}
else
{
// add a new entry:
if (m_size >= m_sizeThreshold) rehash ();
buckets = m_buckets;
bucketIndex = (key & 0x7FFFFFFF) % buckets.length;
final Entry bucketListHead = buckets [bucketIndex];
final Entry newEntry = new Entry (key, value, bucketListHead);
buckets [bucketIndex] = newEntry;
++ m_size;
return null;
}
}
// protected: .............................................................
// package: ...............................................................
void debugDump (final StringBuffer out)
{
if (out != null)
{
out.append (super.toString ()); out.append (EOL);
out.append ("size = " + m_size + ", bucket table size = " + m_buckets.length + ", load factor = " + m_loadFactor + EOL);
out.append ("size threshold = " + m_sizeThreshold + EOL);
}
}
// private: ...............................................................
/**
* The structure used for chaining colliding keys.
*/
private static final class Entry implements Serializable
{
Entry (final int key, final Object value, final Entry next)
{
m_key = key;
m_value = value;
m_next = next;
}
Object m_value; // reference to the value [never null]
final int m_key;
Entry m_next; // singly-linked list link
} // end of nested class
/**
* Re-hashes the table into a new array of buckets.
*/
private void rehash ()
{
// TODO: it is possible to run this method twice, first time using the 2*k+1 prime sequencer for newBucketCount
// and then with that value reduced to actually shrink capacity. As it is right now, the bucket table can
// only grow in size
final Entry [] buckets = m_buckets;
final int newBucketCount = (m_buckets.length << 1) + 1;
final Entry [] newBuckets = new Entry [newBucketCount];
// rehash all entry chains in every bucket:
for (int b = 0; b < buckets.length; ++ b)
{
for (Entry entry = buckets [b]; entry != null; )
{
final Entry next = entry.m_next; // remember next pointer because we are going to reuse this entry
final int entryKey = entry.m_key;
// index into the corresponding new hash bucket:
final int newBucketIndex = (entryKey & 0x7FFFFFFF) % newBucketCount;
final Entry bucketListHead = newBuckets [newBucketIndex];
entry.m_next = bucketListHead;
newBuckets [newBucketIndex] = entry;
entry = next;
}
}
m_sizeThreshold = (int) (newBucketCount * m_loadFactor);
m_buckets = newBuckets;
}
private final float m_loadFactor; // determines the setting of m_sizeThreshold
private Entry [] m_buckets; // table of buckets
private int m_size; // number of keys in the table, not cleared as of last check
private int m_sizeThreshold; // size threshold for rehashing
private static final String EOL = System.getProperty ("line.separator", "\n");
} // end of class
// ----------------------------------------------------------------------------
List Map
/*
*
* JAFFA - Java Application Framework For All
*
* Copyright (C) 2002 JAFFA Development Group
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Redistribution and use of this software and associated documentation ("Software"),
* with or without modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain copyright statements and notices.
* Redistributions must also contain a copy of this document.
* 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 name "JAFFA" must not be used to endorse or promote products derived from
* this Software without prior written permission. For written permission,
* please contact mail to: jaffagroup@yahoo.ru.
* 4. Products derived from this Software may not be called "JAFFA" nor may "JAFFA"
* appear in their names without prior written permission.
* 5. Due credit should be given to the JAFFA Project (http://jaffa.sourceforge.net).
*
* 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.
*
*/
import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* This class combines the utility of the HashMap & the ListSet. Features are:
* 1) Ensure quick random access to an object using a "key"
* 2) Iterate through the Map in the order in which the entries were made
*/
public class ListMap implements Map, Cloneable, Serializable {
private static final int TYPE_KEY_SET = 0;
private static final int TYPE_ENTRY_SET = 1;
private static final int TYPE_VALUES = 2;
/** This Map will contain the key-value pairs */
private HashMap m_map = null;
/** This List will maintain the keys in the order of entry */
private ListSet m_list = null;
// fields to hold the return Collections.
private transient Set m_keySet = null;
private transient Set m_entrySet = null;
private transient Collection m_values = null;
/** Creates new ListMap */
public ListMap() {
m_map = new HashMap();
m_list = new ListSet();
}
/** Creates new ListMap specifying the initial capacity.
* @param initialCapacity The initial capacity.
*/
public ListMap(int initialCapacity) {
m_map = new HashMap(initialCapacity);
m_list = new ListSet(initialCapacity);
}
/** Creates new ListMap specifying the initial capacity and load factor.
* @param initialCapacity The initial capacity.
* @param loadFactor The loadFactor.
*/
public ListMap(int initialCapacity, float loadFactor) {
m_map = new HashMap(initialCapacity, loadFactor);
m_list = new ListSet(initialCapacity);
}
/** Creates new ListMap from an existing Map
* @param t An existing Map.
*/
public ListMap(Map t) {
if (t == null) {
m_map = new HashMap();
m_list = new ListSet();
} else {
m_map = new HashMap(t);
m_list = new ListSet( t.keySet() );
}
}
// *** MAP INTERFACE METHODS ***
/** Adds an object to the Map. If the map previously contained a mapping for this key, the old value is replaced by the specified value.
* @param key The key used for adding the object.
* @param value The object to be added.
* @return previous value associated with specified key, or null if there was no mapping for key. A null return can also indicate that the map previously associated null with the specified key.
*/
public Object put(Object key, Object value) {
return put(-1, key, value);
}
/** Removes the mapping for this key from this map if it is present.
* @param key key whose mapping is to be removed from the map.
* @return previous value associated with specified key, or null if there was no mapping for key.
*/
public Object remove(Object key) {
m_list.remove(key);
return m_map.remove(key);
}
/** Returns a set view of the keys contained in this map.
* @return a set view of the keys contained in this map.
*/
public Set keySet() {
if (m_keySet == null)
m_keySet = new ListMap.KeySet();
return m_keySet;
}
/** Removes all mappings from this map .*/
public void clear() {
m_list.clear();
m_map.clear();
}
/** Returns a collection view of the values contained in this map.
* @return a collection view of the values contained in this map.
*/
public Collection values() {
if (m_values == null)
m_values = new ListMap.Values();
return m_values;
}
/** Returns the hash code value for this map.
* @return the hash code value for this map.
*/
public int hashCode() {
return m_map.hashCode();
}
/** Returns true if this map contains a mapping for the specified key.
* @param key key whose presence in this map is to be tested.
* @return true if this map contains a mapping for the specified key.
*/
public boolean containsKey(Object key) {
return m_map.containsKey(key);
}
/** Returns the number of key-value mappings in this map.
* @return the number of key-value mappings in this map.
*/
public int size() {
return m_map.size();
}
/** Returns a set view of the mappings contained in this map.
* @return a set view of the mappings contained in this map.
*/
public Set entrySet() {
if (m_entrySet == null)
m_entrySet = new ListMap.EntrySet();
return m_entrySet;
}
/** Returns true if this map maps one or more keys to the specified value.
* @param value value whose presence in this map is to be tested.
* @return true if this map maps one or more keys to the specified value.
*/
public boolean containsValue(Object value) {
return m_map.containsValue(value);
}
/** Copies all of the mappings from the specified map to this map.
* @param t Mappings to be stored in this map.
*/
public void putAll(Map t) {
if (t != null) {
m_map.putAll(t);
m_list.addAll( t.keySet() );
}
}
/** Compares the specified object with this map for equality.
* Returns true if the given object is also a ListMap and the two Maps represent the same mappings.
* @param o object to be compared for equality with this map.
* @return true if the specified object is equal to this map.
*/
public boolean equals(Object o) {
boolean result = false;
if (o instanceof ListMap) {
ListMap listMap = (ListMap) o;
result = m_map.equals(listMap.m_map);
}
return result;
}
/** Returns true if this map contains no key-value mappings.
* @return true if this map contains no key-value mappings.
*/
public boolean isEmpty() {
return m_map.isEmpty();
}
/** Returns the value to which this map maps the specified key.
* Returns null if the map contains no mapping for this key.
* A return value of null does not necessarily indicate that the map contains no mapping for the key; it"s also possible that the map explicitly maps the key to null.
* The containsKey operation may be used to distinguish these two cases.
* @param key key whose associated value is to be returned.
* @return the value to which this map maps the specified key, or null if the map contains no mapping for this key.
*/
public Object get(Object key) {
return m_map.get(key);
}
// *** Additional Methods ***
/** Adds an object to the Map. If the map previously contained a mapping for this key, the old value is replaced by the specified value.
* @param index The position at which the the object will be added.
* @param key The key used for adding the object.
* @param value The object to be added.
* @return previous value associated with specified key, or null if there was no mapping for key. A null return can also indicate that the map previously associated null with the specified key.
*/
public Object put(int index, Object key, Object value) {
Object returnObject = m_map.put(key, value);
// add to the list, only if it doesn"t already exist
if ( !m_list.contains(key) ) {
if (index == -1)
m_list.add(key);
else
m_list.add(index, key);
}
return returnObject;
}
/** Returns the mapping at the specified index.
* @param index The position at from which the mapping is to be retrieved.
* @return the mapping at the specified index.
*/
public Object getByIndex(int index) {
return get( m_list.get(index) );
}
/** Removes the mapping for this index from this map if it is present.
* @param index The position at from which the mapping is to be removed.
* @return previous value associated with position, or null if there was no mapping for position.
*/
public Object remove(int index) {
return remove( m_list.get(index) );
}
/** Returns the index in this Map of the specified key.
* A "-1" is returned in case no such key exists.
* @param key The key used for adding the object.
* @return the index in this Map of the specified key.
*/
public int indexOf(Object key) {
return m_list.indexOf(key);
}
// *** CLONEABLE INTERFACE METHODS ***
/** Returns a clone of the Map.
* @throws CloneNotSupportedException if cloning is not supported. Should never happen.
* @return a clone of the Map.
*/
public Object clone() throws CloneNotSupportedException {
ListMap obj = (ListMap) super.clone();
if (m_map != null && m_map instanceof HashMap)
obj.m_map = (HashMap) m_map.clone();
if (m_list != null && m_list instanceof ListSet)
obj.m_list = (ListSet) m_list.clone();
// reset the transient fields
obj.m_keySet = null;
obj.m_entrySet = null;
obj.m_values = null;
return obj;
}
// *** PRIVATE METHODS ***
private Iterator getIterator(int type) {
return new ListMap.ListMapIterator(type);
}
private Map.Entry getEntry(Object key, Object value) {
return new ListMap.ListMapEntry(key, value);
}
// *** INNER CLASSES ***
private class ListMapIterator implements Iterator {
private Iterator m_iterator = null;
private int m_type;
private Object m_lastReturned = null;
private ListMapIterator(int type) {
m_type = type;
m_iterator = ListMap.this.m_list.iterator();
}
/** Returns true if the iteration has more elements.
* @return true if the iteration has more elements.
*/
public boolean hasNext() {
return m_iterator.hasNext();
}
/** Returns the next element in the iteration.
* @return the next element in the iteration.
*/
public Object next() {
m_lastReturned = m_iterator.next();
Object obj = null;
switch (m_type) {
case TYPE_KEY_SET :
obj = m_lastReturned;
break;
case TYPE_ENTRY_SET :
Object value = ListMap.this.get(m_lastReturned);
obj = ListMap.this.getEntry(m_lastReturned, value);
break;
case TYPE_VALUES :
obj = ListMap.this.get(m_lastReturned);
break;
}
return obj;
}
/** Removes from the underlying collection the last element returned by the iterator.
*/
public void remove() {
m_iterator.remove();
ListMap.this.remove(m_lastReturned);
}
}
private class KeySet extends AbstractSet {
/** Returns the number of elements in this set.
* @return the number of elements in this set.
*/
public int size() {
return ListMap.this.size();
}
/** Returns true if this set contains the specified element.
* @param o element whose presence in this set is to be tested.
* @return true if this set contains the specified element.
*/
public boolean contains(Object o) {
return ListMap.this.containsKey(o);
}
/** Removes the specified element from this set if it is present.
* @param o object to be removed from this set, if present.
* @return true if the set contained the specified element.
*/
public boolean remove(Object o) {
int i = size();
ListMap.this.remove(o);
return ( size() != i );
}
/** Removes all of the elements from this set.*/
public void clear() {
ListMap.this.clear();
}
/** Returns an iterator over the elements in this set.
* @return an iterator over the elements in this set.
*/
public Iterator iterator() {
return ListMap.this.getIterator(ListMap.TYPE_KEY_SET);
}
}
private class EntrySet extends AbstractSet {
/** Returns the number of elements in this set.
* @return the number of elements in this set.
*/
public int size() {
return ListMap.this.size();
}
/** Returns true if this set contains the specified element.
* @param o element whose presence in this set is to be tested.
* @return true if this set contains the specified element.
*/
public boolean contains(Object o) {
boolean result = false;
if (o != null && o instanceof Map.Entry) {
Map.Entry entry = (Map.Entry) o;
Object key = entry.getKey();
if ( ListMap.this.containsKey(key) ) {
Object value1 = ListMap.this.get(key);
Object value2 = entry.getValue();
result = ( value1 == null ? value2 == null : value1.equals(value2) );
}
}
return result;
}
/** Removes the specified element from this set if it is present.
* @param o object to be removed from this set, if present.
* @return true if the set contained the specified element.
*/
public boolean remove(Object o) {
boolean result = false;
if (o != null && o instanceof Map.Entry) {
Map.Entry entry = (Map.Entry) o;
Object key = entry.getKey();
int i = size();
ListMap.this.remove(key);
result = ( i != size() );
}
return result;
}
/** Removes all of the elements from this set.*/
public void clear() {
ListMap.this.clear();
}
/** Returns an iterator over the elements in this set.
* @return an iterator over the elements in this set.
*/
public Iterator iterator() {
return ListMap.this.getIterator(ListMap.TYPE_ENTRY_SET);
}
}
private class Values extends AbstractCollection {
/** Returns the number of elements in this collection.
* @return the number of elements in this collection.
*/
public int size() {
return ListMap.this.size();
}
/** Returns true if this collection contains the specified element.
* @param o element whose presence in this collection is to be tested.
* @return true if this collection contains the specified element.
*/
public boolean contains(Object o) {
return ListMap.this.containsValue(o);
}
/** Removes all of the elements from this collection.
*/
public void clear() {
ListMap.this.clear();
}
/** Returns an iterator over the elements in this collection.
* @return an iterator over the elements in this collection.
*/
public Iterator iterator() {
return ListMap.this.getIterator(ListMap.TYPE_VALUES);
}
}
private class ListMapEntry implements Map.Entry {
Object m_key = null;
Object m_value = null;
private ListMapEntry(Object key, Object value) {
m_key = key;
m_value = value;
}
/** Returns the key corresponding to this entry.
* @return the key corresponding to this entry.
*/
public Object getKey() {
return m_key;
}
/** Returns the hash code value for this map entry.
* @return the hash code value for this map entry.
*/
public int hashCode() {
return (m_key==null ? 0 : m_key.hashCode() )
+ (m_value==null ? 0 : m_value.hashCode() );
}
/** Returns the value corresponding to this entry.
* @return the value corresponding to this entry.
*/
public Object getValue() {
return m_value;
}
/** This is an Unsupported method. It throws the UnsupportedOperationException.
* @param value the value to be set.
* @return old value corresponding to the entry.
*/
public Object setValue(Object value) {
throw new UnsupportedOperationException();
}
/** Compares the specified object with this entry for equality.
* Returns true if the given object is also a ListMap.Entry object and the two entries represent the same mapping.
* @param o object to be compared for equality with this map entry.
* @return true if the specified object is equal to this map entry.
*/
public boolean equals(Object o) {
boolean result = false;
if (o instanceof ListMap.ListMapEntry) {
ListMap.ListMapEntry e2 = (ListMap.ListMapEntry) o;
result = ( getKey()==null ?
e2.getKey()==null : getKey().equals( e2.getKey() ) )
&& ( getValue()==null ?
e2.getValue()==null : getValue().equals(e2.getValue() ) );
}
return result;
}
}
}
/*
*
* JAFFA - Java Application Framework For All
*
* Copyright (C) 2002 JAFFA Development Group
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Redistribution and use of this software and associated documentation ("Software"),
* with or without modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain copyright statements and notices.
* Redistributions must also contain a copy of this document.
* 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 name "JAFFA" must not be used to endorse or promote products derived from
* this Software without prior written permission. For written permission,
* please contact mail to: jaffagroup@yahoo.ru.
* 4. Products derived from this Software may not be called "JAFFA" nor may "JAFFA"
* appear in their names without prior written permission.
* 5. Due credit should be given to the JAFFA Project (http://jaffa.sourceforge.net).
*
* 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 class is backed by an ArrayList. Features are
* 1) Ensure the Set functionality of unique elements(including a null) in this data structure
* 2) Iterate through the list in the order in which the entries were made
*/
class ListSet implements Set, Cloneable, Serializable {
List m_set = null;
/** Creates new ListSet */
public ListSet() {
m_set = new ArrayList();
}
/** Creates new ListSet specifying the initial capacity.
* @param initialCapacity The initial capacity.
*/
public ListSet(int initialCapacity) {
m_set = new ArrayList(initialCapacity);
}
/** Creates new ListSet from an existing Collection.
* @param c An existing collection.
*/
public ListSet(Collection c) {
m_set = new ArrayList();
if (c != null) {
for (Iterator itr = c.iterator(); itr.hasNext();) {
Object obj = itr.next();
if ( !m_set.contains(obj) )
m_set.add(obj);
}
}
}
// *** Set Interface methods ***
/** Retains only the elements in this set that are contained in the specified collection.
* @param c collection that defines which elements this set will retain.
* @return true if this collection changed as a result of the call.
*/
public boolean retainAll(Collection c) {
return m_set.retainAll(c);
}
/** Returns true if this set contains the specified element.
* @param o element whose presence in this set is to be tested.
* @return true if this set contains the specified element.
*/
public boolean contains(Object o) {
return m_set.contains(o);
}
/** Returns an array containing all of the elements in this set.
* @return an array containing all of the elements in this set.
*/
public Object[] toArray() {
return m_set.toArray();
}
/** Returns an array containing all of the elements in this set; the runtime type of the returned array is that of the specified array.
* @param a the array into which the elements of this set are to be stored, if it is big enough; otherwise, a new array of the same runtime type is allocated for this purpose.
* @return an array containing all of the elements in this set; the runtime type of the returned array is that of the specified array
*/
public Object[] toArray(Object[] a) {
return m_set.toArray(a);
}
/** Returns an iterator over the elements in this set.
* @return an iterator over the elements in this set.
*/
public Iterator iterator() {
return m_set.iterator();
}
/** Removes from this set all of its elements that are contained in the specified collection.
* @param c collection that defines which elements will be removed from this set.
* @return true if this set changed as a result of the call.
*/
public boolean removeAll(Collection c) {
return m_set.removeAll(c);
}
/** Removes the specified element from this set if it is present.
* @param o object to be removed from this set, if present.
* @return true if the set contained the specified element.
*/
public boolean remove(Object o) {
return m_set.remove(o);
}
/** Removes all of the elements from this set.*/
public void clear() {
m_set.clear();
}
/** Returns the hash code value for this set.
* @return the hash code value for this set.
*/
public int hashCode() {
return m_set.hashCode();
}
/** Adds all of the elements in the specified collection to this set if they"re not already present.
* @param c collection whose elements are to be added to this set.
* @return true if this set changed as a result of the call.
*/
public boolean addAll(Collection c) {
boolean added = false;
if (c != null) {
for (Iterator itr = c.iterator(); itr.hasNext();) {
Object obj = itr.next();
if ( !m_set.contains(obj) ) {
m_set.add(obj);
added = true;
}
}
}
return added;
}
/** Returns the number of elements in this set.
* @return the number of elements in this set.
*/
public int size() {
return m_set.size();
}
/** Returns true if this set contains all of the elements of the specified collection.
* @param c collection to be checked for containment in this set.
* @return true if this set contains all of the elements of the specified collection.
*/
public boolean containsAll(Collection c) {
return m_set.containsAll(c);
}
/** Adds the specified element to this set if it is not already present.
* @param o element to be added to this set.
* @return true if this set did not already contain the specified element.
*/
public boolean add(Object o) {
boolean added = false;
if ( !m_set.contains(o) ) {
m_set.add(o);
added = true;
}
return added;
}
/** Compares the specified object with this set for equality.
* @param o Object to be compared for equality with this set.
* @return true if the specified Object is equal to this set.
*/
public boolean equals(Object o) {
return m_set.equals(o);
}
/** Returns true if this set contains no elements.
* @return true if this set contains no elements.
*/
public boolean isEmpty() {
return m_set.isEmpty();
}
// *** Additional Methods ***
/** Adds the specified element to this set if it is not already present, at the specified index.
* @param index The position at which the element is to be added.
* @param o element to be added to this set.
*/
public void add(int index, Object o) {
if ( !m_set.contains(o) )
m_set.add(index, o);
}
/** Returns the element from the specified position.
* @param index The position from which the element is to be retrieved.
* @return the element from the specified position.
*/
public Object get(int index) {
return m_set.get(index);
}
/** Remove the element from the specified position.
* @param index The position from which the element is to be removed.
* @return the element being removed.
*/
public Object remove(int index) {
return m_set.remove(index);
}
/** Returns the index of the element in this set.
* @param o The element whose index is to be found.
* @return the index of the element in this set.
*/
public int indexOf(Object o) {
return m_set.indexOf(o);
}
// *** CLONEABLE INTERFACE METHODS ***
/** Returns a clone of the Set.
* @throws CloneNotSupportedException if cloning is not supported. Should never happen.
* @return a clone of the Set.
*/
public Object clone() throws CloneNotSupportedException {
Object obj = super.clone();
if (m_set != null && m_set instanceof Cloneable)
( (ListSet) obj ).m_set = (List) ( (ArrayList) m_set ).clone();
return obj;
}
}
List ordered map
/*
* Copyright 2004, 2005, 2006 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
*
* List ordered map.
* The iterators returned by <code>keySet()</code>, <code>values()</code>
* and <code>entrySet()</code> methods reflect the order in which keys have
* been added to the map.
*
* @author Christoph Beck
*/
public class ListOrderedMap implements Map {
private final Map map;
private final List lst;
public static Map decorate(Map map) {
return new ListOrderedMap(map, new ArrayList());
}
protected ListOrderedMap(Map map, List lst) {
super();
this.map = map;
this.lst = lst;
lst.addAll(map.keySet());
}
public boolean containsKey(Object key) {
return map.containsKey(key);
}
public boolean containsValue(Object value) {
return map.containsValue(value);
}
public Object get(Object key) {
return map.get(key);
}
public boolean isEmpty() {
return map.isEmpty();
}
public int size() {
return map.size();
}
public boolean equals(Object object) {
return object == this ? true : map.equals(object);
}
public int hashCode() {
return map.hashCode();
}
public Object put(Object key, Object value) {
if (!map.containsKey(key)) {
lst.add(key);
}
return map.put(key, value);
}
public void putAll(Map map) {
Iterator it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
put(entry.getKey(), entry.getValue());
}
}
public Object remove(Object key) {
if (map.containsKey(key)) {
lst.remove(key);
return map.remove(key);
}
return null;
}
public void clear() {
map.clear();
lst.clear();
}
public Collection values() {
return new AbstractCollection() {
public int size() {
return map.size();
}
public boolean contains(Object value) {
return map.containsValue(value);
}
public void clear() {
ListOrderedMap.this.clear();
}
public Iterator iterator() {
return new Iterator() {
Object last = null;
Iterator keys = lst.iterator();
public Object next() {
return map.get(last = keys.next());
}
public boolean hasNext() {
return keys.hasNext();
}
public void remove() {
keys.remove();
map.remove(last);
}
};
}
};
}
public Set keySet() {
return new AbstractSet() {
public int size() {
return map.size();
}
public boolean contains(Object value) {
return map.containsKey(value);
}
public void clear() {
ListOrderedMap.this.clear();
}
public Iterator iterator() {
return new Iterator() {
Object last = null;
Iterator keys = lst.iterator();
public Object next() {
return last = keys.next();
}
public boolean hasNext() {
return keys.hasNext();
}
public void remove() {
keys.remove();
map.remove(last);
}
};
}
};
}
public Set entrySet() {
return new AbstractSet() {
Set delegate = ListOrderedMap.this.map.entrySet();
public int size() {
return ListOrderedMap.this.size();
}
public boolean contains(Object obj) {
return delegate.contains(obj);
}
public boolean remove(Object obj) {
boolean result = contains(obj);
if (result) {
ListOrderedMap.this.remove(((Map.Entry)obj).getKey());
}
return result;
}
public void clear() {
ListOrderedMap.this.clear();
}
public boolean equals(Object obj) {
return obj == this ? true : delegate.equals(obj);
}
public int hashCode() {
return delegate.hashCode();
}
public String toString() {
return delegate.toString();
}
public Iterator iterator() {
return new Iterator() {
Iterator keys = lst.iterator();
Object last = null;
public Object next() {
last = keys.next();
return new Map.Entry() {
Object key = last;
public Object getKey() {
return key;
}
public Object getValue() {
return map.get(key);
}
public Object setValue(Object value) {
return map.put(key, value);
}
};
}
public boolean hasNext() {
return keys.hasNext();
}
public void remove() {
keys.remove();
map.remove(last);
}
};
}
};
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("{");
Iterator keys = keySet().iterator();
while (keys.hasNext()) {
Object key = keys.next();
buf.append(key);
buf.append("=");
buf.append(get(key));
if (keys.hasNext()) {
buf.append(", ");
}
}
buf.append("}");
return buf.toString();
}
}
Lookup table that stores a list of strings
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* $Id: StringToIntTable.java 468655 2006-10-28 07:12:06Z minchau $
*/
/**
* A very simple lookup table that stores a list of strings, the even
* number strings being keys, and the odd number strings being values.
* @xsl.usage internal
*/
public class StringToIntTable
{
public static final int INVALID_KEY = -10000;
/** Block size to allocate */
private int m_blocksize;
/** Array of strings this table points to. Associated with ints
* in m_values */
private String m_map[];
/** Array of ints this table points. Associated with strings from
* m_map. */
private int m_values[];
/** Number of ints in the table */
private int m_firstFree = 0;
/** Size of this table */
private int m_mapSize;
/**
* Default constructor. Note that the default
* block size is very small, for small lists.
*/
public StringToIntTable()
{
m_blocksize = 8;
m_mapSize = m_blocksize;
m_map = new String[m_blocksize];
m_values = new int[m_blocksize];
}
/**
* Construct a StringToIntTable, using the given block size.
*
* @param blocksize Size of block to allocate
*/
public StringToIntTable(int blocksize)
{
m_blocksize = blocksize;
m_mapSize = blocksize;
m_map = new String[blocksize];
m_values = new int[m_blocksize];
}
/**
* Get the length of the list.
*
* @return the length of the list
*/
public final int getLength()
{
return m_firstFree;
}
/**
* Append a string onto the vector.
*
* @param key String to append
* @param value The int value of the string
*/
public final void put(String key, int value)
{
if ((m_firstFree + 1) >= m_mapSize)
{
m_mapSize += m_blocksize;
String newMap[] = new String[m_mapSize];
System.arraycopy(m_map, 0, newMap, 0, m_firstFree + 1);
m_map = newMap;
int newValues[] = new int[m_mapSize];
System.arraycopy(m_values, 0, newValues, 0, m_firstFree + 1);
m_values = newValues;
}
m_map[m_firstFree] = key;
m_values[m_firstFree] = value;
m_firstFree++;
}
/**
* Tell if the table contains the given string.
*
* @param key String to look for
*
* @return The String"s int value
*
*/
public final int get(String key)
{
for (int i = 0; i < m_firstFree; i++)
{
if (m_map[i].equals(key))
return m_values[i];
}
return INVALID_KEY;
}
/**
* Tell if the table contains the given string. Ignore case.
*
* @param key String to look for
*
* @return The string"s int value
*/
public final int getIgnoreCase(String key)
{
if (null == key)
return INVALID_KEY;
for (int i = 0; i < m_firstFree; i++)
{
if (m_map[i].equalsIgnoreCase(key))
return m_values[i];
}
return INVALID_KEY;
}
/**
* Tell if the table contains the given string.
*
* @param key String to look for
*
* @return True if the string is in the table
*/
public final boolean contains(String key)
{
for (int i = 0; i < m_firstFree; i++)
{
if (m_map[i].equals(key))
return true;
}
return false;
}
/**
* Return array of keys in the table.
*
* @return Array of strings
*/
public final String[] keys()
{
String [] keysArr = new String[m_firstFree];
for (int i = 0; i < m_firstFree; i++)
{
keysArr[i] = m_map[i];
}
return keysArr;
}
}
Map implementation Optimized for Strings keys
//
// Copyright 2004-2005 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import java.io.Externalizable;
import java.util.AbstractMap;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/* ------------------------------------------------------------ */
/** Map implementation Optimized for Strings keys..
* This String Map has been optimized for mapping small sets of
* Strings where the most frequently accessed Strings have been put to
* the map first.
*
* It also has the benefit that it can look up entries by substring or
* sections of char and byte arrays. This can prevent many String
* objects from being created just to look up in the map.
*
* This map is NOT synchronized.
*
* @author Greg Wilkins (gregw)
*/
public class StringMap extends AbstractMap implements Externalizable
{
public static final boolean CASE_INSENSTIVE=true;
protected static final int __HASH_WIDTH=17;
/* ------------------------------------------------------------ */
protected int _width=__HASH_WIDTH;
protected Node _root=new Node();
protected boolean _ignoreCase=false;
protected NullEntry _nullEntry=null;
protected Object _nullValue=null;
protected HashSet _entrySet=new HashSet(3);
protected Set _umEntrySet=Collections.unmodifiableSet(_entrySet);
/* ------------------------------------------------------------ */
/** Constructor.
*/
public StringMap()
{}
/* ------------------------------------------------------------ */
/** Constructor.
* @param ignoreCase
*/
public StringMap(boolean ignoreCase)
{
this();
_ignoreCase=ignoreCase;
}
/* ------------------------------------------------------------ */
/** Constructor.
* @param ignoreCase
* @param width Width of hash tables, larger values are faster but
* use more memory.
*/
public StringMap(boolean ignoreCase,int width)
{
this();
_ignoreCase=ignoreCase;
_width=width;
}
/* ------------------------------------------------------------ */
/** Set the ignoreCase attribute.
* @param ic If true, the map is case insensitive for keys.
*/
public void setIgnoreCase(boolean ic)
{
if (_root._children!=null)
throw new IllegalStateException("Must be set before first put");
_ignoreCase=ic;
}
/* ------------------------------------------------------------ */
public boolean isIgnoreCase()
{
return _ignoreCase;
}
/* ------------------------------------------------------------ */
/** Set the hash width.
* @param width Width of hash tables, larger values are faster but
* use more memory.
*/
public void setWidth(int width)
{
_width=width;
}
/* ------------------------------------------------------------ */
public int getWidth()
{
return _width;
}
/* ------------------------------------------------------------ */
public Object put(Object key, Object value)
{
if (key==null)
return put(null,value);
return put(key.toString(),value);
}
/* ------------------------------------------------------------ */
public Object put(String key, Object value)
{
if (key==null)
{
Object oldValue=_nullValue;
_nullValue=value;
if (_nullEntry==null)
{
_nullEntry=new NullEntry();
_entrySet.add(_nullEntry);
}
return oldValue;
}
Node node = _root;
int ni=-1;
Node prev = null;
Node parent = null;
// look for best match
charLoop:
for (int i=0;i<key.length();i++)
{
char c=key.charAt(i);
// Advance node
if (ni==-1)
{
parent=node;
prev=null;
ni=0;
node=(node._children==null)?null:node._children[c%_width];
}
// Loop through a node chain at the same level
while (node!=null)
{
// If it is a matching node, goto next char
if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
{
prev=null;
ni++;
if (ni==node._char.length)
ni=-1;
continue charLoop;
}
// no char match
// if the first char,
if (ni==0)
{
// look along the chain for a char match
prev=node;
node=node._next;
}
else
{
// Split the current node!
node.split(this,ni);
i--;
ni=-1;
continue charLoop;
}
}
// We have run out of nodes, so as this is a put, make one
node = new Node(_ignoreCase,key,i);
if (prev!=null) // add to end of chain
prev._next=node;
else if (parent!=null) // add new child
{
if (parent._children==null)
parent._children=new Node[_width];
parent._children[c%_width]=node;
int oi=node._ochar[0]%_width;
if (node._ochar!=null && node._char[0]%_width!=oi)
{
if (parent._children[oi]==null)
parent._children[oi]=node;
else
{
Node n=parent._children[oi];
while(n._next!=null)
n=n._next;
n._next=node;
}
}
}
else // this is the root.
_root=node;
break;
}
// Do we have a node
if (node!=null)
{
// Split it if we are in the middle
if(ni>0)
node.split(this,ni);
Object old = node._value;
node._key=key;
node._value=value;
_entrySet.add(node);
return old;
}
return null;
}
/* ------------------------------------------------------------ */
public Object get(Object key)
{
if (key==null)
return _nullValue;
if (key instanceof String)
return get((String)key);
return get(key.toString());
}
/* ------------------------------------------------------------ */
public Object get(String key)
{
if (key==null)
return _nullValue;
Map.Entry entry = getEntry(key,0,key.length());
if (entry==null)
return null;
return entry.getValue();
}
/* ------------------------------------------------------------ */
/** Get a map entry by substring key.
* @param key String containing the key
* @param offset Offset of the key within the String.
* @param length The length of the key
* @return The Map.Entry for the key or null if the key is not in
* the map.
*/
public Map.Entry getEntry(String key,int offset, int length)
{
if (key==null)
return _nullEntry;
Node node = _root;
int ni=-1;
// look for best match
charLoop:
for (int i=0;i<length;i++)
{
char c=key.charAt(offset+i);
// Advance node
if (ni==-1)
{
ni=0;
node=(node._children==null)?null:node._children[c%_width];
}
// Look through the node chain
while (node!=null)
{
// If it is a matching node, goto next char
if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
{
ni++;
if (ni==node._char.length)
ni=-1;
continue charLoop;
}
// No char match, so if mid node then no match at all.
if (ni>0) return null;
// try next in chain
node=node._next;
}
return null;
}
if (ni>0) return null;
if (node!=null && node._key==null)
return null;
return node;
}
/* ------------------------------------------------------------ */
/** Get a map entry by char array key.
* @param key char array containing the key
* @param offset Offset of the key within the array.
* @param length The length of the key
* @return The Map.Entry for the key or null if the key is not in
* the map.
*/
public Map.Entry getEntry(char[] key,int offset, int length)
{
if (key==null)
return _nullEntry;
Node node = _root;
int ni=-1;
// look for best match
charLoop:
for (int i=0;i<length;i++)
{
char c=key[offset+i];
// Advance node
if (ni==-1)
{
ni=0;
node=(node._children==null)?null:node._children[c%_width];
}
// While we have a node to try
while (node!=null)
{
// If it is a matching node, goto next char
if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
{
ni++;
if (ni==node._char.length)
ni=-1;
continue charLoop;
}
// No char match, so if mid node then no match at all.
if (ni>0) return null;
// try next in chain
node=node._next;
}
return null;
}
if (ni>0) return null;
if (node!=null && node._key==null)
return null;
return node;
}
/* ------------------------------------------------------------ */
/** Get a map entry by byte array key, using as much of the passed key as needed for a match.
* A simple 8859-1 byte to char mapping is assumed.
* @param key char array containing the key
* @param offset Offset of the key within the array.
* @param maxLength The length of the key
* @return The Map.Entry for the key or null if the key is not in
* the map.
*/
public Map.Entry getBestEntry(byte[] key,int offset, int maxLength)
{
if (key==null)
return _nullEntry;
Node node = _root;
int ni=-1;
// look for best match
charLoop:
for (int i=0;i<maxLength;i++)
{
char c=(char)key[offset+i];
// Advance node
if (ni==-1)
{
ni=0;
Node child = (node._children==null)?null:node._children[c%_width];
if (child==null && i>0)
return node; // This is the best match
node=child;
}
// While we have a node to try
while (node!=null)
{
// If it is a matching node, goto next char
if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
{
ni++;
if (ni==node._char.length)
ni=-1;
continue charLoop;
}
// No char match, so if mid node then no match at all.
if (ni>0) return null;
// try next in chain
node=node._next;
}
return null;
}
if (ni>0) return null;
if (node!=null && node._key==null)
return null;
return node;
}
/* ------------------------------------------------------------ */
public Object remove(Object key)
{
if (key==null)
return remove(null);
return remove(key.toString());
}
/* ------------------------------------------------------------ */
public Object remove(String key)
{
if (key==null)
{
Object oldValue=_nullValue;
if (_nullEntry!=null)
{
_entrySet.remove(_nullEntry);
_nullEntry=null;
_nullValue=null;
}
return oldValue;
}
Node node = _root;
int ni=-1;
// look for best match
charLoop:
for (int i=0;i<key.length();i++)
{
char c=key.charAt(i);
// Advance node
if (ni==-1)
{
ni=0;
node=(node._children==null)?null:node._children[c%_width];
}
// While we have a node to try
while (node!=null)
{
// If it is a matching node, goto next char
if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
{
ni++;
if (ni==node._char.length)
ni=-1;
continue charLoop;
}
// No char match, so if mid node then no match at all.
if (ni>0) return null;
// try next in chain
node=node._next;
}
return null;
}
if (ni>0) return null;
if (node!=null && node._key==null)
return null;
Object old = node._value;
_entrySet.remove(node);
node._value=null;
node._key=null;
return old;
}
/* ------------------------------------------------------------ */
public Set entrySet()
{
return _umEntrySet;
}
/* ------------------------------------------------------------ */
public int size()
{
return _entrySet.size();
}
/* ------------------------------------------------------------ */
public boolean isEmpty()
{
return _entrySet.isEmpty();
}
/* ------------------------------------------------------------ */
public boolean containsKey(Object key)
{
if (key==null)
return _nullEntry!=null;
return
getEntry(key.toString(),0,key==null?0:key.toString().length())!=null;
}
/* ------------------------------------------------------------ */
public void clear()
{
_root=new Node();
_nullEntry=null;
_nullValue=null;
_entrySet.clear();
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
private static class Node implements Map.Entry
{
char[] _char;
char[] _ochar;
Node _next;
Node[] _children;
String _key;
Object _value;
Node(){}
Node(boolean ignoreCase,String s, int offset)
{
int l=s.length()-offset;
_char=new char[l];
_ochar=new char[l];
for (int i=0;i<l;i++)
{
char c=s.charAt(offset+i);
_char[i]=c;
if (ignoreCase)
{
char o=c;
if (Character.isUpperCase(c))
o=Character.toLowerCase(c);
else if (Character.isLowerCase(c))
o=Character.toUpperCase(c);
_ochar[i]=o;
}
}
}
Node split(StringMap map,int offset)
{
Node split = new Node();
int sl=_char.length-offset;
char[] tmp=this._char;
this._char=new char[offset];
split._char = new char[sl];
System.arraycopy(tmp,0,this._char,0,offset);
System.arraycopy(tmp,offset,split._char,0,sl);
if (this._ochar!=null)
{
tmp=this._ochar;
this._ochar=new char[offset];
split._ochar = new char[sl];
System.arraycopy(tmp,0,this._ochar,0,offset);
System.arraycopy(tmp,offset,split._ochar,0,sl);
}
split._key=this._key;
split._value=this._value;
this._key=null;
this._value=null;
if (map._entrySet.remove(this))
map._entrySet.add(split);
split._children=this._children;
this._children=new Node[map._width];
this._children[split._char[0]%map._width]=split;
if (split._ochar!=null && this._children[split._ochar[0]%map._width]!=split)
this._children[split._ochar[0]%map._width]=split;
return split;
}
public Object getKey(){return _key;}
public Object getValue(){return _value;}
public Object setValue(Object o){Object old=_value;_value=o;return old;}
public String toString()
{
StringBuffer buf=new StringBuffer();
synchronized(buf)
{
toString(buf);
}
return buf.toString();
}
private void toString(StringBuffer buf)
{
buf.append("{[");
if (_char==null)
buf.append("-");
else
for (int i=0;i<_char.length;i++)
buf.append(_char[i]);
buf.append(":");
buf.append(_key);
buf.append("=");
buf.append(_value);
buf.append("]");
if (_children!=null)
{
for (int i=0;i<_children.length;i++)
{
buf.append("|");
if (_children[i]!=null)
_children[i].toString(buf);
else
buf.append("-");
}
}
buf.append("}");
if (_next!=null)
{
buf.append(",\n");
_next.toString(buf);
}
}
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
private class NullEntry implements Map.Entry
{
public Object getKey(){return null;}
public Object getValue(){return _nullValue;}
public Object setValue(Object o)
{Object old=_nullValue;_nullValue=o;return old;}
public String toString(){return "[:null="+_nullValue+"]";}
}
/* ------------------------------------------------------------ */
public void writeExternal(java.io.ObjectOutput out)
throws java.io.IOException
{
HashMap map = new HashMap(this);
out.writeBoolean(_ignoreCase);
out.writeObject(map);
}
/* ------------------------------------------------------------ */
public void readExternal(java.io.ObjectInput in)
throws java.io.IOException, ClassNotFoundException
{
boolean ic=in.readBoolean();
HashMap map = (HashMap)in.readObject();
setIgnoreCase(ic);
this.putAll(map);
}
}
Map using Locale objects as keys
/*
* Copyright 2004 Outerthought bvba and Schaubroeck nv
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
The Apache Software License, Version 1.1
Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.
Redistribution and use in source and binary forms, with or without modifica-
tion, 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 acknowledgment: "This product includes software
developed by the Apache Software Foundation (http://www.apache.org/)."
Alternately, this acknowledgment may appear in the software itself, if
and wherever such third-party acknowledgments normally appear.
4. The names "Apache Cocoon" 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 name, 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 (INCLU-
DING, 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 and was originally created by
Stefano Mazzocchi <stefano@apache.org>. For more information on the Apache
Software Foundation, please see <http://www.apache.org/>.
*/
import java.util.Locale;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
/**
* Map using Locale objects as keys.
*
* <b>This class is based on code from Apache Cocoon.</b>
*
* <p>This map should be filled once using calls to {@link #put}, before any calls
* are made to {@link #get}.
*
*/
public class LocaleMap {
/** Contains the original values the have been put in the map. */
protected ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<String, Object>();
/** Contains "resolved" locales for quick lookup. */
protected ConcurrentHashMap<String, Object> searchMap = new ConcurrentHashMap<String, Object>();
private static final String NO_RESULT = "(no result: you should never see this)";
/**
* Gets an object based on the given locale. An automatic fallback mechanism is used:
* if nothing is found for language-COUNTRY-variant, then language-COUNTRY is searched,
* the language, and finally "" (empty string). If nothing is found null is returned.
*/
public Object get(Locale locale) {
if (map.size() == 0)
return null;
String full = getFullKey(locale);
if (!searchMap.containsKey(full)) {
if (map.containsKey(full)) {
Object object = map.get(full);
searchMap.put(full, object);
return object;
}
String altKey = locale.getLanguage() + "-" + locale.getCountry();
Object object = map.get(altKey);
if (object != null) {
searchMap.put(full, object);
return object;
}
altKey = locale.getLanguage();
object = map.get(altKey);
if (object != null) {
searchMap.put(full, object);
return object;
}
object = map.get("");
if (object != null) {
searchMap.put(full, object);
return object;
}
searchMap.put(full, NO_RESULT);
}
Object result = searchMap.get(full);
return result == NO_RESULT ? null : result;
}
public Object getExact(Locale locale) {
return map.get(getString(locale));
}
public void clear() {
map.clear();
searchMap.clear();
}
public void remove(Locale locale) {
put(locale, null);
}
public Locale[] getLocales() {
String[] localeNames = map.keySet().toArray(new String[0]);
Locale[] locales = new Locale[localeNames.length];
for (int i = 0; i < locales.length; i++)
locales[i] = parseLocale(localeNames[i]);
return locales;
}
public Set entrySet() {
return map.entrySet();
}
private String getFullKey(Locale locale) {
return locale.getLanguage() + "-" + locale.getCountry() + "-" + locale.getVariant();
}
public void put(Locale locale, Object object) {
if (object == null)
map.remove(getString(locale));
else
map.put(getString(locale), object);
searchMap.clear();
}
public boolean isEmpty() {
return map.isEmpty();
}
public boolean equals(Object obj) {
if (!(obj instanceof LocaleMap))
return false;
LocaleMap other = (LocaleMap)obj;
// map can do equals
return map.equals(other.map);
}
public static Locale parseLocale(String localeString) {
StringTokenizer localeParser = new StringTokenizer(localeString, "-_");
String lang = null, country = null, variant = null;
if (localeParser.hasMoreTokens())
lang = localeParser.nextToken();
if (localeParser.hasMoreTokens())
country = localeParser.nextToken();
if (localeParser.hasMoreTokens())
variant = localeParser.nextToken();
if (lang != null && country != null && variant != null)
return new Locale(lang, country, variant);
else if (lang != null && country != null)
return new Locale(lang, country);
else if (lang != null)
return new Locale(lang);
else
return new Locale("");
}
public static String getString(Locale locale) {
boolean hasLanguage = !locale.getLanguage().equals("");
boolean hasCountry = !locale.getCountry().equals("");
boolean hasVariant = !locale.getVariant().equals("");
if (hasLanguage && hasCountry && hasVariant)
return locale.getLanguage() + "-" + locale.getCountry() + "-" + locale.getVariant();
else if (hasLanguage && hasCountry)
return locale.getLanguage() + "-" + locale.getCountry();
else if (hasLanguage)
return locale.getLanguage();
else
return "";
}
}
Map with keys iterated in insertion order
/*
Copyright (c) 2007, Dennis M. Sosnoski
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 of JiBX 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.
*/
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* Map with keys iterated in insertion order. This is similar to the Java 1.4
* java.util.LinkedHashMap class, but compatible with earlier JVM versions. It
* also guarantees insertion ordering only for iterating through the key values,
* not for other iterations. This implementation is optimized for insert-only
* maps.
*/
public class InsertionOrderedMap implements Map
{
private final Map m_baseMap;
private final ArrayList m_insertList;
public InsertionOrderedMap() {
m_baseMap = new HashMap();
m_insertList = new ArrayList();
}
/* (non-Javadoc)
* @see java.util.Map#clear()
*/
public void clear() {
m_baseMap.clear();
m_insertList.clear();
}
/* (non-Javadoc)
* @see java.util.Map#containsKey(java.lang.Object)
*/
public boolean containsKey(Object key) {
return m_baseMap.containsKey(key);
}
/* (non-Javadoc)
* @see java.util.Map#containsValue(java.lang.Object)
*/
public boolean containsValue(Object value) {
return m_baseMap.containsValue(value);
}
/* (non-Javadoc)
* @see java.util.Map#entrySet()
*/
public Set entrySet() {
return m_baseMap.entrySet();
}
/* (non-Javadoc)
* @see java.util.Map#get(java.lang.Object)
*/
public Object get(Object key) {
return m_baseMap.get(key);
}
/* (non-Javadoc)
* @see java.util.Map#isEmpty()
*/
public boolean isEmpty() {
return m_baseMap.isEmpty();
}
/* (non-Javadoc)
* @see java.util.Map#keySet()
*/
public Set keySet() {
return new ListSet(m_insertList);
}
/* (non-Javadoc)
* @see java.util.Map#put(java.lang.Object, java.lang.Object)
*/
public Object put(Object key, Object value) {
if (!m_baseMap.containsKey(key)) {
m_insertList.add(key);
}
return m_baseMap.put(key, value);
}
/* (non-Javadoc)
* @see java.util.Map#putAll(java.util.Map)
*/
public void putAll(Map t) {
for (Iterator iter = t.entrySet().iterator(); iter.hasNext();) {
Entry entry = (Entry)iter.next();
put(entry.getKey(), entry.getValue());
}
}
/* (non-Javadoc)
* @see java.util.Map#remove(java.lang.Object)
*/
public Object remove(Object key) {
if (m_baseMap.containsKey(key)) {
m_insertList.remove(key);
return m_baseMap.remove(key);
} else {
return null;
}
}
/* (non-Javadoc)
* @see java.util.Map#size()
*/
public int size() {
return m_baseMap.size();
}
/* (non-Javadoc)
* @see java.util.Map#values()
*/
public Collection values() {
return new ValueCollection();
}
/**
* Get list of keys in order added. The returned list is live, and will
* grow or shrink as pairs are added to or removed from the map.
*
* @return key list
*/
public ArrayList keyList() {
return m_insertList;
}
/**
* Set implementation backed by a list.
*/
protected static class ListSet extends AbstractSet
{
private final List m_list;
public ListSet(List list) {
m_list = list;
}
public Iterator iterator() {
return m_list.iterator();
}
public int size() {
return m_list.size();
}
}
protected class ValueCollection extends AbstractCollection
{
public Iterator iterator() {
return new ValueIterator();
}
public int size() {
return m_insertList.size();
}
}
protected class ValueIterator implements Iterator
{
private int m_index = -1;
public boolean hasNext() {
return m_index < m_insertList.size()-1;
}
public Object next() {
if (m_index < m_insertList.size()-1) {
Object key = m_insertList.get(++m_index);
return m_baseMap.get(key);
} else {
throw new NoSuchElementException("Past end of list");
}
}
public void remove() {
throw new UnsupportedOperationException("Internal error - remove() not supported");
}
}
}
Most Recently Used Map
/*
* $Id: MostRecentlyUsedMap.java 457812 2005-10-06 21:20:34Z jdonnerstag $
* $Revision: 457812 $ $Date: 2005-10-06 23:20:34 +0200 (Thu, 06 Oct 2005) $
*
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Holds a map of most recently used items of a given maximum size. Old entries
* are expired when the map exceeds that maximum size.
*
* @author Jonathan Locke
*/
public class MostRecentlyUsedMap extends LinkedHashMap
{
private static final long serialVersionUID = 1L;
/** Value most recently removed from map */
Object removedValue;
/** Maximum number of entries allowed in this map */
private int maxEntries;
/**
* Constructor
*
* @param maxEntries
* Maximum number of entries allowed in the map
*/
public MostRecentlyUsedMap(final int maxEntries)
{
super(10, 0.75f, true);
if (maxEntries <= 0)
{
throw new IllegalArgumentException("Must have at least one entry");
}
this.maxEntries = maxEntries;
}
/**
* @return Returns the removedValue.
*/
public Object getRemovedValue()
{
return removedValue;
}
/**
* @see java.util.LinkedHashMap#removeEldestEntry(java.util.Map.Entry)
*/
protected boolean removeEldestEntry(final Map.Entry eldest)
{
final boolean remove = size() > maxEntries;
// when it should be removed remember the oldest value that will be removed
if (remove)
{
this.removedValue = eldest.getValue();
}
else
{
removedValue = null;
}
return remove;
}
}
Multi Map
/*
* FindBugs - Find Bugs in Java programs
* Copyright (C) 2006, University of Maryland
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* @author pugh
*/
public class MultiMap<K, V> {
final Class<? extends Collection<V>> containerClass;
@SuppressWarnings("unchecked")
public MultiMap(Class<? extends Collection> c) {
containerClass = (Class<? extends Collection<V>>) c;
}
private Collection<V> makeCollection() {
try {
return containerClass.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
Map<K, Collection<V>> map = new HashMap<K, Collection<V>>();
public Collection<? extends K> keySet() {
return map.keySet();
}
public void clear() {
map.clear();
}
public void add(K k, V v) {
Collection<V> s = map.get(k);
if (s == null) {
s = makeCollection();
map.put(k, s);
}
s.add(v);
}
public void remove(K k, V v) {
Collection<V> s = map.get(k);
if (s != null) {
s.remove(v);
if (s.isEmpty()) map.remove(k);
}
}
public void removeAll(K k) {
map.remove(k);
}
public Collection<V> get(K k) {
Collection<V> s = map.get(k);
if (s != null) return s;
return Collections.<V>emptySet();
}
}
MultiMap is a Java version of the C++ STL class std::multimap
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
*
* MultiMap is a Java version of the C++ STL class std::multimap. It allows
* users to associate more than one value with a unique key. Each key points
* to a collection of values that can be traversed independently of the map.
* The integrity of the map is such that it only protects the key values, not
* the values they point to.
* <br><br>
* All entries into the multimap are (Object, java.util.Collection), where
* each key object must be unique.
*
* @author Dan Jemiolo (danj)
*
*/
public final class MultiMap extends HashMap
{
private static final long serialVersionUID = -9145022530624375133L;
//
// The Collection class that holds the values
//
private Class _collectionClass = null;
//
// The number of KEYS in the map - we need to track this ourselves
// because size() is overridden to return the number of VALUES (the
// number of values is the sum of Collection.size()).
//
private int _keySize = 0;
/**
*
* The default constructor creates a new map using a HashSet as the
* value collection type.
*
* @see MultiMap#MultiMap(Class)
*
*/
public MultiMap()
{
this(HashSet.class);
}
/**
*
* @param collectionClass
* The Collection type to use for the map"s values. The Class
* must be an implementation of java.util.Collection.
*
*/
public MultiMap(Class collectionClass)
{
_collectionClass = collectionClass;
}
public final void clear()
{
super.clear();
_keySize = 0;
}
/**
*
* @param value
*
* @return True if one or more of the keys in this map points to the
* given value.
*
*/
public final boolean containsValue(Object value)
{
Iterator i = values().iterator();
//
// look through each map entry"s set of values to see if
// the given value is present
//
while (i.hasNext())
{
Collection values = (Collection)i.next();
if (values.contains(value))
return true;
}
return false;
}
/**
*
* @return The number of keys in the map, <b>not</b> the number of
* entries.
*
* @see #size()
*
*/
public final int keySetSize()
{
return _keySize;
}
/**
*
* Associates the value with the given key. If the key is not already in
* the map, it is added; otherwise, the value is simply added to the
* key"s collection.
*
* @return The value reference on success or null if the value was
* already associated with the key.
*
*/
public final Object put(Object key, Object value)
{
Collection values = (Collection)get(key);
//
// if the key wasn"t found - add it!
//
if (values == null)
{
values = (Collection)newInstance(_collectionClass);
super.put(key, values);
++_keySize;
}
//
// try to add the value to the key"s set
//
boolean success = values.add(value);
return success ? value : null;
}
/**
*
* Invokes the Class.newInstance() method on the given Class.
*
* @param theClass
* The type to instantiate.
*
* @return An object of the given type, created with the default
* constructor. A RuntimeException is thrown if the object could not
* be created.
*
*/
public static Object newInstance(Class theClass)
{
try
{
return theClass.newInstance();
}
catch (InstantiationException error)
{
Object[] filler = { theClass };
String message = "ObjectCreationFailed";
throw new RuntimeException(message);
}
catch (IllegalAccessException error)
{
Object[] filler = { theClass };
String message = "DefaultConstructorHidden";
throw new RuntimeException(message);
}
}
/**
*
* Adds all of the entries in a generic map to the multimap.
* <br><br>
* NOTE: Because we cannot guarantee that the implementation of the
* base class putAll(Map) simply calls the put method in a loop, we must
* override it here to ensure this happens. Otherwise, HashMap might
* associate the values directly with the keys, breaking the unofficial
* key -> Collection system.
*
* @param map
* The Map to copy - this does not have to be another MultiMap.
*
*/
public final void putAll(Map map)
{
Set keys = map.keySet();
Iterator i = keys.iterator();
//
// if the argument is a multi-map, we want to add all of the
// values individually, otherwise each key will have a set
// of values whose only value is... the collection of values.
// that is, instead of key -> collection<value>, we"d have
// key -> collection<collection<value>>
//
if (map instanceof MultiMap)
{
while (i.hasNext())
{
Object key = i.next();
Object value = map.get(key);
Collection setOfValues = (Collection)value;
Iterator j = setOfValues.iterator();
while (j.hasNext())
put(key, j.next());
}
}
//
// it"s a "normal" map - just add the name-value pairs
//
else
{
while (i.hasNext())
{
Object key = i.next();
put(key, map.get(key));
}
}
}
/**
*
* Removes <b>all</b> values associated with the given key.
*
* @param key
*
* @return The Collection of values previously associated with the key.
*
*/
public final Object remove(Object key)
{
Object value = super.remove(key);
--_keySize;
return value;
}
/**
*
* NOTE: This method takes O(n) time, where n is the number of keys in
* the map. It would be more efficient to keep a counter for the size,
* but this would require overriding more methods and dealing with the
* complicated issue of map integrity and entrySet(). This implementation
* is the most robust when you consider that all Maps allow users to
* modify their contents without using the interface directly.
*
* @return The sum of the sizes of all the map entries (value collections).
*
*/
public final int size()
{
Iterator i = values().iterator();
int count = 0;
//
// for each key, add the number of values to the count
//
while (i.hasNext())
{
Collection values = (Collection)i.next();
count += values.size();
}
return count;
}
}
Object Int Map
/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
*
* This program and the accompanying materials are made available under
* the terms of the Common Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/cpl-v10.html
*
* $Id: ObjectIntMap.java,v 1.1.1.1 2004/05/09 16:57:54 vlad_r Exp $
*/
// ----------------------------------------------------------------------------
/**
*
* MT-safety: an instance of this class is <I>not</I> safe for access from
* multiple concurrent threads [even if access is done by a single thread at a
* time]. The caller is expected to synchronize externally on an instance [the
* implementation does not do internal synchronization for the sake of efficiency].
* java.util.ConcurrentModificationException is not supported either.
*
* @author (C) 2001, Vlad Roubtsov
*/
public
final class ObjectIntMap
{
// public: ................................................................
// TODO: optimize key comparisons using key.hash == entry.key.hash condition
/**
* Equivalent to <CODE>IntObjectMap(11, 0.75F)</CODE>.
*/
public ObjectIntMap ()
{
this (11, 0.75F);
}
/**
* Equivalent to <CODE>IntObjectMap(capacity, 0.75F)</CODE>.
*/
public ObjectIntMap (final int initialCapacity)
{
this (initialCapacity, 0.75F);
}
/**
* Constructs an IntObjectMap with specified initial capacity and load factor.
*
* @param initialCapacity initial number of hash buckets in the table [may not be negative, 0 is equivalent to 1].
* @param loadFactor the load factor to use to determine rehashing points [must be in (0.0, 1.0] range].
*/
public ObjectIntMap (int initialCapacity, final float loadFactor)
{
if (initialCapacity < 0) throw new IllegalArgumentException ("negative input: initialCapacity [" + initialCapacity + "]");
if ((loadFactor <= 0.0) || (loadFactor >= 1.0 + 1.0E-6))
throw new IllegalArgumentException ("loadFactor not in (0.0, 1.0] range: " + loadFactor);
if (initialCapacity == 0) initialCapacity = 1;
m_loadFactor = loadFactor > 1.0 ? 1.0F : loadFactor;
m_sizeThreshold = (int) (initialCapacity * loadFactor);
m_buckets = new Entry [initialCapacity];
}
/**
* Overrides Object.toString() for debug purposes.
*/
public String toString ()
{
final StringBuffer s = new StringBuffer ();
debugDump (s);
return s.toString ();
}
/**
* Returns the number of key-value mappings in this map.
*/
public int size ()
{
return m_size;
}
public boolean contains (final Object key)
{
// index into the corresponding hash bucket:
final Entry [] buckets = m_buckets;
final int keyHash = key.hashCode ();
final int bucketIndex = (keyHash & 0x7FFFFFFF) % buckets.length;
// traverse the singly-linked list of entries in the bucket:
for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
{
if ((keyHash == entry.m_key.hashCode ()) || entry.m_key.equals (key))
return true;
}
return false;
}
/**
* Returns the value that is mapped to a given "key". Returns
* false if this key has never been mapped.
*
* @param key mapping key [may not be null]
* @param out holder for the found value [must be at least of size 1]
*
* @return "true" if this key was mapped to an existing value
*/
public boolean get (final Object key, final int [] out)
{
// index into the corresponding hash bucket:
final Entry [] buckets = m_buckets;
final int keyHash = key.hashCode ();
final int bucketIndex = (keyHash & 0x7FFFFFFF) % buckets.length;
// traverse the singly-linked list of entries in the bucket:
for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
{
if ((keyHash == entry.m_key.hashCode ()) || entry.m_key.equals (key))
{
out [0] = entry.m_value;
return true;
}
}
return false;
}
public Object [] keys ()
{
final Object [] result = new Object [m_size];
int scan = 0;
for (int b = 0; b < m_buckets.length; ++ b)
{
for (Entry entry = m_buckets [b]; entry != null; entry = entry.m_next)
{
result [scan ++] = entry.m_key;
}
}
return result;
}
/**
* Updates the table to map "key" to "value". Any existing mapping is overwritten.
*
* @param key mapping key [may not be null]
* @param value mapping value.
*/
public void put (final Object key, final int value)
{
Entry currentKeyEntry = null;
// detect if "key" is already in the table [in which case, set "currentKeyEntry" to point to its entry]:
// index into the corresponding hash bucket:
final int keyHash = key.hashCode ();
int bucketIndex = (keyHash & 0x7FFFFFFF) % m_buckets.length;
// traverse the singly-linked list of entries in the bucket:
Entry [] buckets = m_buckets;
for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
{
if ((keyHash == entry.m_key.hashCode ()) || entry.m_key.equals (key))
{
currentKeyEntry = entry;
break;
}
}
if (currentKeyEntry != null)
{
// replace the current value:
currentKeyEntry.m_value = value;
}
else
{
// add a new entry:
if (m_size >= m_sizeThreshold) rehash ();
buckets = m_buckets;
bucketIndex = (keyHash & 0x7FFFFFFF) % buckets.length;
final Entry bucketListHead = buckets [bucketIndex];
final Entry newEntry = new Entry (key, value, bucketListHead);
buckets [bucketIndex] = newEntry;
++ m_size;
}
}
/**
* Updates the table to map "key" to "value". Any existing mapping is overwritten.
*
* @param key mapping key [may not be null]
*/
public void remove (final Object key)
{
// index into the corresponding hash bucket:
final int keyHash = key.hashCode ();
final int bucketIndex = (keyHash & 0x7FFFFFFF) % m_buckets.length;
// traverse the singly-linked list of entries in the bucket:
Entry [] buckets = m_buckets;
for (Entry entry = buckets [bucketIndex], prev = entry; entry != null; )
{
final Entry next = entry.m_next;
if ((keyHash == entry.m_key.hashCode ()) || entry.m_key.equals (key))
{
if (prev == entry)
buckets [bucketIndex] = next;
else
prev.m_next = next;
-- m_size;
break;
}
prev = entry;
entry = next;
}
}
// protected: .............................................................
// package: ...............................................................
void debugDump (final StringBuffer out)
{
if (out != null)
{
out.append (super.toString ()); out.append (EOL);
out.append ("size = " + m_size + ", bucket table size = " + m_buckets.length + ", load factor = " + m_loadFactor + EOL);
out.append ("size threshold = " + m_sizeThreshold + EOL);
}
}
// private: ...............................................................
/**
* The structure used for chaining colliding keys.
*/
private static final class Entry
{
Entry (final Object key, final int value, final Entry next)
{
m_key = key;
m_value = value;
m_next = next;
}
Object m_key; // reference to the value [never null]
int m_value;
Entry m_next; // singly-linked list link
} // end of nested class
/**
* Re-hashes the table into a new array of buckets.
*/
private void rehash ()
{
// TODO: it is possible to run this method twice, first time using the 2*k+1 prime sequencer for newBucketCount
// and then with that value reduced to actually shrink capacity. As it is right now, the bucket table can
// only grow in size
final Entry [] buckets = m_buckets;
final int newBucketCount = (m_buckets.length << 1) + 1;
final Entry [] newBuckets = new Entry [newBucketCount];
// rehash all entry chains in every bucket:
for (int b = 0; b < buckets.length; ++ b)
{
for (Entry entry = buckets [b]; entry != null; )
{
final Entry next = entry.m_next; // remember next pointer because we are going to reuse this entry
final int entryKeyHash = entry.m_key.hashCode () & 0x7FFFFFFF;
// index into the corresponding new hash bucket:
final int newBucketIndex = entryKeyHash % newBucketCount;
final Entry bucketListHead = newBuckets [newBucketIndex];
entry.m_next = bucketListHead;
newBuckets [newBucketIndex] = entry;
entry = next;
}
}
m_sizeThreshold = (int) (newBucketCount * m_loadFactor);
m_buckets = newBuckets;
}
private final float m_loadFactor; // determines the setting of m_sizeThreshold
private Entry [] m_buckets; // table of buckets
private int m_size; // number of keys in the table, not cleared as of last check
private int m_sizeThreshold; // size threshold for rehashing
private static final String EOL = System.getProperty ("line.separator", "\n");
} // end of class
// ----------------------------------------------------------------------------
Ordered Map
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* This is a simple Map implementation backed by a List of Map.Entry objects.
* It has the property that iterators return entries in the order in whick
* they were inserted.
*
* Operations involving searching, including get() and put(), have cost linear
* to the size of the map. In other words, avoid this implementation if your
* Map might get large.
*
* If we could assume Java 1.4+, we"d just use java.util.LinkedHashMap
* instead of this class. But we can"t.
*
* @author Mike Williams
*/
public class OrderedMap extends AbstractMap {
private Set _entrySet = new OrderedSet();
public Set entrySet() {
return _entrySet;
}
public Object put(Object key, Object value) {
Entry existingEntry = getEntryWithKey(key);
if (existingEntry == null) {
entrySet().add(new Entry(key, value));
return null;
}
Object previousValue = existingEntry.getValue();
existingEntry.setValue(value);
return previousValue;
}
private Entry getEntryWithKey(Object key) {
Iterator i = entrySet().iterator();
while (i.hasNext()) {
Entry e = (Entry) i.next();
if (eq(e.getKey(), key)) {
return e;
}
}
return null;
}
static class OrderedSet extends AbstractSet {
private List _elementList = new LinkedList();
public int size() {
return _elementList.size();
}
public Iterator iterator() {
return _elementList.iterator();
}
public boolean add(Object o) {
_elementList.add(o);
return true;
}
}
static class Entry implements Map.Entry {
Object _key;
Object _value;
public Entry(Object key, Object value) {
_key = key;
_value = value;
}
public Object getKey() {
return _key;
}
public Object getValue() {
return _value;
}
public Object setValue(Object value) {
Object oldValue = _value;
_value = value;
return oldValue;
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry)) {
return false;
}
Map.Entry e = (Map.Entry) o;
return eq(_key, e.getKey()) && eq(_value, e.getValue());
}
public int hashCode() {
return ((_key == null) ? 0 : _key.hashCode()) ^
((_value == null) ? 0 : _value.hashCode());
}
public String toString() {
return _key + "=" + _value;
}
}
private static boolean eq(Object o1, Object o2) {
return (o1 == null ? o2 == null : o1.equals(o2));
}
}
Sequenced HashMap
/*
* $Header: /home/projects/aspectwerkz/scm/aspectwerkz4/src/main/org/codehaus/aspectwerkz/util/SequencedHashMap.java,v 1.3 2004/10/22 12:40:40 avasseur Exp $
* $Revision: 1.3 $
* $Date: 2004/10/22 12:40:40 $
*
*
*
* The Apache Software License, Version 1.1
*
* Copyright (c) 1999-2002 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 acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements 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 Group.
*
* 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
* <http://www.apache.org/>.
*
*/
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* A map of objects whose mapping entries are sequenced based on the order in which they were added. This data structure
* has fast <I>O(1) </I> search time, deletion time, and insertion time. <p/>
* <P>
* Although this map is sequenced, it cannot implement {@link java.util.List}because of incompatible interface
* definitions. The remove methods in List and Map have different return values (see:
* {@linkjava.util.List#remove(Object)}and {@link java.util.Map#remove(Object)}).<p/>
* <P>
* This class is not thread safe. When a thread safe implementation is required, use {@link
* Collections#synchronizedMap(Map)} as it is documented, or use explicit synchronization controls.
*
* @author
* @since 2.0
*/
public class SequencedHashMap implements Map, Cloneable, Externalizable {
// constants to define what the iterator should return on "next"
private static final int KEY = 0;
private static final int VALUE = 1;
private static final int ENTRY = 2;
private static final int REMOVED_MASK = 0x80000000;
// add a serial version uid, so that if we change things in the future
// without changing the format, we can still deserialize properly.
private static final long serialVersionUID = 3380552487888102930L;
/**
* Sentinel used to hold the head and tail of the list of entries.
*/
private Entry sentinel;
/**
* Map of keys to entries
*/
private HashMap entries;
/**
* Holds the number of modifications that have occurred to the map, excluding modifications made through a
* collection view"s iterator (e.g. entrySet().iterator().remove()). This is used to create a fail-fast behavior
* with the iterators.
*/
private transient long modCount = 0;
/**
* Construct a new sequenced hash map with default initial size and load factor.
*/
public SequencedHashMap() {
sentinel = createSentinel();
entries = new HashMap();
}
/**
* Construct a new sequenced hash map with the specified initial size and default load factor.
*
* @param initialSize the initial size for the hash table
* @see HashMap#HashMap(int)
*/
public SequencedHashMap(int initialSize) {
sentinel = createSentinel();
entries = new HashMap(initialSize);
}
/**
* Construct a new sequenced hash map with the specified initial size and load factor.
*
* @param initialSize the initial size for the hash table
* @param loadFactor the load factor for the hash table.
* @see HashMap#HashMap(int,float)
*/
public SequencedHashMap(int initialSize, float loadFactor) {
sentinel = createSentinel();
entries = new HashMap(initialSize, loadFactor);
}
/**
* Construct a new sequenced hash map and add all the elements in the specified map. The order in which the mappings
* in the specified map are added is defined by {@link #putAll(Map)}.
*/
public SequencedHashMap(Map m) {
this();
putAll(m);
}
/**
* Construct an empty sentinel used to hold the head (sentinel.next) and the tail (sentinel.prev) of the list. The
* sentinal has a <code>null</code> key and value.
*/
private static final Entry createSentinel() {
Entry s = new Entry(null, null);
s.prev = s;
s.next = s;
return s;
}
/**
* Removes an internal entry from the linked list. This does not remove it from the underlying map.
*/
private void removeEntry(Entry entry) {
entry.next.prev = entry.prev;
entry.prev.next = entry.next;
}
/**
* Inserts a new internal entry to the tail of the linked list. This does not add the entry to the underlying map.
*/
private void insertEntry(Entry entry) {
entry.next = sentinel;
entry.prev = sentinel.prev;
sentinel.prev.next = entry;
sentinel.prev = entry;
}
// per Map.size()
/**
* Implements {@link Map#size()}.
*/
public int size() {
// use the underlying Map"s size since size is not maintained here.
return entries.size();
}
/**
* Implements {@link Map#isEmpty()}.
*/
public boolean isEmpty() {
// for quick check whether the map is entry, we can check the linked list
// and see if there"s anything in it.
return sentinel.next == sentinel;
}
/**
* Implements {@link Map#containsKey(Object)}.
*/
public boolean containsKey(Object key) {
// pass on to underlying map implementation
return entries.containsKey(key);
}
/**
* Implements {@link Map#containsValue(Object)}.
*/
public boolean containsValue(Object value) {
// unfortunately, we cannot just pass this call to the underlying map
// because we are mapping keys to entries, not keys to values. The
// underlying map doesn"t have an efficient implementation anyway, so this
// isn"t a big deal.
// do null comparison outside loop so we only need to do it once. This
// provides a tighter, more efficient loop at the expense of slight
// code duplication.
if (value == null) {
for (Entry pos = sentinel.next; pos != sentinel; pos = pos.next) {
if (pos.getValue() == null) {
return true;
}
}
} else {
for (Entry pos = sentinel.next; pos != sentinel; pos = pos.next) {
if (value.equals(pos.getValue())) {
return true;
}
}
}
return false;
}
/**
* Implements {@link Map#get(Object)}.
*/
public Object get(Object o) {
// find entry for the specified key object
Entry entry = (Entry) entries.get(o);
if (entry == null) {
return null;
}
return entry.getValue();
}
/**
* Return the entry for the "oldest" mapping. That is, return the Map.Entry for the key-value pair that was first
* put into the map when compared to all the other pairings in the map. This behavior is equivalent to using
* <code>entrySet().iterator().next()</code>, but this method provides an optimized implementation.
*
* @return The first entry in the sequence, or <code>null</code> if the map is empty.
*/
public Map.Entry getFirst() {
// sentinel.next points to the "first" element of the sequence -- the head
// of the list, which is exactly the entry we need to return. We must test
// for an empty list though because we don"t want to return the sentinel!
return (isEmpty()) ? null : sentinel.next;
}
/**
* Return the key for the "oldest" mapping. That is, return the key for the mapping that was first put into the map
* when compared to all the other objects in the map. This behavior is equivalent to using
* <code>getFirst().getKey()</code>, but this method provides a slightly optimized implementation.
*
* @return The first key in the sequence, or <code>null</code> if the map is empty.
*/
public Object getFirstKey() {
// sentinel.next points to the "first" element of the sequence -- the head
// of the list -- and the requisite key is returned from it. An empty list
// does not need to be tested. In cases where the list is empty,
// sentinel.next will point to the sentinel itself which has a null key,
// which is exactly what we would want to return if the list is empty (a
// nice convient way to avoid test for an empty list)
return sentinel.next.getKey();
}
/**
* Return the value for the "oldest" mapping. That is, return the value for the mapping that was first put into the
* map when compared to all the other objects in the map. This behavior is equivalent to using
* <code>getFirst().getValue()</code>, but this method provides a slightly optimized implementation.
*
* @return The first value in the sequence, or <code>null</code> if the map is empty.
*/
public Object getFirstValue() {
// sentinel.next points to the "first" element of the sequence -- the head
// of the list -- and the requisite value is returned from it. An empty
// list does not need to be tested. In cases where the list is empty,
// sentinel.next will point to the sentinel itself which has a null value,
// which is exactly what we would want to return if the list is empty (a
// nice convient way to avoid test for an empty list)
return sentinel.next.getValue();
}
/**
* Return the entry for the "newest" mapping. That is, return the Map.Entry for the key-value pair that was first
* put into the map when compared to all the other pairings in the map. The behavior is equivalent to: <p/>
* <p/>
* <pre>
* Object obj = null;
* Iterator iter = entrySet().iterator();
* while (iter.hasNext()) {
* obj = iter.next();
* }
* return (Map.Entry) obj;
* </pre>
* <p/>
* <p/>However, the implementation of this method ensures an O(1) lookup of the last key rather than O(n).
*
* @return The last entry in the sequence, or <code>null</code> if the map is empty.
*/
public Map.Entry getLast() {
// sentinel.prev points to the "last" element of the sequence -- the tail
// of the list, which is exactly the entry we need to return. We must test
// for an empty list though because we don"t want to return the sentinel!
return (isEmpty()) ? null : sentinel.prev;
}
/**
* Return the key for the "newest" mapping. That is, return the key for the mapping that was last put into the map
* when compared to all the other objects in the map. This behavior is equivalent to using
* <code>getLast().getKey()</code>, but this method provides a slightly optimized implementation.
*
* @return The last key in the sequence, or <code>null</code> if the map is empty.
*/
public Object getLastKey() {
// sentinel.prev points to the "last" element of the sequence -- the tail
// of the list -- and the requisite key is returned from it. An empty list
// does not need to be tested. In cases where the list is empty,
// sentinel.prev will point to the sentinel itself which has a null key,
// which is exactly what we would want to return if the list is empty (a
// nice convient way to avoid test for an empty list)
return sentinel.prev.getKey();
}
/**
* Return the value for the "newest" mapping. That is, return the value for the mapping that was last put into the
* map when compared to all the other objects in the map. This behavior is equivalent to using
* <code>getLast().getValue()</code>, but this method provides a slightly optimized implementation.
*
* @return The last value in the sequence, or <code>null</code> if the map is empty.
*/
public Object getLastValue() {
// sentinel.prev points to the "last" element of the sequence -- the tail
// of the list -- and the requisite value is returned from it. An empty
// list does not need to be tested. In cases where the list is empty,
// sentinel.prev will point to the sentinel itself which has a null value,
// which is exactly what we would want to return if the list is empty (a
// nice convient way to avoid test for an empty list)
return sentinel.prev.getValue();
}
/**
* Implements {@link Map#put(Object, Object)}.
*/
public Object put(Object key, Object value) {
modCount++;
Object oldValue = null;
// lookup the entry for the specified key
Entry e = (Entry) entries.get(key);
// check to see if it already exists
if (e != null) {
// remove from list so the entry gets "moved" to the end of list
removeEntry(e);
// update value in map
oldValue = e.setValue(value);
// Note: We do not update the key here because its unnecessary. We only
// do comparisons using equals(Object) and we know the specified key and
// that in the map are equal in that sense. This may cause a problem if
// someone does not implement their hashCode() and/or equals(Object)
// method properly and then use it as a key in this map.
} else {
// add new entry
e = new Entry(key, value);
entries.put(key, e);
}
// assert(entry in map, but not list)
// add to list
insertEntry(e);
return oldValue;
}
/**
* Implements {@link Map#remove(Object)}.
*/
public Object remove(Object key) {
Entry e = removeImpl(key);
return (e == null) ? null : e.getValue();
}
/**
* Fully remove an entry from the map, returning the old entry or null if there was no such entry with the specified
* key.
*/
private Entry removeImpl(Object key) {
Entry e = (Entry) entries.remove(key);
if (e == null) {
return null;
}
modCount++;
removeEntry(e);
return e;
}
/**
* Adds all the mappings in the specified map to this map, replacing any mappings that already exist (as per
* {@linkMap#putAll(Map)}). The order in which the entries are added is determined by the iterator returned from
* {@linkMap#entrySet()}for the specified map.
*
* @param t the mappings that should be added to this map.
* @throws NullPointerException if <code>t</code> is <code>null</code>
*/
public void putAll(Map t) {
Iterator iter = t.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
put(entry.getKey(), entry.getValue());
}
}
/**
* Implements {@link Map#clear()}.
*/
public void clear() {
modCount++;
// remove all from the underlying map
entries.clear();
// and the list
sentinel.next = sentinel;
sentinel.prev = sentinel;
}
/**
* Implements {@link Map#equals(Object)}.
*/
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (!(obj instanceof Map)) {
return false;
}
return entrySet().equals(((Map) obj).entrySet());
}
/**
* Implements {@link Map#hashCode()}.
*/
public int hashCode() {
return entrySet().hashCode();
}
/**
* Provides a string representation of the entries within the map. The format of the returned string may change with
* different releases, so this method is suitable for debugging purposes only. If a specific format is required, use
* {@link #entrySet()}.{@link Set#iterator() iterator()}and iterate over the entries in the map formatting them
* as appropriate.
*/
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("[");
for (Entry pos = sentinel.next; pos != sentinel; pos = pos.next) {
buf.append(pos.getKey());
buf.append("=");
buf.append(pos.getValue());
if (pos.next != sentinel) {
buf.append(",");
}
}
buf.append("]");
return buf.toString();
}
/**
* Implements {@link Map#keySet()}.
*/
public Set keySet() {
return new AbstractSet() {
// required impls
public Iterator iterator() {
return new OrderedIterator(KEY);
}
public boolean remove(Object o) {
Entry e = SequencedHashMap.this.removeImpl(o);
return (e != null);
}
// more efficient impls than abstract set
public void clear() {
SequencedHashMap.this.clear();
}
public int size() {
return SequencedHashMap.this.size();
}
public boolean isEmpty() {
return SequencedHashMap.this.isEmpty();
}
public boolean contains(Object o) {
return SequencedHashMap.this.containsKey(o);
}
};
}
/**
* Implements {@link Map#values()}.
*/
public Collection values() {
return new AbstractCollection() {
// required impl
public Iterator iterator() {
return new OrderedIterator(VALUE);
}
public boolean remove(Object value) {
// do null comparison outside loop so we only need to do it once. This
// provides a tighter, more efficient loop at the expense of slight
// code duplication.
if (value == null) {
for (Entry pos = sentinel.next; pos != sentinel; pos = pos.next) {
if (pos.getValue() == null) {
SequencedHashMap.this.removeImpl(pos.getKey());
return true;
}
}
} else {
for (Entry pos = sentinel.next; pos != sentinel; pos = pos.next) {
if (value.equals(pos.getValue())) {
SequencedHashMap.this.removeImpl(pos.getKey());
return true;
}
}
}
return false;
}
// more efficient impls than abstract collection
public void clear() {
SequencedHashMap.this.clear();
}
public int size() {
return SequencedHashMap.this.size();
}
public boolean isEmpty() {
return SequencedHashMap.this.isEmpty();
}
public boolean contains(Object o) {
return SequencedHashMap.this.containsValue(o);
}
};
}
/**
* Implements {@link Map#entrySet()}.
*/
public Set entrySet() {
return new AbstractSet() {
// helper
private Entry findEntry(Object o) {
if (o == null) {
return null;
}
if (!(o instanceof Map.Entry)) {
return null;
}
Map.Entry e = (Map.Entry) o;
Entry entry = (Entry) entries.get(e.getKey());
if ((entry != null) && entry.equals(e)) {
return entry;
} else {
return null;
}
}
// required impl
public Iterator iterator() {
return new OrderedIterator(ENTRY);
}
public boolean remove(Object o) {
Entry e = findEntry(o);
if (e == null) {
return false;
}
return SequencedHashMap.this.removeImpl(e.getKey()) != null;
}
// more efficient impls than abstract collection
public void clear() {
SequencedHashMap.this.clear();
}
public int size() {
return SequencedHashMap.this.size();
}
public boolean isEmpty() {
return SequencedHashMap.this.isEmpty();
}
public boolean contains(Object o) {
return findEntry(o) != null;
}
};
}
// APIs maintained from previous version of SequencedHashMap for backwards
// compatibility
/**
* Creates a shallow copy of this object, preserving the internal structure by copying only references. The keys and
* values themselves are not <code>clone()</code> "d. The cloned object maintains the same sequence.
*
* @return A clone of this instance.
* @throws CloneNotSupportedException if clone is not supported by a subclass.
*/
public Object clone() throws CloneNotSupportedException {
// yes, calling super.clone() silly since we"re just blowing away all
// the stuff that super might be doing anyway, but for motivations on
// this, see:
// http://www.javaworld.ru/javaworld/jw-01-1999/jw-01-object.html
SequencedHashMap map = (SequencedHashMap) super.clone();
// create new, empty sentinel
map.sentinel = createSentinel();
// create a new, empty entry map
// note: this does not preserve the initial capacity and load factor.
map.entries = new HashMap();
// add all the mappings
map.putAll(this);
// Note: We cannot just clone the hashmap and sentinel because we must
// duplicate our internal structures. Cloning those two will not clone all
// the other entries they reference, and so the cloned hash map will not be
// able to maintain internal consistency because there are two objects with
// the same entries. See discussion in the Entry implementation on why we
// cannot implement a clone of the Entry (and thus why we need to recreate
// everything).
return map;
}
/**
* Returns the Map.Entry at the specified index
*
* @throws ArrayIndexOutOfBoundsException if the specified index is <code>< 0</code> or <code>></code> the
* size of the map.
*/
private Map.Entry getEntry(int index) {
Entry pos = sentinel;
if (index < 0) {
throw new ArrayIndexOutOfBoundsException(index + " < 0");
}
// loop to one before the position
int i = -1;
while ((i < (index - 1)) && (pos.next != sentinel)) {
i++;
pos = pos.next;
}
// pos.next is the requested position
// if sentinel is next, past end of list
if (pos.next == sentinel) {
throw new ArrayIndexOutOfBoundsException(index + " >= " + (i + 1));
}
return pos.next;
}
/**
* Returns the key at the specified index.
*
* @throws ArrayIndexOutOfBoundsException if the <code>index</code> is <code>< 0</code> or <code>></code>
* the size of the map.
*/
public Object get(int index) {
return getEntry(index).getKey();
}
/**
* Returns the value at the specified index.
*
* @throws ArrayIndexOutOfBoundsException if the <code>index</code> is <code>< 0</code> or <code>></code>
* the size of the map.
*/
public Object getValue(int index) {
return getEntry(index).getValue();
}
/**
* Returns the index of the specified key.
*/
public int indexOf(Object key) {
Entry e = (Entry) entries.get(key);
int pos = 0;
while (e.prev != sentinel) {
pos++;
e = e.prev;
}
return pos;
}
/**
* Returns a key iterator.
*/
public Iterator iterator() {
return keySet().iterator();
}
/**
* Returns the last index of the specified key.
*/
public int lastIndexOf(Object key) {
// keys in a map are guarunteed to be unique
return indexOf(key);
}
/**
* Returns a List view of the keys rather than a set view. The returned list is unmodifiable. This is required
* because changes to the values of the list (using {@link java.util.ListIterator#set(Object)}) will effectively
* remove the value from the list and reinsert that value at the end of the list, which is an unexpected side effect
* of changing the value of a list. This occurs because changing the key, changes when the mapping is added to the
* map and thus where it appears in the list. <p/>
* <P>
* An alternative to this method is to use {@link #keySet()}
*
* @return The ordered list of keys.
* @see #keySet()
*/
public List sequence() {
List l = new ArrayList(size());
Iterator iter = keySet().iterator();
while (iter.hasNext()) {
l.add(iter.next());
}
return Collections.unmodifiableList(l);
}
/**
* Removes the element at the specified index.
*
* @param index The index of the object to remove.
* @return The previous value coressponding the <code>key</code>, or <code>null</code> if none existed.
* @throws ArrayIndexOutOfBoundsException if the <code>index</code> is <code>< 0</code> or <code>></code>
* the size of the map.
*/
public Object remove(int index) {
return remove(get(index));
}
// per Externalizable.readExternal(ObjectInput)
/**
* Deserializes this map from the given stream.
*
* @param in the stream to deserialize from
* @throws IOException if the stream raises it
* @throws ClassNotFoundException if the stream raises it
*/
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
int size = in.readInt();
for (int i = 0; i < size; i++) {
Object key = in.readObject();
Object value = in.readObject();
put(key, value);
}
}
/**
* Serializes this map to the given stream.
*
* @param out the stream to serialize to
* @throws IOException if the stream raises it
*/
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(size());
for (Entry pos = sentinel.next; pos != sentinel; pos = pos.next) {
out.writeObject(pos.getKey());
out.writeObject(pos.getValue());
}
}
/**
* {@link java.util.Map.Entry}that doubles as a node in the linked list of sequenced mappings.
*/
private static class Entry implements Map.Entry {
// Note: This class cannot easily be made clonable. While the actual
// implementation of a clone would be simple, defining the semantics is
// difficult. If a shallow clone is implemented, then entry.next.prev !=
// entry, which is unintuitive and probably breaks all sorts of assumptions
// in code that uses this implementation. If a deep clone is
// implementated, then what happens when the linked list is cyclical (as is
// the case with SequencedHashMap)? It"s impossible to know in the clone
// when to stop cloning, and thus you end up in a recursive loop,
// continuously cloning the "next" in the list.
private final Object key;
private Object value;
// package private to allow the SequencedHashMap to access and manipulate
// them.
Entry next = null;
Entry prev = null;
public Entry(Object key, Object value) {
this.key = key;
this.value = value;
}
// per Map.Entry.getKey()
public Object getKey() {
return this.key;
}
// per Map.Entry.getValue()
public Object getValue() {
return this.value;
}
// per Map.Entry.setValue()
public Object setValue(Object value) {
Object oldValue = this.value;
this.value = value;
return oldValue;
}
public int hashCode() {
// implemented per api docs for Map.Entry.hashCode()
return (((getKey() == null) ? 0 : getKey().hashCode()) ^
((getValue() == null) ? 0 : getValue().hashCode()));
}
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (!(obj instanceof Map.Entry)) {
return false;
}
Map.Entry other = (Map.Entry) obj;
// implemented per api docs for Map.Entry.equals(Object)
return (((getKey() == null) ? (other.getKey() == null) : getKey().equals(other.getKey())) && ((getValue() ==
null)
?
(other.getValue() ==
null)
:
getValue()
.equals(other.getValue())));
}
public String toString() {
return "[" + getKey() + "=" + getValue() + "]";
}
}
private class OrderedIterator implements Iterator {
/**
* Holds the type that should be returned from the iterator. The value should be either {@link #KEY},
* {@link#VALUE}, or {@link #ENTRY}. To save a tiny bit of memory, this field is also used as a marker for
* when remove has been called on the current object to prevent a second remove on the same element.
* Essientially, if this value is negative (i.e. the bit specified by {@link #REMOVED_MASK}is set), the current
* position has been removed. If positive, remove can still be called.
*/
private int returnType;
/**
* Holds the "current" position in the iterator. When pos.next is the sentinel, we"ve reached the end of the
* list.
*/
private Entry pos = sentinel;
/**
* Holds the expected modification count. If the actual modification count of the map differs from this value,
* then a concurrent modification has occurred.
*/
private transient long expectedModCount = modCount;
/**
* Construct an iterator over the sequenced elements in the order in which they were added. The {@link #next()}
* method returns the type specified by <code>returnType</code> which must be either {@link #KEY},
* {@link#VALUE}, or {@link #ENTRY}.
*/
public OrderedIterator(int returnType) {
//// Since this is a private inner class, nothing else should have
//// access to the constructor. Since we know the rest of the outer
//// class uses the iterator correctly, we can leave of the following
//// check:
//if(returnType >= 0 && returnType <= 2) {
// throw new IllegalArgumentException("Invalid iterator type");
//}
// Set the "removed" bit so that the iterator starts in a state where
// "next" must be called before "remove" will succeed.
this.returnType = returnType | REMOVED_MASK;
}
/**
* Returns whether there is any additional elements in the iterator to be returned.
*
* @return <code>true</code> if there are more elements left to be returned from the iterator;
* <code>false</code> otherwise.
*/
public boolean hasNext() {
return pos.next != sentinel;
}
/**
* Returns the next element from the iterator.
*
* @return the next element from the iterator.
* @throws NoSuchElementException if there are no more elements in the iterator.
* @throws ConcurrentModificationException
* if a modification occurs in the underlying map.
*/
public Object next() {
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
if (pos.next == sentinel) {
throw new NoSuchElementException();
}
// clear the "removed" flag
returnType = returnType & ~REMOVED_MASK;
pos = pos.next;
switch (returnType) {
case KEY:
return pos.getKey();
case VALUE:
return pos.getValue();
case ENTRY:
return pos;
default:
// should never happen
throw new Error("bad iterator type: " + returnType);
}
}
/**
* Removes the last element returned from the {@link #next()}method from the sequenced map.
*
* @throws IllegalStateException if there isn"t a "last element" to be removed. That is, if {@link #next()}has
* never been called, or if {@link #remove()}was already called on the element.
* @throws ConcurrentModificationException
* if a modification occurs in the underlying map.
*/
public void remove() {
if ((returnType & REMOVED_MASK) != 0) {
throw new IllegalStateException("remove() must follow next()");
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
SequencedHashMap.this.removeImpl(pos.getKey());
// update the expected mod count for the remove operation
expectedModCount++;
// set the removed flag
returnType = returnType | REMOVED_MASK;
}
}
}
String Map
//
// $Id: StringMap.java,v 1.13 2004/10/23 09:03:22 gregwilkins Exp $
// Copyright 1997-2004 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import java.io.Externalizable;
import java.util.AbstractMap;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/* ------------------------------------------------------------ */
/** Map like class of Strings to Objects.
* This String Map has been optimized for mapping small sets of
* Strings where the most frequently accessed Strings have been put to
* the map first.
*
* It also has the benefit that it can look up entries by substring or
* sections of char and byte arrays. This can prevent many String
* objects from being created just to look up in the map.
*
* This map is NOT synchronized.
*
* @version $Id: StringMap.java,v 1.13 2004/10/23 09:03:22 gregwilkins Exp $
* @author Greg Wilkins (gregw)
*/
public class StringMap extends AbstractMap implements Externalizable
{
private static final int __HASH_WIDTH=9;
/* ------------------------------------------------------------ */
protected int _width=__HASH_WIDTH;
protected Node _root=new Node();
protected boolean _ignoreCase=false;
protected NullEntry _nullEntry=null;
protected Object _nullValue=null;
protected HashSet _entrySet=new HashSet(3);
protected Set _umEntrySet=Collections.unmodifiableSet(_entrySet);
/* ------------------------------------------------------------ */
/** Constructor.
*/
public StringMap()
{}
/* ------------------------------------------------------------ */
/** Constructor.
* @param ignoreCase
*/
public StringMap(boolean ignoreCase)
{
_ignoreCase=ignoreCase;
}
/* ------------------------------------------------------------ */
/** Constructor.
* @param ignoreCase
* @param width Width of hash tables, larger values are faster but
* use more memory.
*/
public StringMap(boolean ignoreCase,int width)
{
_ignoreCase=ignoreCase;
_width=width;
}
/* ------------------------------------------------------------ */
/** Set the ignoreCase attribute.
* @param ic If true, the map is case insensitive for keys.
*/
public void setIgnoreCase(boolean ic)
{
if (_root._children!=null)
throw new IllegalStateException("Must be set before first put");
_ignoreCase=ic;
}
/* ------------------------------------------------------------ */
public boolean isIgnoreCase()
{
return _ignoreCase;
}
/* ------------------------------------------------------------ */
/** Set the hash width.
* @param width Width of hash tables, larger values are faster but
* use more memory.
*/
public void setWidth(int width)
{
_width=width;
}
/* ------------------------------------------------------------ */
public int getWidth()
{
return _width;
}
/* ------------------------------------------------------------ */
public Object put(Object key, Object value)
{
if (key==null)
return put(null,value);
return put(key.toString(),value);
}
/* ------------------------------------------------------------ */
public Object put(String key, Object value)
{
if (key==null)
{
Object oldValue=_nullValue;
_nullValue=value;
if (_nullEntry==null)
{
_nullEntry=new NullEntry();
_entrySet.add(_nullEntry);
}
return oldValue;
}
Node node = _root;
int ni=-1;
Node prev = null;
Node parent = null;
// look for best match
charLoop:
for (int i=0;i<key.length();i++)
{
char c=key.charAt(i);
// Advance node
if (ni==-1)
{
parent=node;
prev=null;
ni=0;
node=(node._children==null)?null:node._children[c%_width];
}
// Loop through a node chain at the same level
while (node!=null)
{
// If it is a matching node, goto next char
if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
{
prev=null;
ni++;
if (ni==node._char.length)
ni=-1;
continue charLoop;
}
// no char match
// if the first char,
if (ni==0)
{
// look along the chain for a char match
prev=node;
node=node._next;
}
else
{
// Split the current node!
node.split(this,ni);
i--;
ni=-1;
continue charLoop;
}
}
// We have run out of nodes, so as this is a put, make one
node = new Node(_ignoreCase,key,i);
if (prev!=null) // add to end of chain
prev._next=node;
else if (parent!=null) // add new child
{
if (parent._children==null)
parent._children=new Node[_width];
parent._children[c%_width]=node;
int oi=node._ochar[0]%_width;
if (node._ochar!=null && node._char[0]%_width!=oi)
{
if (parent._children[oi]==null)
parent._children[oi]=node;
else
{
Node n=parent._children[oi];
while(n._next!=null)
n=n._next;
n._next=node;
}
}
}
else // this is the root.
_root=node;
break;
}
// Do we have a node
if (node!=null)
{
// Split it if we are in the middle
if(ni>0)
node.split(this,ni);
Object old = node._value;
node._key=key;
node._value=value;
_entrySet.add(node);
return old;
}
return null;
}
/* ------------------------------------------------------------ */
public Object get(Object key)
{
if (key==null)
return _nullValue;
if (key instanceof String)
return get((String)key);
return get(key.toString());
}
/* ------------------------------------------------------------ */
public Object get(String key)
{
if (key==null)
return _nullValue;
Map.Entry entry = getEntry(key,0,key.length());
if (entry==null)
return null;
return entry.getValue();
}
/* ------------------------------------------------------------ */
/** Get a map entry by substring key.
* @param key String containing the key
* @param offset Offset of the key within the String.
* @param length The length of the key
* @return The Map.Entry for the key or null if the key is not in
* the map.
*/
public Map.Entry getEntry(String key,int offset, int length)
{
if (key==null)
return _nullEntry;
Node node = _root;
int ni=-1;
// look for best match
charLoop:
for (int i=0;i<length;i++)
{
char c=key.charAt(offset+i);
// Advance node
if (ni==-1)
{
ni=0;
node=(node._children==null)?null:node._children[c%_width];
}
// Look through the node chain
while (node!=null)
{
// If it is a matching node, goto next char
if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
{
ni++;
if (ni==node._char.length)
ni=-1;
continue charLoop;
}
// No char match, so if mid node then no match at all.
if (ni>0) return null;
// try next in chain
node=node._next;
}
return null;
}
if (ni>0) return null;
if (node!=null && node._key==null)
return null;
return node;
}
/* ------------------------------------------------------------ */
/** Get a map entry by char array key.
* @param key char array containing the key
* @param offset Offset of the key within the array.
* @param length The length of the key
* @return The Map.Entry for the key or null if the key is not in
* the map.
*/
public Map.Entry getEntry(char[] key,int offset, int length)
{
if (key==null)
return _nullEntry;
Node node = _root;
int ni=-1;
// look for best match
charLoop:
for (int i=0;i<length;i++)
{
char c=key[offset+i];
// Advance node
if (ni==-1)
{
ni=0;
node=(node._children==null)?null:node._children[c%_width];
}
// While we have a node to try
while (node!=null)
{
// If it is a matching node, goto next char
if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
{
ni++;
if (ni==node._char.length)
ni=-1;
continue charLoop;
}
// No char match, so if mid node then no match at all.
if (ni>0) return null;
// try next in chain
node=node._next;
}
return null;
}
if (ni>0) return null;
if (node!=null && node._key==null)
return null;
return node;
}
/* ------------------------------------------------------------ */
/** Get a map entry by byte array key.
* @param key byte array containing the key. A simple ASCII byte
* to char mapping is used.
* @param offset Offset of the key within the array.
* @param length The length of the key
* @return The Map.Entry for the key or null if the key is not in
* the map.
*/
public Map.Entry getEntry(byte[] key,int offset, int length)
{
if (key==null)
return _nullEntry;
Node node = _root;
int ni=-1;
// look for best match
charLoop:
for (int i=0;i<length;i++)
{
char c=(char)(key[offset+i]);
// Advance node
if (ni==-1)
{
ni=0;
node=(node._children==null)?null:node._children[c%_width];
}
// While we have a node to try
while (node!=null)
{
// If it is a matching node, goto next char
if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
{
ni++;
if (ni==node._char.length)
ni=-1;
continue charLoop;
}
// No char match, so if mid node then no match at all.
if (ni>0) return null;
// try next in chain
node=node._next;
}
return null;
}
if (ni>0) return null;
if (node!=null && node._key==null)
return null;
return node;
}
/* ------------------------------------------------------------ */
public Object remove(Object key)
{
if (key==null)
return remove(null);
return remove(key.toString());
}
/* ------------------------------------------------------------ */
public Object remove(String key)
{
if (key==null)
{
Object oldValue=_nullValue;
if (_nullEntry!=null)
{
_entrySet.remove(_nullEntry);
_nullEntry=null;
_nullValue=null;
}
return oldValue;
}
Node node = _root;
int ni=-1;
// look for best match
charLoop:
for (int i=0;i<key.length();i++)
{
char c=key.charAt(i);
// Advance node
if (ni==-1)
{
ni=0;
node=(node._children==null)?null:node._children[c%_width];
}
// While we have a node to try
while (node!=null)
{
// If it is a matching node, goto next char
if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
{
ni++;
if (ni==node._char.length)
ni=-1;
continue charLoop;
}
// No char match, so if mid node then no match at all.
if (ni>0) return null;
// try next in chain
node=node._next;
}
return null;
}
if (ni>0) return null;
if (node==null || node._key==null)
return null;
Object old = node._value;
_entrySet.remove(node);
node._value=null;
node._key=null;
return old;
}
/* ------------------------------------------------------------ */
public Set entrySet()
{
return _umEntrySet;
}
/* ------------------------------------------------------------ */
public int size()
{
return _entrySet.size();
}
/* ------------------------------------------------------------ */
public boolean isEmpty()
{
return _entrySet.isEmpty();
}
/* ------------------------------------------------------------ */
public boolean containsKey(Object key)
{
if (key==null)
return _nullEntry!=null;
return
getEntry(key.toString(),0,key==null?0:key.toString().length())!=null;
}
/* ------------------------------------------------------------ */
public void clear()
{
_root=new Node();
_nullEntry=null;
_nullValue=null;
_entrySet.clear();
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
private static class Node implements Map.Entry
{
char[] _char;
char[] _ochar;
Node _next;
Node[] _children;
String _key;
Object _value;
Node(){}
Node(boolean ignoreCase,String s, int offset)
{
int l=s.length()-offset;
_char=new char[l];
_ochar=new char[l];
for (int i=0;i<l;i++)
{
char c=s.charAt(offset+i);
_char[i]=c;
if (ignoreCase)
{
char o=c;
if (Character.isUpperCase(c))
o=Character.toLowerCase(c);
else if (Character.isLowerCase(c))
o=Character.toUpperCase(c);
_ochar[i]=o;
}
}
}
Node split(StringMap map,int offset)
{
Node split = new Node();
int sl=_char.length-offset;
char[] tmp=this._char;
this._char=new char[offset];
split._char = new char[sl];
System.arraycopy(tmp,0,this._char,0,offset);
System.arraycopy(tmp,offset,split._char,0,sl);
if (this._ochar!=null)
{
tmp=this._ochar;
this._ochar=new char[offset];
split._ochar = new char[sl];
System.arraycopy(tmp,0,this._ochar,0,offset);
System.arraycopy(tmp,offset,split._ochar,0,sl);
}
split._key=this._key;
split._value=this._value;
this._key=null;
this._value=null;
if (map._entrySet.remove(this))
map._entrySet.add(split);
split._children=this._children;
this._children=new Node[map._width];
this._children[split._char[0]%map._width]=split;
if (split._ochar!=null && this._children[split._ochar[0]%map._width]!=split)
this._children[split._ochar[0]%map._width]=split;
return split;
}
public Object getKey(){return _key;}
public Object getValue(){return _value;}
public Object setValue(Object o){Object old=_value;_value=o;return old;}
public String toString()
{
StringBuffer buf=new StringBuffer();
synchronized(buf)
{
toString(buf);
}
return buf.toString();
}
private void toString(StringBuffer buf)
{
buf.append("{[");
if (_char==null)
buf.append("-");
else
for (int i=0;i<_char.length;i++)
buf.append(_char[i]);
buf.append(":");
buf.append(_key);
buf.append("=");
buf.append(_value);
buf.append("]");
if (_children!=null)
{
for (int i=0;i<_children.length;i++)
{
buf.append("|");
if (_children[i]!=null)
_children[i].toString(buf);
else
buf.append("-");
}
}
buf.append("}");
if (_next!=null)
{
buf.append(",\n");
_next.toString(buf);
}
}
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
private class NullEntry implements Map.Entry
{
public Object getKey(){return null;}
public Object getValue(){return _nullValue;}
public Object setValue(Object o)
{Object old=_nullValue;_nullValue=o;return old;}
public String toString(){return "[:null="+_nullValue+"]";}
}
/* ------------------------------------------------------------ */
public void writeExternal(java.io.ObjectOutput out)
throws java.io.IOException
{
HashMap map = new HashMap(this);
out.writeObject(map);
}
/* ------------------------------------------------------------ */
public void readExternal(java.io.ObjectInput in)
throws java.io.IOException, ClassNotFoundException
{
HashMap map = (HashMap)in.readObject();
this.putAll(map);
}
}
Type-safe Map, from char array to String value
/* Woodstox XML processor
*
* Copyright (c) 2004- Tatu Saloranta, tatu.saloranta@iki.fi
*
* Licensed under the License specified in the file LICENSE which is
* included with the source code.
* You may not use this file except in compliance with the License.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* This class is a kind of specialized type-safe Map, from char array to
* String value. Specialization means that in addition to type-safety
* and specific access patterns (key char array, Value optionally interned
* String; values added on access if necessary), and that instances are
* meant to be used concurrently, but by using well-defined mechanisms
* to obtain such concurrently usable instances. Main use for the class
* is to store symbol table information for things like compilers and
* parsers; especially when number of symbols (keywords) is limited.
*<p>
* For optimal performance, usage pattern should be one where matches
* should be very common (esp. after "warm-up"), and as with most hash-based
* maps/sets, that hash codes are uniformly distributed. Also, collisions
* are slightly more expensive than with HashMap or HashSet, since hash codes
* are not used in resolving collisions; that is, equals() comparison is
* done with all symbols in same bucket index.<br />
* Finally, rehashing is also more expensive, as hash codes are not
* stored; rehashing requires all entries" hash codes to be recalculated.
* Reason for not storing hash codes is reduced memory usage, hoping
* for better memory locality.
*<p>
* Usual usage pattern is to create a single "master" instance, and either
* use that instance in sequential fashion, or to create derived "child"
* instances, which after use, are asked to return possible symbol additions
* to master instance. In either case benefit is that symbol table gets
* initialized so that further uses are more efficient, as eventually all
* symbols needed will already be in symbol table. At that point no more
* Symbol String allocations are needed, nor changes to symbol table itself.
*<p>
* Note that while individual SymbolTable instances are NOT thread-safe
* (much like generic collection classes), concurrently used "child"
* instances can be freely used without synchronization. However, using
* master table concurrently with child instances can only be done if
* access to master instance is read-only (ie. no modifications done).
*/
public class SymbolTable {
/**
* Default initial table size; no need to make it miniscule, due
* to couple of things: first, overhead of array reallocation
* is significant,
* and second, overhead of rehashing is also non-negligible.
*<p>
* Let"s use 128 as the default; it allows for up to 96 symbols,
* and uses about 512 bytes on 32-bit machines.
*/
protected static final int DEFAULT_TABLE_SIZE = 128;
protected static final float DEFAULT_FILL_FACTOR = 0.75f;
protected static final String EMPTY_STRING = "";
/*
////////////////////////////////////////
// Configuration:
////////////////////////////////////////
*/
/**
* Flag that determines whether Strings to be added need to be
* interned before being added or not. Forcing intern()ing will add
* some overhead when adding new Strings, but may be beneficial if such
* Strings are generally used by other parts of system. Note that even
* without interning, all returned String instances are guaranteed
* to be comparable with equality (==) operator; it"s just that such
* guarantees are not made for Strings other classes return.
*/
protected boolean mInternStrings;
/*
////////////////////////////////////////
// Actual symbol table data:
////////////////////////////////////////
*/
/**
* Primary matching symbols; it"s expected most match occur from
* here.
*/
protected String[] mSymbols;
/**
* Overflow buckets; if primary doesn"t match, lookup is done
* from here.
*<p>
* Note: Number of buckets is half of number of symbol entries, on
* assumption there"s less need for buckets.
*/
protected Bucket[] mBuckets;
/**
* Current size (number of entries); needed to know if and when
* rehash.
*/
protected int mSize;
/**
* Limit that indicates maximum size this instance can hold before
* it needs to be expanded and rehashed. Calculated using fill
* factor passed in to constructor.
*/
protected int mSizeThreshold;
/**
* Mask used to get index from hash values; equal to
* <code>mBuckets.length - 1</code>, when mBuckets.length is
* a power of two.
*/
protected int mIndexMask;
/*
////////////////////////////////////////
// Information about concurrency
////////////////////////////////////////
*/
/**
* Version of this table instance; used when deriving new concurrently
* used versions from existing "master" instance.
*/
protected int mThisVersion;
/**
* Flag that indicates if any changes have been made to the data;
* used to both determine if bucket array needs to be copied when
* (first) change is made, and potentially if updated bucket list
* is to be resync"ed back to master instance.
*/
protected boolean mDirty;
/*
////////////////////////////////////////
// Life-cycle:
////////////////////////////////////////
*/
/**
* Method for constructing a master symbol table instance; this one
* will create master instance with default size, and with interning
* enabled.
*/
public SymbolTable() {
this(true);
}
/**
* Method for constructing a master symbol table instance.
*/
public SymbolTable(boolean internStrings) {
this(internStrings, DEFAULT_TABLE_SIZE);
}
/**
* Method for constructing a master symbol table instance.
*/
public SymbolTable(boolean internStrings, int initialSize) {
this(internStrings, initialSize, DEFAULT_FILL_FACTOR);
}
/**
* Main method for constructing a master symbol table instance; will
* be called by other public constructors.
*
* @param internStrings Whether Strings to add are intern()ed or not
* @param initialSize Minimum initial size for bucket array; internally
* will always use a power of two equal to or bigger than this value.
* @param fillFactor Maximum fill factor allowed for bucket table;
* when more entries are added, table will be expanded.
*/
public SymbolTable(boolean internStrings, int initialSize,
float fillFactor)
{
mInternStrings = internStrings;
// Let"s start versions from 1
mThisVersion = 1;
// And we"ll also set flags so no copying of buckets is needed:
mDirty = true;
// No point in requesting funny initial sizes...
if (initialSize < 1) {
throw new IllegalArgumentException("Can not use negative/zero initial size: "+initialSize);
}
/* Initial size has to be a power of two. Also, let"s not honour
* sizes that are ridiculously small...
*/
{
int currSize = 4;
while (currSize < initialSize) {
currSize += currSize;
}
initialSize = currSize;
}
mSymbols = new String[initialSize];
mBuckets = new Bucket[initialSize >> 1];
// Mask is easy to calc for powers of two.
mIndexMask = initialSize - 1;
mSize = 0;
// Sanity check for fill factor:
if (fillFactor < 0.01f) {
throw new IllegalArgumentException("Fill factor can not be lower than 0.01.");
}
if (fillFactor > 10.0f) { // just to catch stupid values, ie. useless from performance perspective
throw new IllegalArgumentException("Fill factor can not be higher than 10.0.");
}
mSizeThreshold = (int) (initialSize * fillFactor + 0.5);
}
/**
* Internal constructor used when creating child instances.
*/
private SymbolTable(boolean internStrings, String[] symbols,
Bucket[] buckets, int size, int sizeThreshold,
int indexMask, int version)
{
mInternStrings = internStrings;
mSymbols = symbols;
mBuckets = buckets;
mSize = size;
mSizeThreshold = sizeThreshold;
mIndexMask = indexMask;
mThisVersion = version;
// Need to make copies of arrays, if/when adding new entries
mDirty = false;
}
/**
* "Factory" method; will create a new child instance of this symbol
* table. It will be a copy-on-write instance, ie. it will only use
* read-only copy of parent"s data, but when changes are needed, a
* copy will be created.
*<p>
* Note: while this method is synchronized, it is generally not
* safe to both use makeChild/mergeChild, AND to use instance
* actively. Instead, a separate "root" instance should be used
* on which only makeChild/mergeChild are called, but instance itself
* is not used as a symbol table.
*/
public synchronized SymbolTable makeChild() {
return new SymbolTable(mInternStrings, mSymbols, mBuckets,
mSize, mSizeThreshold, mIndexMask,
mThisVersion+1);
}
/**
* Method that allows contents of child table to potentially be
* "merged in" with contents of this symbol table.
*<p>
* Note that caller has to make sure symbol table passed in is
* really a child or sibling of this symbol table.
*/
public synchronized void mergeChild(SymbolTable child)
{
// Let"s do a basic sanity check first:
if (child.size() <= size()) { // nothing to add
return;
}
// Okie dokie, let"s get the data in!
mSymbols = child.mSymbols;
mBuckets = child.mBuckets;
mSize = child.mSize;
mSizeThreshold = child.mSizeThreshold;
mIndexMask = child.mIndexMask;
mThisVersion++; // to prevent other children from overriding
// Dirty flag... well, let"s just clear it, to force copying just
// in case. Shouldn"t really matter, for master tables.
mDirty = false;
/* However, we have to mark child as dirty, so that it will not
* be modifying arrays we "took over" (since child may have
* returned an updated table before it stopped fully using
* the SymbolTable: for example, it may still use it for
* parsing PI targets in epilog)
*/
child.mDirty = false;
}
/*
////////////////////////////////////////////////////
// Public API, configuration
////////////////////////////////////////////////////
*/
public void setInternStrings(boolean state) {
mInternStrings = state;
}
/*
////////////////////////////////////////////////////
// Public API, generic accessors:
////////////////////////////////////////////////////
*/
public int size() { return mSize; }
public int version() { return mThisVersion; }
public boolean isDirty() { return mDirty; }
public boolean isDirectChildOf(SymbolTable t)
{
/* Actually, this doesn"t really prove it is a child (would have to
* use sequence number, or identityHash to really prove it), but
* it"s good enough if relationship is known to exist.
*/
/* (for real check, one would need to child/descendant stuff; or
* at least an identity hash... or maybe even just a _static_ global
* counter for instances... maybe that would actually be worth
* doing?)
*/
if (mThisVersion == (t.mThisVersion + 1)) {
return true;
}
return false;
}
/*
////////////////////////////////////////////////////
// Public API, accessing symbols:
////////////////////////////////////////////////////
*/
/**
* Main access method; will check if actual symbol String exists;
* if so, returns it; if not, will create, add and return it.
*
* @return The symbol matching String in input array
*/
/*
public String findSymbol(char[] buffer, int start, int len)
{
return findSymbol(buffer, start, len, calcHash(buffer, start, len));
}
*/
public String findSymbol(char[] buffer, int start, int len, int hash)
{
// Sanity check:
if (len < 1) {
return EMPTY_STRING;
}
hash &= mIndexMask;
String sym = mSymbols[hash];
// Optimal case; checking existing primary symbol for hash index:
if (sym != null) {
// Let"s inline primary String equality checking:
if (sym.length() == len) {
int i = 0;
do {
if (sym.charAt(i) != buffer[start+i]) {
break;
}
} while (++i < len);
// Optimal case; primary match found
if (i == len) {
return sym;
}
}
// How about collision bucket?
Bucket b = mBuckets[hash >> 1];
if (b != null) {
sym = b.find(buffer, start, len);
if (sym != null) {
return sym;
}
}
}
// Need to expand?
if (mSize >= mSizeThreshold) {
rehash();
/* Need to recalc hash; rare occurence (index mask has been
* recalculated as part of rehash)
*/
hash = calcHash(buffer, start, len) & mIndexMask;
} else if (!mDirty) {
// Or perhaps we need to do copy-on-write?
copyArrays();
mDirty = true;
}
++mSize;
String newSymbol = new String(buffer, start, len);
if (mInternStrings) {
newSymbol = newSymbol.intern();
}
// Ok; do we need to add primary entry, or a bucket?
if (mSymbols[hash] == null) {
mSymbols[hash] = newSymbol;
} else {
int bix = hash >> 1;
mBuckets[bix] = new Bucket(newSymbol, mBuckets[bix]);
}
return newSymbol;
}
/**
* Similar to {link #findSymbol}, but will not add passed in symbol
* if it is not in symbol table yet.
*/
public String findSymbolIfExists(char[] buffer, int start, int len, int hash)
{
// Sanity check:
if (len < 1) {
return EMPTY_STRING;
}
hash &= mIndexMask;
String sym = mSymbols[hash];
// Optimal case; checking existing primary symbol for hash index:
if (sym != null) {
// Let"s inline primary String equality checking:
if (sym.length() == len) {
int i = 0;
do {
if (sym.charAt(i) != buffer[start+i]) {
break;
}
} while (++i < len);
// Optimal case; primary match found
if (i == len) {
return sym;
}
}
// How about collision bucket?
Bucket b = mBuckets[hash >> 1];
if (b != null) {
sym = b.find(buffer, start, len);
if (sym != null) {
return sym;
}
}
}
return null;
}
/**
* Similar to to {@link #findSymbol(char[],int,int,int)}; used to either
* do potentially cheap intern() (if table already has intern()ed version),
* or to pre-populate symbol table with known values.
*/
public String findSymbol(String str)
{
int len = str.length();
// Sanity check:
if (len < 1) {
return EMPTY_STRING;
}
int index = calcHash(str) & mIndexMask;
String sym = mSymbols[index];
// Optimal case; checking existing primary symbol for hash index:
if (sym != null) {
// Let"s inline primary String equality checking:
if (sym.length() == len) {
int i = 0;
for (; i < len; ++i) {
if (sym.charAt(i) != str.charAt(i)) {
break;
}
}
// Optimal case; primary match found
if (i == len) {
return sym;
}
}
// How about collision bucket?
Bucket b = mBuckets[index >> 1];
if (b != null) {
sym = b.find(str);
if (sym != null) {
return sym;
}
}
}
// Need to expand?
if (mSize >= mSizeThreshold) {
rehash();
/* Need to recalc hash; rare occurence (index mask has been
* recalculated as part of rehash)
*/
index = calcHash(str) & mIndexMask;
} else if (!mDirty) {
// Or perhaps we need to do copy-on-write?
copyArrays();
mDirty = true;
}
++mSize;
if (mInternStrings) {
str = str.intern();
}
// Ok; do we need to add primary entry, or a bucket?
if (mSymbols[index] == null) {
mSymbols[index] = str;
} else {
int bix = index >> 1;
mBuckets[bix] = new Bucket(str, mBuckets[bix]);
}
return str;
}
/**
* Implementation of a hashing method for variable length
* Strings. Most of the time intention is that this calculation
* is done by caller during parsing, not here; however, sometimes
* it needs to be done for parsed "String" too.
*
* @param len Length of String; has to be at least 1 (caller guarantees
* this pre-condition)
*/
public static int calcHash(char[] buffer, int start, int len) {
int hash = (int) buffer[0];
for (int i = 1; i < len; ++i) {
hash = (hash * 31) + (int) buffer[i];
}
return hash;
}
public static int calcHash(String key) {
int hash = (int) key.charAt(0);
for (int i = 1, len = key.length(); i < len; ++i) {
hash = (hash * 31) + (int) key.charAt(i);
}
return hash;
}
/*
//////////////////////////////////////////////////////////
// Internal methods
//////////////////////////////////////////////////////////
*/
/**
* Method called when copy-on-write is needed; generally when first
* change is made to a derived symbol table.
*/
private void copyArrays() {
String[] oldSyms = mSymbols;
int size = oldSyms.length;
mSymbols = new String[size];
System.arraycopy(oldSyms, 0, mSymbols, 0, size);
Bucket[] oldBuckets = mBuckets;
size = oldBuckets.length;
mBuckets = new Bucket[size];
System.arraycopy(oldBuckets, 0, mBuckets, 0, size);
}
/**
* Method called when size (number of entries) of symbol table grows
* so big that load factor is exceeded. Since size has to remain
* power of two, arrays will then always be doubled. Main work
* is really redistributing old entries into new String/Bucket
* entries.
*/
private void rehash()
{
int size = mSymbols.length;
int newSize = size + size;
String[] oldSyms = mSymbols;
Bucket[] oldBuckets = mBuckets;
mSymbols = new String[newSize];
mBuckets = new Bucket[newSize >> 1];
// Let"s update index mask, threshold, now (needed for rehashing)
mIndexMask = newSize - 1;
mSizeThreshold += mSizeThreshold;
int count = 0; // let"s do sanity check
/* Need to do two loops, unfortunately, since spillover area is
* only half the size:
*/
for (int i = 0; i < size; ++i) {
String symbol = oldSyms[i];
if (symbol != null) {
++count;
int index = calcHash(symbol) & mIndexMask;
if (mSymbols[index] == null) {
mSymbols[index] = symbol;
} else {
int bix = index >> 1;
mBuckets[bix] = new Bucket(symbol, mBuckets[bix]);
}
}
}
size >>= 1;
for (int i = 0; i < size; ++i) {
Bucket b = oldBuckets[i];
while (b != null) {
++count;
String symbol = b.getSymbol();
int index = calcHash(symbol) & mIndexMask;
if (mSymbols[index] == null) {
mSymbols[index] = symbol;
} else {
int bix = index >> 1;
mBuckets[bix] = new Bucket(symbol, mBuckets[bix]);
}
b = b.getNext();
}
}
if (count != mSize) {
throw new IllegalStateException("Internal error on SymbolTable.rehash(): had "+mSize+" entries; now have "+count+".");
}
}
/*
//////////////////////////////////////////////////////////
// Test/debug support:
//////////////////////////////////////////////////////////
*/
public double calcAvgSeek() {
int count = 0;
for (int i = 0, len = mSymbols.length; i < len; ++i) {
if (mSymbols[i] != null) {
++count;
}
}
for (int i = 0, len = mBuckets.length; i < len; ++i) {
Bucket b = mBuckets[i];
int cost = 2;
while (b != null) {
count += cost;
++cost;
b = b.getNext();
}
}
return ((double) count) / ((double) mSize);
}
/*
//////////////////////////////////////////////////////////
// Bucket class
//////////////////////////////////////////////////////////
*/
/**
* This class is a symbol table entry. Each entry acts as a node
* in a linked list.
*/
static final class Bucket {
private final String mSymbol;
private final Bucket mNext;
public Bucket(String symbol, Bucket next) {
mSymbol = symbol;
mNext = next;
}
public String getSymbol() { return mSymbol; }
public Bucket getNext() { return mNext; }
public String find(char[] buf, int start, int len) {
String sym = mSymbol;
Bucket b = mNext;
while (true) { // Inlined equality comparison:
if (sym.length() == len) {
int i = 0;
do {
if (sym.charAt(i) != buf[start+i]) {
break;
}
} while (++i < len);
if (i == len) {
return sym;
}
}
if (b == null) {
break;
}
sym = b.getSymbol();
b = b.getNext();
}
return null;
}
public String find(String str) {
String sym = mSymbol;
Bucket b = mNext;
while (true) {
if (sym.equals(str)) {
return sym;
}
if (b == null) {
break;
}
sym = b.getSymbol();
b = b.getNext();
}
return null;
}
}
}
Utility methods for operating on memory-efficient maps.
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Utility methods for operating on memory-efficient maps. All maps of size 0 or
* 1 are assumed to be immutable. All maps of size greater than 1 are assumed to
* be mutable.
*/
public class Maps {
private static final Class<?> MULTI_MAP_CLASS = HashMap.class;
private static final Class<?> SINGLETON_MAP_CLASS = Collections.singletonMap(
null, null).getClass();
public static <K, V> Map<K, V> create() {
return Collections.emptyMap();
}
public static <K, V> Map<K, V> create(K key, V value) {
return Collections.singletonMap(key, value);
}
public static <K, V> Map<K, V> normalize(Map<K, V> map) {
switch (map.size()) {
case 0:
return create();
case 1: {
if (map.getClass() == SINGLETON_MAP_CLASS) {
return map;
}
K key = map.keySet().iterator().next();
return create(key, map.get(key));
}
default:
if (map.getClass() == MULTI_MAP_CLASS) {
return map;
}
return new HashMap<K, V>(map);
}
}
public static <K, V> Map<K, V> normalizeUnmodifiable(Map<K, V> map) {
if (map.size() < 2) {
return normalize(map);
} else {
// TODO: implement an UnmodifiableHashMap?
return Collections.unmodifiableMap(normalize(map));
}
}
public static <K, V> Map<K, V> put(Map<K, V> map, K key, V value) {
switch (map.size()) {
case 0:
// Empty -> Singleton
return Collections.singletonMap(key, value);
case 1: {
if (map.containsKey(key)) {
return create(key, value);
}
// Singleton -> HashMap
Map<K, V> result = new HashMap<K, V>();
result.put(map.keySet().iterator().next(),
map.values().iterator().next());
result.put(key, value);
return result;
}
default:
// HashMap
map.put(key, value);
return map;
}
}
public static <K, V> Map<K, V> putAll(Map<K, V> map, Map<K, V> toAdd) {
switch (toAdd.size()) {
case 0:
// No-op.
return map;
case 1: {
// Add one element.
K key = toAdd.keySet().iterator().next();
return put(map, key, toAdd.get(key));
}
default:
// True list merge, result >= 2.
switch (map.size()) {
case 0:
return new HashMap<K, V>(toAdd);
case 1: {
HashMap<K, V> result = new HashMap<K, V>();
K key = map.keySet().iterator().next();
result.put(key, map.get(key));
result.putAll(toAdd);
return normalize(result);
}
default:
map.putAll(toAdd);
return map;
}
}
}
public static <K, V> Map<K, V> remove(Map<K, V> map, K key) {
switch (map.size()) {
case 0:
// Empty
return map;
case 1:
// Singleton -> Empty
if (map.containsKey(key)) {
return create();
}
return map;
case 2:
// HashMap -> Singleton
if (map.containsKey(key)) {
map.remove(key);
key = map.keySet().iterator().next();
return create(key, map.get(key));
}
return map;
default:
// IdentityHashMap
map.remove(key);
return map;
}
}
}