/* * Javolution - Java(TM) Solution for Real-Time and Embedded Systems * Copyright (C) 2006 - Javolution (http://javolution.org/) * All rights reserved. * * Permission to use, copy, modify, and distribute this software is * freely granted, provided that this notice is preserved. */ package javolution.util; import java.io.IOException; import java.io.PrintStream; import j2me.io.ObjectInputStream; import j2me.io.ObjectOutputStream; import j2me.io.Serializable; import j2me.lang.IllegalStateException; import j2me.lang.UnsupportedOperationException; import j2me.util.Collection; import j2me.util.Iterator; import j2me.util.Map; import j2me.util.NoSuchElementException; import j2me.util.Set; import j2mex.realtime.MemoryArea; import javolution.context.ArrayFactory; import javolution.context.LogContext; import javolution.context.ObjectFactory; import javolution.context.PersistentContext; import javolution.lang.MathLib; import javolution.lang.Realtime; import javolution.lang.Reusable; import javolution.text.Text; import javolution.util.FastCollection.Record; import javolution.xml.XMLSerializable; /** * <p> This class represents a hash map with real-time behavior; * smooth capacity increase and <i>thread-safe</i> without external * synchronization when {@link #shared shared}.</p> * <img src="doc-files/map-put.png"/> * * <p> {@link FastMap} has a predictable iteration order, which is the order in * which keys are inserted into the map (similar to * <code>java.util.LinkedHashMap</code> collection class). If the map is * marked {@link #shared shared} then all operations are * thread-safe including iterations over the map's collections. * Unlike <code>ConcurrentHashMap</code>, {@link #get(Object) access} never * blocks; retrieval reflects the map state not older than the last time the * accessing threads have been synchronized (for multi-processors systems * synchronizing ensures that the CPU internal cache is not stale). * In most application it is not a problem because thread synchronization * is done at high level (e.g. scheduler) and threads run freely * (and quickly) until the next synchronization point. In some cases the * "happen before" guarantee is necessary (e.g. to ensure unicity) and * threads have to be synchronized explicitly. Whenever possible such * synchronization should be performed on the key object itself and * not the whole map. For example:[code] * FastMap<Index, Object> sparseVector = new FastMap<Index, Object>().shared() * ... // Put * synchronized(index) { // javolution.util.Index instances are unique. * sparseVector.put(index, value); * } * ... // Get * synchronized(index) { // Blocking only when accessing the same index. * value = sparseVector.get(index); // Latest value guaranteed. * }[/code]</p> * * <p> {@link FastMap} may use custom key comparators; the default comparator is * either {@link FastComparator#DIRECT DIRECT} or * {@link FastComparator#REHASH REHASH} based upon the current <a href= * "{@docRoot}/overview-summary.html#configuration">Javolution * Configuration</a>. Users may explicitly set the key comparator to * {@link FastComparator#DIRECT DIRECT} for optimum performance * when the hash codes are well distributed for all run-time platforms * (e.g. calculated hash codes).</p> * * <p> Custom key comparators are extremely useful for value retrieval when * map's keys and argument keys are not of the same class (such as * {@link String} and {@link javolution.text.Text Text} * ({@link FastComparator#LEXICAL LEXICAL})), to substitute more efficient * hash code calculations ({@link FastComparator#STRING STRING}) * or for identity maps ({@link FastComparator#IDENTITY IDENTITY}):[code] * FastMap identityMap = new FastMap().setKeyComparator(FastComparator.IDENTITY); * [/code]</p> * * <p> {@link FastMap.Entry} can quickly be iterated over (forward or backward) * without using iterators. For example:[code] * FastMap<String, Thread> map = ...; * for (FastMap.Entry<String, Thread> e = map.head(), end = map.tail(); (e = e.getNext()) != end;) { * String key = e.getKey(); // No typecast necessary. * Thread value = e.getValue(); // No typecast necessary. * }[/code]</p> * * <p> Custom map implementations may override the {@link #newEntry} method * in order to return their own {@link Entry} implementation (with * additional fields for example).</p> * * <p> {@link FastMap} are {@link Reusable reusable}; they maintain an * internal pool of <code>Map.Entry</code> objects. When an entry is removed * from a map, it is automatically restored to its pool. Any new entry is * allocated in the same memory area as the map itself (RTSJ). If the map * is shared, removed entries are not recycled but only dereferenced * (to maintain thread-safety) </p> * * <p> {@link #shared() Shared} maps do not use internal synchronization, except in case of * concurrent modifications of the map structure (entries being added/deleted). * Reads and iterations are never synchronized and never blocking. * With regards to the memory model, shared maps are equivalent to shared * non-volatile variables (no "happen before" guarantee). They can be used * as very efficient lookup tables. For example:[code] * public class Unit { * static FastMap<Unit, String> labels = new FastMap().shared(); * ... * public String toString() { * String label = labels.get(this); // No synchronization. * if (label != null) return label; * label = makeLabel(); * labels.put(this, label); * return label; * } * }[/code]</p> * * <p> <b>Implementation Note:</b> To maintain time-determinism, rehash/resize * is performed only when the map's size is small (see chart). For large * maps (size > 512), the map is divided recursively into (64) * smaller sub-maps. The cost of the dispatching (based upon hashcode * value) has been measured to be at most 20% of the access time * (and most often way less).</p> * * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle </a> * @version 5.2, September 11, 2007 */ public class FastMap/*<K,V>*/ implements Map/*<K,V>*/, Reusable, XMLSerializable, Realtime { // We do a full resize (and rehash) only when the capacity is less than C1. // For large maps we dispatch to sub-maps. private static final int B0 = 4; // Initial capacity in bits. private static final int C0 = 1 << B0; // Initial capacity (16) private static final int B1 = 10; // Entries array resize limit in bits. private static final int C1 = 1 << B1; // Entries array resize limit (1024). private static final int B2 = B1 - B0; // Sub-maps array length in bits. private static final int C2 = 1 << B2; // Sub-maps array length (64). /** * Holds the head entry to which the first entry attaches. * The head entry never changes (entries always added last). */ private transient Entry/*<K,V>*/ _head; /** * Holds the tail entry to which the last entry attaches. * The tail entry changes as entries are added/removed. */ private transient Entry/*<K,V>*/ _tail; /** * Holds the map's entries. */ private transient Entry/*<K,V>*/[] _entries; /** * Holds the number of user entry in the entry table. */ private transient int _entryCount; /** * Holds the number of NULL (when entry removed). The number has to be * taken into account to clean-up the table if too many NULL are present. */ private transient int _nullCount; /** * Holds sub-maps (for large collection). */ private transient FastMap[] _subMaps; /** * Indicates if sub-maps are active. */ private transient boolean _useSubMaps; /** * The hash shift (for sub-maps to discard bits already taken into account). */ private transient int _keyShift; /** * Holds the values view. */ private transient Values _values; /** * Holds the key set view. */ private transient KeySet _keySet; /** * Holds the entry set view. */ private transient EntrySet _entrySet; /** * Holds the unmodifiable view. */ private transient Map/*<K,V>*/ _unmodifiable; /** * Holds the key comparator. */ private transient FastComparator _keyComparator; /** * Indicates if key comparator is direct. */ private transient boolean _isDirectKeyComparator; /** * Holds the value comparator. */ private transient FastComparator _valueComparator; /** * Indicates if this map is shared (thread-safe). */ private transient boolean _isShared; /** * Creates a map whose capacity increment smoothly without large resize * operations. */ public FastMap() { this(4); } /** * Creates a persistent map associated to the specified unique identifier * (convenience method). * * @param id the unique identifier for this map. * @throws IllegalArgumentException if the identifier is not unique. * @see javolution.context.PersistentContext.Reference */ public FastMap(String id) { this(); new PersistentContext.Reference(id, this) { protected void notifyChange() { FastMap.this.clear(); FastMap.this.putAll((FastMap) this.get()); } }; } /** * Creates a map of specified maximum size (a full resize may occur if the * specififed capacity is exceeded). * * @param capacity the maximum capacity. */ public FastMap(int capacity) { setKeyComparator(FastComparator.DEFAULT); setValueComparator(FastComparator.DEFAULT); setup(capacity); } private void setup(int capacity) { int tableLength = C0; while (tableLength < capacity) { tableLength <<= 1; } _entries = (Entry/*<K,V>*/[]) new Entry[tableLength << 1]; _head = newEntry(); _tail = newEntry(); _head._next = _tail; _tail._previous = _head; Entry previous = _tail; for (int i = 0; i++ < capacity;) { Entry newEntry = newEntry(); newEntry._previous = previous; previous._next = newEntry; previous = newEntry; } } /** * Creates a map containing the specified entries, in the order they * are returned by the map iterator. * * @param map the map whose entries are to be placed into this map. */ public FastMap(Map/*<? extends K, ? extends V>*/ map) { this(map.size()); putAll(map); } /** * Used solely for sub-maps (we don't need head or tail just the table). */ private FastMap(Entry[] entries) { _entries = entries; } /** * Returns a potentially {@link #recycle recycled} map instance. * * @return a new, preallocated or recycled map instance. */ public static/*<K,V>*/ FastMap/*<K,V>*/ newInstance() { return (FastMap/*<K,V>*/) FACTORY.object(); } /** * Recycles the specified map instance. * * @param instance the map instance to recycle. */ public static void recycle(FastMap instance) { FACTORY.recycle(instance); } /** * Returns the reverse mapping of this map (for which the keys are this * map's values and the values are this map's keys). * * @return the map having for keys this map values and for values * this map's key. */ public final FastMap/*<V,K>*/ reverse() { FastMap/*<V,K>*/ map = (FastMap/*<V,K>*/) FACTORY.object(); for (Entry/*<K,V>*/ e = _head, n = _tail; (e = e._next) != n;) { map.put(e._value, e._key); } return map; } /** * Returns the head entry of this map. * * @return the entry such as <code>head().getNext()</code> holds * the first map entry. */ public final Entry/*<K,V>*/ head() { return _head; } /** * Returns the tail entry of this map. * * @return the entry such as <code>tail().getPrevious()</code> * holds the last map entry. */ public final Entry/*<K,V>*/ tail() { return _tail; } /** * Returns the number of key-value mappings in this {@link FastMap}. * * <p>Note: If concurrent updates are performed, application should not * rely upon the size during iterations.</p> * * @return this map's size. */ public final int size() { if (!_useSubMaps) { return _entryCount; } int sum = 0; for (int i = 0; i < _subMaps.length;) { sum += _subMaps[i++].size(); } return sum; } /** * Indicates if this map contains no key-value mappings. * * @return <code>true</code> if this map contains no key-value mappings; * <code>false</code> otherwise. */ public final boolean isEmpty() { return _head._next == _tail; } /** * Indicates if this map 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 final boolean containsKey(Object key) { return getEntry(key) != null; } /** * Indicates if this map associates 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 final boolean containsValue(Object value) { return values().contains(value); } /** * Returns the value to which this map associates the specified key. * This method is always thread-safe regardless whether or not the map * is marked {@link #isShared() shared}. * * @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 final Object/*{V}*/ get(Object key) { Entry/*<K,V>*/ entry = getEntry(key); return (entry != null) ? entry._value : null; } /** * Returns the entry with the specified key. * This method is always thread-safe without synchronization. * * @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 final Entry/*<K,V>*/ getEntry(Object key) { return getEntry(key, _isDirectKeyComparator ? key.hashCode() : _keyComparator.hashCodeOf(key)); } private final Entry getEntry(Object key, int keyHash) { final FastMap map = getSubMap(keyHash); final Entry[] entries = map._entries; // Atomic. final int mask = entries.length - 1; for (int i = keyHash >> map._keyShift;; i++) { Entry entry = entries[i & mask]; if (entry == null) { return null; } if ((key == entry._key) || ((keyHash == entry._keyHash) && (_isDirectKeyComparator ? key.equals(entry._key) : _keyComparator.areEqual(key, entry._key)))) { return entry; } } } private final FastMap getSubMap(int keyHash) { return _useSubMaps ? _subMaps[keyHash & (C2 - 1)].getSubMap(keyHash >> B2) : this; } /** * Associates the specified value with the specified key in this map. * If this map previously contained a mapping for this key, the old value * is replaced. For {@link #isShared() shared} map, internal synchronization * is performed only when new entries are created. * * @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 final Object/*{V}*/ put(Object/*{K}*/ key, Object/*{V}*/ value) { return (Object/*{V}*/) put(key, value, _isDirectKeyComparator ? key.hashCode() : _keyComparator.hashCodeOf(key), _isShared, false, false); } private final Object put(Object key, Object value, int keyHash, boolean concurrent, boolean noReplace, boolean returnEntry) { final FastMap map = getSubMap(keyHash); final Entry[] entries = map._entries; // Atomic. final int mask = entries.length - 1; int slot = -1; for (int i = keyHash >> map._keyShift;; i++) { Entry entry = entries[i & mask]; if (entry == null) { slot = slot < 0 ? i & mask : slot; break; } else if (entry == Entry.NULL) { slot = slot < 0 ? i & mask : slot; } else if ((key == entry._key) || ((keyHash == entry._keyHash) && (_isDirectKeyComparator ? key.equals(entry._key) : _keyComparator.areEqual(key, entry._key)))) { if (noReplace) { return returnEntry ? entry : entry._value; } Object prevValue = entry._value; entry._value = value; return returnEntry ? entry : prevValue; } } // Add new entry (synchronize if concurrent). if (concurrent) { synchronized (this) { return put(key, value, keyHash, false, noReplace, returnEntry); } } // Setup entry. final Entry entry; if(!_isShared) { entry = _tail; entry._key = key; entry._value = value; entry._keyHash = keyHash; if (entry._next == null) { createNewEntries(); } entries[slot] = entry; map._entryCount += ONE_VOLATILE; // Prevents reordering. _tail = _tail._next; } else { // keep _tail as the same object // check entry caches if (_tail._next == null) { createNewEntries(); } // assign entry entry = _tail._next; // step forward in the entry cache _tail._next = entry._next; // populate entry entry._key = key; entry._value = value; entry._keyHash = keyHash; entry._next = _tail; entry._previous = _tail._previous; // backwards // set the hash table slots entries[slot] = entry; map._entryCount += ONE_VOLATILE; // Prevents reordering. // insert into chain at correct location entry._next._previous = entry; // backwards entry._previous._next = entry; // forwards } if (map._entryCount + map._nullCount > (entries.length >> 1)) { // Table more than half empty. map.resizeTable(_isShared); } return returnEntry ? entry : null; } private void createNewEntries() { // Increase the number of entries. MemoryArea.getMemoryArea(this).executeInArea(new Runnable() { public void run() { Entry previous = _tail; for (int i = 0; i < 8; i++) { // Creates 8 entries at a time. Entry/*<K,V>*/ newEntry = newEntry(); newEntry._previous = previous; previous._next = newEntry; previous = newEntry; } } }); } // This method is called only on final sub-maps. private void resizeTable(final boolean isShared) { MemoryArea.getMemoryArea(this).executeInArea(new Runnable() { public void run() { // Reset the NULL count (we don't copy Entry.NULL). final int nullCount = _nullCount; _nullCount = 0; // Check if we can just cleanup (remove NULL entries). if (nullCount > _entryCount) { // Yes. if (isShared) { // Replaces with a new table. Entry[] tmp = new Entry[_entries.length]; copyEntries(_entries, tmp, _entries.length); _entries = tmp; } else { // We need a temporary buffer. Object[] tmp = (Object[]) ArrayFactory.OBJECTS_FACTORY.array(_entries.length); System.arraycopy(_entries, 0, tmp, 0, _entries.length); FastMap.reset(_entries); // Ok not shared. copyEntries(tmp, _entries, _entries.length); FastMap.reset(tmp); // Clear references. ArrayFactory.OBJECTS_FACTORY.recycle(tmp); } return; } // Resize if size is small. int tableLength = _entries.length << 1; if (tableLength <= C1) { // Ok to resize. Entry[] tmp = new Entry[tableLength]; copyEntries(_entries, tmp, _entries.length); _entries = tmp; return; } // No choice but to use sub-maps. if (_subMaps == null) { // Creates sub-maps. _subMaps = newSubMaps(tableLength >> (B2 - 1)); } // Copies the current entries to sub-maps. for (int i = 0; i < _entries.length;) { Entry entry = _entries[i++]; if ((entry == null) || (entry == Entry.NULL)) { continue; } FastMap subMap = _subMaps[(entry._keyHash >> _keyShift) & (C2 - 1)]; subMap.mapEntry(entry); if (((subMap._entryCount + subMap._nullCount) << 1) >= subMap._entries.length) { // Serious problem submap already full, don't use submap just resize. LogContext.warning("Unevenly distributed hash code - Degraded Performance"); Entry[] tmp = new Entry[tableLength]; copyEntries(_entries, tmp, _entries.length); _entries = tmp; _subMaps = null; // Discards sub-maps. return; } } if(isShared) { // clear the entries which now are held by submaps FastMap.reset(_entries); _nullCount = 0; _entryCount = 0; } _useSubMaps = ONE_VOLATILE == 1 ? true : false; // Prevents reordering. } }); } private FastMap[] newSubMaps(int capacity) { FastMap[] subMaps = new FastMap[C2]; for (int i = 0; i < C2; i++) { FastMap subMap = new FastMap(new Entry[capacity]); subMap._keyShift = B2 + _keyShift; subMaps[i] = subMap; } return subMaps; } // Adds the specified entry to this map table. private void mapEntry(Entry entry) { final int mask = _entries.length - 1; for (int i = entry._keyHash >> _keyShift;; i++) { Entry e = _entries[i & mask]; if (e == null) { _entries[i & mask] = entry; break; } } _entryCount++; } // The destination table must be empty. private void copyEntries(Object[] from, Entry[] to, int count) { final int mask = to.length - 1; for (int i = 0; i < count;) { Entry entry = (Entry) from[i++]; if ((entry == null) || (entry == Entry.NULL)) { continue; } for (int j = entry._keyHash >> _keyShift;; j++) { Entry tmp = to[j & mask]; if (tmp == null) { to[j & mask] = entry; break; } } } } /** * Associates the specified value with the specified key in this map and * returns the corresponding entry. * * @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 entry being added. * @throws NullPointerException if the key is <code>null</code>. */ public final Entry/*<K,V>*/ putEntry(Object/*{K}*/ key, Object/*{V}*/ value) { return (Entry/*<K,V>*/) put(key, value, _isDirectKeyComparator ? key.hashCode() : _keyComparator.hashCodeOf(key), _isShared, false, true); } /** * Copies all of the mappings from the specified map to this map. * * @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 final void putAll(Map/*<? extends K, ? extends V>*/ map) { for (Iterator i = map.entrySet().iterator(); i.hasNext();) { Map.Entry/*<K,V>*/ e = (Map.Entry/*<K,V>*/) i.next(); put(e.getKey(), e.getValue()); } } /** * Associates the specified value only if the specified key is not already * associated. This is equivalent to:[code] * if (!map.containsKey(key)) * return map.put(key, value); * else * return map.get(key);[/code] * except that for shared maps the action is performed atomically. * For shared maps, this method guarantees that if two threads try to * put the same key concurrently only one of them will succeed. * * @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 final Object/*{V}*/ putIfAbsent(Object/*{K}*/ key, Object/*{V}*/ value) { return (Object/*{V}*/) put(key, value, _isDirectKeyComparator ? key.hashCode() : _keyComparator.hashCodeOf(key), _isShared, true, false); } /** * Removes the entry for the specified key if present. The entry * is recycled if the map is not marked as {@link #isShared shared}; * otherwise the entry is candidate for garbage collection. * * <p> Note: Shared maps in ImmortalMemory (e.g. static) should not remove * their entries as it could cause a memory leak (ImmortalMemory * is never garbage collected), instead they should set their * entry values to <code>null</code>.</p> * * @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 final Object/*{V}*/ remove(Object key) { return (Object/*{V}*/) remove(key, _isDirectKeyComparator ? key.hashCode() : _keyComparator.hashCodeOf(key), _isShared); } private final Object remove(Object key, int keyHash, boolean concurrent) { final FastMap map = getSubMap(keyHash); final Entry[] entries = map._entries; // Atomic. final int mask = entries.length - 1; for (int i = keyHash >> map._keyShift;; i++) { Entry entry = entries[i & mask]; if (entry == null) { return null; } // No entry. if ((key == entry._key) || ((keyHash == entry._keyHash) && (_isDirectKeyComparator ? key.equals(entry._key) : _keyComparator.areEqual(key, entry._key)))) { // Found the entry. if (concurrent) { synchronized (this) { return remove(key, keyHash, false); } } // Detaches entry from list. entry._previous._next = entry._next; entry._next._previous = entry._previous; // Removes from table. entries[i & mask] = Entry.NULL; map._nullCount++; map._entryCount--; Object prevValue = entry._value; if (!_isShared) { // Clears key/value and recycle. entry._key = null; entry._value = null; final Entry next = _tail._next; entry._previous = _tail; entry._next = next; _tail._next = entry; if (next != null) { next._previous = entry; } } else { // do nothing, preserving the iterator-free iterations of other threads } return prevValue; } } } /** * <p> Sets the shared status of this map (whether the map is thread-safe * or not). Shared maps are typically used for lookup table (e.g. static * instances in ImmortalMemory). They support concurrent access * (e.g. iterations) without synchronization, the maps updates * themselves are synchronized internally.</p> * <p> Unlike <code>ConcurrentHashMap</code> access to a shared map never * blocks. Retrieval reflects the map state not older than the last * time the accessing thread has been synchronized (for multi-processors * systems synchronizing ensures that the CPU internal cache is not * stale).</p> * * @return <code>this</code> */ public FastMap/*<K,V>*/ shared() { _isShared = true; return this; } /** * @deprecated Replaced by {@link #shared} */ public FastMap/*<K,V>*/ setShared(boolean isShared) { _isShared = isShared; return this; } /** * Indicates if this map supports concurrent operations without * synchronization (default unshared). * * @return <code>true</code> if this map is thread-safe; <code>false</code> * otherwise. */ public boolean isShared() { return _isShared; } /** * Sets the key comparator for this fast map. * * @param keyComparator the key comparator. * @return <code>this</code> */ public FastMap/*<K,V>*/ setKeyComparator( FastComparator/*<? super K>*/ keyComparator) { _keyComparator = keyComparator; _isDirectKeyComparator = (keyComparator == FastComparator.DIRECT) || ((_keyComparator == FastComparator.DEFAULT) && !((Boolean) FastComparator.REHASH_SYSTEM_HASHCODE.get()).booleanValue()); return this; } /** * Returns the key comparator for this fast map. * * @return the key comparator. */ public FastComparator/*<? super K>*/ getKeyComparator() { return _keyComparator; } /** * Sets the value comparator for this map. * * @param valueComparator the value comparator. * @return <code>this</code> */ public FastMap/*<K,V>*/ setValueComparator( FastComparator/*<? super V>*/ valueComparator) { _valueComparator = valueComparator; return this; } /** * Returns the value comparator for this fast map. * * @return the value comparator. */ public FastComparator/*<? super V>*/ getValueComparator() { return _valueComparator; } /** * Removes all map's entries. The entries are removed and recycled; * unless this map is {@link #isShared shared} in which case the entries * are candidate for garbage collection. * * <p> Note: Shared maps in ImmortalMemory (e.g. static) should not remove * their entries as it could cause a memory leak (ImmortalMemory * is never garbage collected), instead they should set their * entry values to <code>null</code>.</p> */ public final void clear() { if (_isShared) { clearShared(); return; } // Clears keys, values and recycle entries. for (Entry e = _head, end = _tail; (e = e._next) != end;) { e._key = null; e._value = null; } _tail = _head._next; // Reuse linked list of entries. clearTables(); } private void clearTables() { if (_useSubMaps) { for (int i = 0; i < C2;) { _subMaps[i++].clearTables(); } _useSubMaps = false; } FastMap.reset(_entries); _nullCount = 0; _entryCount = 0; } private synchronized void clearShared() { // We do not modify the linked list of entries (e.g. key, values) // Concurrent iterations can still proceed unaffected. // The linked list fragment is detached from the map and will be // garbage collected once all concurrent iterations are completed. _head._next = _tail; _tail._previous = _head; // We also detach the main entry table and sub-maps. MemoryArea.getMemoryArea(this).executeInArea(new Runnable() { public void run() { _entries = (Entry/*<K,V>*/[]) new Entry[C0]; if (_useSubMaps) { _useSubMaps = false; _subMaps = newSubMaps(C0); } _entryCount = 0; _nullCount = 0; } }); } /** * Compares the specified object with this map 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; return this.entrySet().equals(that.entrySet()); } else { return false; } } /** * Returns the hash code value for this map. * * @return the hash code value for this map. */ public int hashCode() { int code = 0; for (Entry e = _head, end = _tail; (e = e._next) != end;) { code += e.hashCode(); } return code; } /** * Returns the textual representation of this map. * * @return the textual representation of the entry set. */ public Text toText() { return Text.valueOf(entrySet()); } /** * Returns the <code>String</code> representation of this * {@link FastMap}. * * @return <code>toText().toString()</code> */ public final String toString() { return toText().toString(); } /** * Returns a new entry for this map; this method can be overriden by * custom map implementations. * * @return a new entry. */ protected Entry/*<K,V>*/ newEntry() { return new Entry(); } /** * Prints the current statistics on this map. * This method may help identify poorly defined hash functions. * The average distance should be less than 20% (most of the entries * are in their slots or close). * * @param out the stream to use for output (e.g. <code>System.out</code>) */ public void printStatistics(PrintStream out) { long sum = this.getSumDistance(); int size = this.size(); int avgDistancePercent = size != 0 ? (int) (100 * sum / size) : 0; synchronized (out) { out.print("SIZE: " + size); out.print(", ENTRIES: " + getCapacity()); out.print(", SLOTS: " + getTableLength()); out.print(", USE SUB-MAPS: " + _useSubMaps); out.print(", SUB-MAPS DEPTH: " + getSubMapDepth()); out.print(", NULL COUNT: " + _nullCount); out.print(", IS SHARED: " + _isShared); out.print(", AVG DISTANCE: " + avgDistancePercent + "%"); out.print(", MAX DISTANCE: " + getMaximumDistance()); out.println(); } } private int getTableLength() { if (_useSubMaps) { int sum = 0; for (int i = 0; i < C2; i++) { sum += _subMaps[i].getTableLength(); } return sum; } else { return _entries.length; } } private int getCapacity() { int capacity = 0; for (Entry e = _head; (e = e._next) != null;) { capacity++; } return capacity - 1; } private int getMaximumDistance() { int max = 0; if (_useSubMaps) { for (int i = 0; i < C2; i++) { int subMapMax = _subMaps[i].getMaximumDistance(); max = MathLib.max(max, subMapMax); } return max; } for (int i = 0; i < _entries.length; i++) { Entry entry = _entries[i]; if ((entry != null) && (entry != Entry.NULL)) { int slot = (entry._keyHash >> _keyShift) & (_entries.length - 1); int distanceToSlot = i - slot; if (distanceToSlot < 0) { distanceToSlot += _entries.length; } if (distanceToSlot > max) { max = distanceToSlot; } } } return max; } private long getSumDistance() { long sum = 0; if (_useSubMaps) { for (int i = 0; i < C2; i++) { sum += _subMaps[i].getSumDistance(); } return sum; } for (int i = 0; i < _entries.length; i++) { Entry entry = _entries[i]; if ((entry != null) && (entry != Entry.NULL)) { int slot = (entry._keyHash >> _keyShift) & (_entries.length - 1); int distanceToSlot = i - slot; if (distanceToSlot < 0) { distanceToSlot += _entries.length; } sum += distanceToSlot; } } return sum; } private int getSubMapDepth() { if (_useSubMaps) { int depth = 0; for (int i = 0; i < C2; i++) { int subMapDepth = _subMaps[i].getSubMapDepth(); depth = MathLib.max(depth, subMapDepth); } return depth + 1; } else { return 0; } } /** * Returns a {@link FastCollection} 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 * (instance of {@link FastCollection}). */ public final Collection/*<V>*/ values() { if (_values == null) { MemoryArea.getMemoryArea(this).executeInArea(new Runnable() { public void run() { _values = new Values(); } }); } return _values; } private final class Values extends FastCollection { public int size() { return FastMap.this.size(); } public void clear() { FastMap.this.clear(); } public Record head() { return FastMap.this._head; } public Record tail() { return FastMap.this._tail; } public Object valueOf(Record record) { return ((Entry) record)._value; } public void delete(Record record) { FastMap.this.remove(((Entry) record).getKey()); } public FastComparator getValueComparator() { return _valueComparator; } public Iterator iterator() { return ValueIterator.valueOf(FastMap.this); } } // Value iterator optimization. private static final class ValueIterator implements Iterator { private static final ObjectFactory FACTORY = new ObjectFactory() { protected Object create() { return new ValueIterator(); } protected void cleanup(Object obj) { ValueIterator iterator = (ValueIterator) obj; iterator._map = null; iterator._current = null; iterator._next = null; iterator._tail = null; } }; private FastMap _map; private Entry _current; private Entry _next; private Entry _tail; public static ValueIterator valueOf(FastMap map) { ValueIterator iterator = (ValueIterator) ValueIterator.FACTORY.object(); iterator._map = map; iterator._next = map._head._next; iterator._tail = map._tail; return iterator; } private ValueIterator() { } public boolean hasNext() { return (_next != _tail); } public Object next() { if (_next == _tail) { throw new NoSuchElementException(); } _current = _next; _next = _next._next; return _current._value; } public void remove() { if (_current != null) { _next = _current._next; _map.remove(_current._key); _current = null; } else { throw new IllegalStateException(); } } } /** * Returns a {@link FastCollection} view of the mappings contained in this * map. Each element in the returned collection is a * <code>FastMap.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 * (instance of {@link FastCollection}). */ public final Set/*<Map.Entry<K,V>>*/ entrySet() { if (_entrySet == null) { MemoryArea.getMemoryArea(this).executeInArea(new Runnable() { public void run() { _entrySet = new EntrySet(); } }); } return _entrySet; } private final class EntrySet extends FastCollection implements Set { public int size() { return FastMap.this.size(); } public void clear() { FastMap.this.clear(); } public boolean contains(Object obj) { // Optimization. if (obj instanceof Map.Entry) { Map.Entry thatEntry = (Map.Entry) obj; Entry thisEntry = getEntry(thatEntry.getKey()); if (thisEntry == null) { return false; } return _valueComparator.areEqual(thisEntry.getValue(), thatEntry.getValue()); } else { return false; } } public Record head() { return _head; } public Record tail() { return _tail; } public Object valueOf(Record record) { return (Map.Entry) record; } public void delete(Record record) { FastMap.this.remove(((Entry) record).getKey()); } public FastComparator getValueComparator() { return _entryComparator; } private final FastComparator _entryComparator = new FastComparator() { public boolean areEqual(Object o1, Object o2) { if ((o1 instanceof Map.Entry) && (o2 instanceof Map.Entry)) { Map.Entry e1 = (Map.Entry) o1; Map.Entry e2 = (Map.Entry) o2; return _keyComparator.areEqual(e1.getKey(), e2.getKey()) && _valueComparator.areEqual(e1.getValue(), e2.getValue()); } return (o1 == null) && (o2 == null); } public int compare(Object o1, Object o2) { return _keyComparator.compare(o1, o2); } public int hashCodeOf(Object obj) { Map.Entry entry = (Map.Entry) obj; return _keyComparator.hashCodeOf(entry.getKey()) + _valueComparator.hashCodeOf(entry.getValue()); } }; public Iterator iterator() { return EntryIterator.valueOf(FastMap.this); } } // Entry iterator optimization. private static final class EntryIterator implements Iterator { private static final ObjectFactory FACTORY = new ObjectFactory() { protected Object create() { return new EntryIterator(); } protected void cleanup(Object obj) { EntryIterator iterator = (EntryIterator) obj; iterator._map = null; iterator._current = null; iterator._next = null; iterator._tail = null; } }; private FastMap _map; private Entry _current; private Entry _next; private Entry _tail; public static EntryIterator valueOf(FastMap map) { EntryIterator iterator = (EntryIterator) EntryIterator.FACTORY.object(); iterator._map = map; iterator._next = map._head._next; iterator._tail = map._tail; return iterator; } private EntryIterator() { } public boolean hasNext() { return (_next != _tail); } public Object next() { if (_next == _tail) { throw new NoSuchElementException(); } _current = _next; _next = _next._next; return _current; } public void remove() { if (_current != null) { _next = _current._next; _map.remove(_current._key); _current = null; } else { throw new IllegalStateException(); } } } /** * Returns a {@link FastCollection} 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>Collection.remove</code>,<code>removeAll<f/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 * (instance of {@link FastCollection}). */ public final Set/*<K>*/ keySet() { if (_keySet == null) { MemoryArea.getMemoryArea(this).executeInArea(new Runnable() { public void run() { _keySet = new KeySet(); } }); } return _keySet; } private final class KeySet extends FastCollection implements Set { public int size() { return FastMap.this.size(); } public void clear() { FastMap.this.clear(); } public boolean contains(Object obj) { // Optimization. return FastMap.this.containsKey(obj); } public boolean remove(Object obj) { // Optimization. return FastMap.this.remove(obj) != null; } public Record head() { return FastMap.this._head; } public Record tail() { return FastMap.this._tail; } public Object valueOf(Record record) { return ((Entry) record)._key; } public void delete(Record record) { FastMap.this.remove(((Entry) record).getKey()); } public FastComparator getValueComparator() { return _keyComparator; } public Iterator iterator() { return KeyIterator.valueOf(FastMap.this); } } // Entry iterator optimization. private static final class KeyIterator implements Iterator { private static final ObjectFactory FACTORY = new ObjectFactory() { protected Object create() { return new KeyIterator(); } protected void cleanup(Object obj) { KeyIterator iterator = (KeyIterator) obj; iterator._map = null; iterator._current = null; iterator._next = null; iterator._tail = null; } }; private FastMap _map; private Entry _current; private Entry _next; private Entry _tail; public static KeyIterator valueOf(FastMap map) { KeyIterator iterator = (KeyIterator) KeyIterator.FACTORY.object(); iterator._map = map; iterator._next = map._head._next; iterator._tail = map._tail; return iterator; } private KeyIterator() { } public boolean hasNext() { return (_next != _tail); } public Object next() { if (_next == _tail) { throw new NoSuchElementException(); } _current = _next; _next = _next._next; return _current._key; } public void remove() { if (_current != null) { _next = _current._next; _map.remove(_current._key); _current = null; } else { throw new IllegalStateException(); } } } /** * Returns the unmodifiable view associated to this map. * Attempts to modify the returned map or to directly access its * (modifiable) map entries (e.g. <code>unmodifiable().entrySet()</code>) * result in an {@link UnsupportedOperationException} being thrown. * Unmodifiable {@link FastCollection} views of this map keys and values * are nonetheless obtainable (e.g. <code>unmodifiable().keySet(), * <code>unmodifiable().values()</code>). * * @return an unmodifiable view of this map. */ public final Map/*<K,V>*/ unmodifiable() { if (_unmodifiable == null) { MemoryArea.getMemoryArea(this).executeInArea(new Runnable() { public void run() { _unmodifiable = new Unmodifiable(); } }); } return _unmodifiable; } // Implements Reusable. public void reset() { _isShared = false; // A shared map can only be reset if no thread use it. clear(); // In which case, it is safe to recycle the entries. setKeyComparator(FastComparator.DEFAULT); setValueComparator(FastComparator.DEFAULT); } /** * 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 { setKeyComparator((FastComparator) stream.readObject()); setValueComparator((FastComparator) stream.readObject()); _isShared = stream.readBoolean(); final int size = stream.readInt(); setup(size); for (int i = 0; i < size; i++) { Object/*{K}*/ key = (Object/*{K}*/) stream.readObject(); Object/*{V}*/ value = (Object/*{V}*/) stream.readObject(); put(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.writeObject(getKeyComparator()); stream.writeObject(getValueComparator()); stream.writeBoolean(_isShared); stream.writeInt(size()); for (Entry e = _head, end = _tail; (e = e._next) != end;) { stream.writeObject(e._key); stream.writeObject(e._value); } } /** * This class represents a {@link FastMap} entry. * Custom {@link FastMap} may use a derived implementation. * For example:[code] * static class MyMap<K,V,X> extends FastMap<K,V> { * protected MyEntry newEntry() { * return new MyEntry(); * } * class MyEntry extends Entry<K,V> { * X xxx; // Additional entry field (e.g. cross references). * } * }[/code] */ public static class Entry/*<K,V>*/ implements Map.Entry/*<K,V>*/, Record, Realtime { /** * Holds NULL entries (to fill empty hole). */ public static final Entry NULL = new Entry(); /** * Holds the next node. */ private Entry/*<K,V>*/ _next; /** * Holds the previous node. */ private Entry/*<K,V>*/ _previous; /** * Holds the entry key. */ private Object/*{K}*/ _key; /** * Holds the entry value. */ private Object/*{V}*/ _value; /** * Holds the key hash code. */ private int _keyHash; /** * Default constructor. */ protected Entry() { } /** * Returns the entry after this one. * * @return the next entry. */ public final Record/*Entry<K,V>*/ getNext() { return _next; } /** * Returns the entry before this one. * * @return the previous entry. */ public final Record/*Entry<K,V>*/ getPrevious() { return _previous; } /** * Returns the key for this entry. * * @return the entry key. */ public final Object/*{K}*/ getKey() { return _key; } /** * Returns the value for this entry. * * @return the entry value. */ public final Object/*{V}*/ getValue() { return _value; } /** * Sets the value for this entry. * * @param value the new value. * @return the previous value. */ public final Object/*{V}*/ setValue(Object/*{V}*/ value) { Object/*{V}*/ old = _value; _value = value; return old; } /** * Indicates if this entry is considered equals to the specified entry * (using default value and key equality comparator to ensure symetry). * * @param that the object to test for equality. * @return <code>true<code> if both entry have equal keys and values. * <code>false<code> otherwise. */ public boolean equals(Object that) { if (that instanceof Map.Entry) { Map.Entry entry = (Map.Entry) that; return FastComparator.DEFAULT.areEqual(_key, entry.getKey()) && FastComparator.DEFAULT.areEqual(_value, entry.getValue()); } else { return false; } } /** * Returns the hash code for this entry. * * @return this entry hash code. */ public int hashCode() { return ((_key != null) ? _key.hashCode() : 0) ^ ((_value != null) ? _value.hashCode() : 0); } // Implements abstract method. public Text toText() { // We use Text concatenation instead of TextBuilder here to avoid // copying the text representation of the keys/values (unknown length) return Text.valueOf(_key).plus("=").plus(_value); } } /** * This class represents an read-only view over a {@link FastMap}. */ private final class Unmodifiable implements Map, Serializable { public boolean equals(Object obj) { return FastMap.this.equals(obj); } public int hashCode() { return FastMap.this.hashCode(); } public Text toText() { return FastMap.this.toText(); } public int size() { return FastMap.this.size(); } public boolean isEmpty() { return FastMap.this.isEmpty(); } public boolean containsKey(Object key) { return FastMap.this.containsKey(key); } public boolean containsValue(Object value) { return FastMap.this.containsValue(value); } public Object get(Object key) { return FastMap.this.get(key); } public Object put(Object key, Object value) { throw new UnsupportedOperationException("Unmodifiable map"); } public Object remove(Object key) { throw new UnsupportedOperationException("Unmodifiable map"); } public void putAll(Map map) { throw new UnsupportedOperationException("Unmodifiable map"); } public void clear() { throw new UnsupportedOperationException("Unmodifiable map"); } public Set keySet() { return (Set) ((KeySet) FastMap.this.keySet()).unmodifiable(); } public Collection values() { return ((Values) FastMap.this.values()).unmodifiable(); } public Set entrySet() { throw new UnsupportedOperationException( "Direct view over unmodifiable map entries is not supported " + " (to prevent access to Entry.setValue(Object) method). " + "To iterate over unmodifiable map entries, applications may " + "use the keySet() and values() fast collection views " + "in conjonction."); } } // Holds the map factory. private static final ObjectFactory FACTORY = new ObjectFactory() { public Object create() { return new FastMap(); } }; // Reset the specified table. private static void reset(Object[] entries) { for (int i = 0; i < entries.length;) { int count = MathLib.min(entries.length - i, C1); System.arraycopy(NULL_ENTRIES, 0, entries, i, count); i += count; } } private static final Entry[] NULL_ENTRIES = new Entry[C1]; static volatile int ONE_VOLATILE = 1; // To prevent reordering. private static final long serialVersionUID = 1L; }