/* * Copyright (c) 1998, 2000, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.tools.jdi; import java.io.*; import java.util.*; /** * Hash table based implementation of the Map interface. This implementation * provides all of the optional Map operations, and permits null values and * the null key. (HashMap is roughly equivalent to Hashtable, except that it * is unsynchronized and permits nulls.) In addition, elements in the map are * ordered and doubly linked together. * <p> * This implementation provides constant-time performance for the basic * operations (get and put), assuming the the hash function disperses the * elements properly among the buckets. Iteration over Collection views * requires time proportional to its size (the number of key-value mappings) * and returns elements in the order they are linked. In a HashMap the * iteration would require time proportional to the capacity of the map * plus the map size. * <p> * An instance of LinkedHashMap has two parameters that affect its efficiency: * its <i>capacity</i> and its <i>load factor</i>. The load factor should be * between 0.0 and 1.0. When the number of mappings in the LinkedHashMap exceeds * the product of the load factor and the current capacity, the capacity is * increased by calling the rehash method which requires time proportional * to the number of key-value mappings in the map. Larger load factors * use memory more efficiently, at the expense of larger expected time per * lookup. * <p> * If many mappings are to be stored in a LinkedHashMap, creating it with a * sufficiently large capacity will allow the mappings to be stored more * efficiently than letting it perform automatic rehashing as needed to grow * the table. * <p> * <strong>Note that this implementation is not synchronized.</strong> If * multiple threads access a LinkedHashMap concurrently, and at least one of the * threads modifies the LinkedHashMap structurally, it <em>must</em> be * synchronized externally. (A structural modification is any operation that * adds or deletes one or more mappings; merely changing the value associated * with a key that is already contained in the Table is not a structural * modification.) This is typically accomplished by synchronizing on some * object that naturally encapsulates the LinkedHashMap. If no such object * exists, the LinkedHashMap should be "wrapped" using the * Collections.synchronizedSet method. This is best done at creation time, to * prevent accidental unsynchronized access to the LinkedHashMap: * <pre> * Map m = Collections.synchronizedMap(new LinkedHashMap(...)); * </pre> * <p> * The Iterators returned by the iterator methods of the Collections returned * by all of LinkedHashMap's "collection view methods" are <em>fail-fast</em>: * if the LinkedHashMap is structurally modified at any time after the Iterator * is created, in any way except through the Iterator's own remove or add * methods, the Iterator will throw a ConcurrentModificationException. 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. * * @author Josh Bloch * @author Arthur van Hoff * @author Zhenghua Li * @see Object#hashCode() * @see java.util.Collection * @see java.util.Map * @see java.util.TreeMap * @see java.util.Hashtable * @see java.util.HashMap */ import java.io.Serializable; public class LinkedHashMap extends AbstractMap implements Map, Serializable { /** * The hash table data. */ private transient Entry table[]; /** * The head of the double linked list. */ private transient Entry header; /** * The total number of mappings in the hash table. */ private transient int count; /** * Rehashes the table when count exceeds this threshold. */ private int threshold; /** * The load factor for the LinkedHashMap. */ private float loadFactor; /** * The number of times this LinkedHashMap has been structurally modified * Structural modifications are those that change the number of mappings in * the LinkedHashMap or otherwise modify its internal structure (e.g., * rehash). This field is used to make iterators on Collection-views of * the LinkedHashMap fail-fast. (See ConcurrentModificationException). */ private transient int modCount = 0; /** * Constructs a new, empty LinkedHashMap with the specified initial * capacity and the specified load factor. * * @param initialCapacity the initial capacity of the LinkedHashMap. * @param loadFactor a number between 0.0 and 1.0. * @exception IllegalArgumentException if the initial capacity is less * than or equal to zero, or if the load factor is less than * or equal to zero. */ public LinkedHashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Initial Capacity: "+ initialCapacity); if ((loadFactor > 1) || (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); header = new Entry(-1, null, null, null); header.before = header.after = header; } /** * Constructs a new, empty LinkedHashMap with the specified initial capacity * and default load factor. * * @param initialCapacity the initial capacity of the LinkedHashMap. */ public LinkedHashMap(int initialCapacity) { this(initialCapacity, 0.75f); } /** * Constructs a new, empty LinkedHashMap with a default capacity and load * factor. */ public LinkedHashMap() { this(101, 0.75f); } /** * Constructs a new LinkedHashMap with the same mappings as the given * Map. The LinkedHashMap is created with a capacity of thrice the number * of mappings in the given Map or 11 (whichever is greater), and a * default load factor. */ public LinkedHashMap(Map t) { this(Math.max(3*t.size(), 11), 0.75f); putAll(t); } /** * Returns the number of key-value mappings in this Map. */ public int size() { return count; } /** * Returns true if this Map contains no key-value mappings. */ public boolean isEmpty() { return count == 0; } /** * Returns true if this LinkedHashMap maps one or more keys to the specified * value. * * @param value value whose presence in this Map is to be tested. */ public boolean containsValue(Object value) { if (value==null) { for (Entry e = header.after; e != header; e = e.after) if (e.value==null) return true; } else { for (Entry e = header.after; e != header; e = e.after) if (value.equals(e.value)) return true; } return false; } /** * Returns true if this LinkedHashMap contains a mapping for the specified * key. * * @param key key whose presence in this Map is to be tested. */ public boolean containsKey(Object key) { Entry tab[] = table; if (key != null) { int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index]; e != null; e = e.next) if (e.hash==hash && e.key.equals(key)) return true; } else { for (Entry e = tab[0]; e != null; e = e.next) if (e.key==null) return true; } return false; } /** * Returns the value to which this LinkedHashMap maps the specified key. * Returns null if the LinkedHashMap contains no mapping for this key. * A return value of null does not <em>necessarily</em> indicate that the * LinkedHashMap contains no mapping for the key; it's also possible that * the LinkedHashMap 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. */ public Object get(Object key) { Entry e = getEntry(key); return e==null ? null : e.value; } /** * Returns the entry associated with the specified key in the LinkedHashMap. * Returns null if the LinkedHashMap contains no mapping for this key. */ private Entry getEntry(Object key) { Entry tab[] = table; if (key != null) { int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index]; e != null; e = e.next) if ((e.hash == hash) && e.key.equals(key)) return e; } else { for (Entry e = tab[0]; e != null; e = e.next) if (e.key==null) return e; } return null; } /** * Rehashes the contents of the LinkedHashMap into a LinkedHashMap with a * larger capacity. This method is called automatically when the * number of keys in the LinkedHashMap exceeds this LinkedHashMap's capacity * and load factor. */ private void rehash() { int oldCapacity = table.length; Entry oldMap[] = table; int newCapacity = oldCapacity * 2 + 1; Entry newMap[] = new Entry[newCapacity]; modCount++; threshold = (int)(newCapacity * loadFactor); table = newMap; for (Entry e = header.after; e != header; e = e.after) { int index = (e.hash & 0x7FFFFFFF) % newCapacity; e.next = newMap[index]; newMap[index] = e; } } /** * Remove an entry from the linked list. */ private void listRemove(Entry entry) { if (entry == null) { return; } entry.before.after = entry.after; entry.after.before = entry.before; } /** * Add the specified entry before the specified existing entry to * the linked list. */ private void listAddBefore(Entry entry, Entry existEntry) { entry.after = existEntry; entry.before = existEntry.before; entry.before.after = entry; entry.after.before = entry; } /** * Returns the position of the mapping for the specified key * in the ordered map. * * @param key the specified key. * @return index of the key mapping. */ public int indexOf(Object key) { int i = 0; if (key == null) { for (Entry e = header.after; e != header; e = e.after, i++) if (e.key == null) return i; } else { for (Entry e = header.after; e != header; e = e.after, i++) if(key.equals(e.key)) return i; } return -1; } /** * Associates the specified value with the specified key in this * LinkedHashMap. If the LinkedHashMap previously contained a mapping for * this key, the old value is replaced and the position of this mapping * entry in the double linked list remains the same. Otherwise, a new * mapping entry is created and inserted into the list before the specified * existing mapping entry. The method returns the previous value associated * with the specified key, or null if there was no mapping for key. A null * return can also indicate that the LinkedHashMap previously associated * null with the specified key. */ private Object putAhead(Object key, Object value, Entry existEntry) { // Makes sure the key is not already in the LinkedHashMap. Entry tab[] = table; int hash = 0; int index = 0; if (key != null) { hash = key.hashCode(); index = (hash & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { Object old = e.value; e.value = value; return old; } } } else { for (Entry e = tab[0] ; e != null ; e = e.next) { if (e.key == null) { 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 = (hash & 0x7FFFFFFF) % tab.length; } // Creates the new entry. Entry e = new Entry(hash, key, value, tab[index]); tab[index] = e; listAddBefore(e, existEntry); count++; return null; } /** * Associates the specified value with the specified key in this * LinkedHashMap and position the mapping at the specified index. * If the LinkedHashMap previously contained a mapping for this key, * the old value is replaced and the position of this mapping entry * in the double linked list remains the same. Otherwise, a new mapping * entry is created and inserted into the list at the specified * position. * * @param index the position to put the key-value mapping. * @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 null if there * was no mapping for key. A null return can also indicate that * the LinkedHashMap previously associated null with the specified * key. */ public Object put(int index, Object key, Object value) { if (index < 0 || index > count) throw new IndexOutOfBoundsException(); Entry e = header.after; if (index == count) return putAhead(key, value, header); //fast approach for append else { for (int i = 0; i < index; i++) e = e.after; return putAhead(key, value, e); } } /** * Associates the specified value with the specified key in this * LinkedHashMap. If the LinkedHashMap previously contained a mapping for * this key, the old value is replaced. The mapping entry is also appended * to the end of the ordered linked list. * * @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 null if there * was no mapping for key. A null return can also indicate that * the LinkedHashMap previously associated null with the specified * key. */ public Object put(Object key, Object value) { return putAhead(key, value, header); } /** * Removes the mapping for this key from this LinkedHashMap if present. * The mapping would also be removed from the double linked list. * * @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. A null return can also indicate that * the LinkedHashMap previously associated null with the specified * key. */ public Object remove(Object key) { Entry tab[] = table; if (key != null) { int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index], prev = null; e != null; prev = e, e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { modCount++; if (prev != null) prev.next = e.next; else tab[index] = e.next; count--; Object oldValue = e.value; e.value = null; listRemove(e); return oldValue; } } } else { for (Entry e = tab[0], prev = null; e != null; prev = e, e = e.next) { if (e.key == null) { modCount++; if (prev != null) prev.next = e.next; else tab[0] = e.next; count--; Object oldValue = e.value; e.value = null; listRemove(e); return oldValue; } } } return null; } /** * Copies all of the mappings from the specified Map to this LinkedHashMap * These mappings will replace any mappings that this LinkedHashMap had for * any of the keys currently in the specified Map. * * @param t Mappings to be stored in this Map. */ public void putAll(Map t) { Iterator i = t.entrySet().iterator(); while (i.hasNext()) { Map.Entry e = (Map.Entry) i.next(); put(e.getKey(), e.getValue()); } } /** * Removes all mappings from this LinkedHashMap. */ public void clear() { Entry tab[] = table; modCount++; for (int index = tab.length; --index >= 0; ) tab[index] = null; count = 0; header.before = header.after = header; } /** * Returns a shallow copy of this LinkedHashMap. The keys and values * themselves are not cloned. */ public Object clone() { return new LinkedHashMap(this); } // Views private transient Set keySet = null; private transient Set entries = null; private transient Collection values = null; /** * Returns a Set view of the keys contained in this LinkedHashMap. The Set * is backed by the LinkedHashMap, so changes to the LinkedHashMap are * reflected in the Set, and vice-versa. The Set supports element removal, * which removes the corresponding mapping from the LinkedHashMap, via the * Iterator.remove, Set.remove, removeAll retainAll, and clear operations. * It does not support the add or addAll operations. */ public Set keySet() { if (keySet == null) { keySet = new AbstractSet() { public Iterator iterator() { return new HashIterator(KEYS); } public int size() { return count; } public boolean contains(Object o) { return containsKey(o); } public boolean remove(Object o) { return LinkedHashMap.this.remove(o) != null; } public void clear() { LinkedHashMap.this.clear(); } }; } return keySet; } /** * Returns a Collection view of the values contained in this LinkedHashMap. * The Collection is backed by the LinkedHashMap, so changes to the * LinkedHashMap are reflected in the Collection, and vice-versa. The * Collection supports element removal, which removes the corresponding * mapping from the LinkedHashMap, via the Iterator.remove, * Collection.remove, removeAll, retainAll and clear operations. It does * not support the add or addAll operations. */ public Collection values() { if (values==null) { values = new AbstractCollection() { public Iterator iterator() { return new HashIterator(VALUES); } public int size() { return count; } public boolean contains(Object o) { return containsValue(o); } public void clear() { LinkedHashMap.this.clear(); } }; } return values; } /** * Returns a Collection view of the mappings contained in this * LinkedHashMap. Each element in the returned collection is a Map.Entry. * The Collection is backed by the LinkedHashMap, so changes to the * LinkedHashMap are reflected in the Collection, and vice-versa. The * Collection supports element removal, which removes the corresponding * mapping from the LinkedHashMap, via the Iterator.remove, * Collection.remove, removeAll, retainAll and clear operations. It does * not support the add or addAll operations. * * @see java.util.Map.Entry */ public Set entrySet() { if (entries==null) { entries = new AbstractSet() { public Iterator iterator() { return new HashIterator(ENTRIES); } 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.hash==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; 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.hash==hash && e.equals(entry)) { modCount++; if (prev != null) prev.next = e.next; else tab[index] = e.next; count--; e.value = null; listRemove(e); return true; } } return false; } public int size() { return count; } public void clear() { LinkedHashMap.this.clear(); } }; } return entries; } /** * Compares the specified Object with this Map for equality. * Returns true if the given object is also a LinkedHashMap and the two * Maps represent the same mappings in the same order. More formally, * two Maps <code>t1</code> and <code>t2</code> represent the same mappings * if <code>t1.keySet().equals(t2.keySet())</code> and for every * key <code>k</code> in <code>t1.keySet()</code>, <code> * (t1.get(k)==null ? t2.get(k)==null : t1.get(k).equals(t2.get(k))) * </code>. * <p> * This implementation first checks if the specified Object is this Map; * if so it returns true. Then, it checks if the specified Object is * a Map whose size is identical to the size of this Set; if not, it * it returns false. If so, it iterates over this Map and the specified * Map's entrySet() Collection, and checks that the specified Map contains * each mapping that this Map contains at the same position. If the * specified Map fails to contain such a mapping in the right order, false * is returned. If the iteration completes, true is returned. * * @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) { if (o == this) return true; if (!(o instanceof LinkedHashMap)) return false; LinkedHashMap t = (LinkedHashMap) o; if (t.size() != size()) return false; Iterator i1 = entrySet().iterator(); Iterator i2 = t.entrySet().iterator(); while (i1.hasNext()) { Entry e1 = (Entry) i1.next(); Entry e2 = (Entry) i2.next(); Object key1 = e1.getKey(); Object value1 = e1.getValue(); Object key2 = e2.getKey(); Object value2 = e2.getValue(); if ((key1 == null ? key2 == null : key1.equals(key2)) && (value1 == null ? value2 == null : value1.equals(value2))) { continue; } else { return false; } } return true; } /** * LinkedHashMap collision list entry. */ private static class Entry implements Map.Entry { int hash; Object key; Object value; Entry next; // These fields comprise the doubly linked list that is used for // iteration. Entry before, after; Entry(int hash, Object key, Object value, Entry next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } // Map.Entry Ops 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 (key==null ? e.getKey()==null : key.equals(e.getKey())) && (value==null ? e.getValue()==null : value.equals(e.getValue())); } public int hashCode() { return hash ^ (value==null ? 0 : value.hashCode()); } public String toString() { return 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 HashIterator implements Iterator { private Entry[] table = LinkedHashMap.this.table; private Entry entry = null; private Entry lastReturned = null; private 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; HashIterator(int type) { this.type = type; this.entry = LinkedHashMap.this.header.after; } public boolean hasNext() { return entry != header; } public Object next() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); if (entry == LinkedHashMap.this.header) throw new NoSuchElementException(); Entry e = lastReturned = entry; entry = e.after; return type == KEYS ? e.key : (type == VALUES ? e.value : e); } public void remove() { if (lastReturned == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); Entry[] tab = LinkedHashMap.this.table; int index = (lastReturned.hash & 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--; listRemove(e); lastReturned = null; return; } } throw new ConcurrentModificationException(); } } /** * Save the state of the LinkedHashMap to a stream (i.e., serialize it). * The objects will be written out in the order they are linked * in the list. */ 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 (Entry e = header.after; e != header; e = e.after) { s.writeObject(e.key); s.writeObject(e.value); } } /** * Reconstitute the LinkedHashMap 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]; header = new Entry(-1, null, null, null); header.before = header; header.after = header; // Read in size (number of Mappings) int size = s.readInt(); // Read the keys and values, and put the mappings in the LinkedHashMap for (int i=0; i<size; i++) { Object key = s.readObject(); Object value = s.readObject(); put(key, value); } } }