/* * 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. */ package java.util; /** * LinkedHashMap is a variant of HashMap. Its entries are kept in a * doubly-linked list. The iteration order is, by default, the order in which * keys were inserted. Reinserting an already existing key doesn't change the * order. A key is existing if a call to {@code containsKey} would return true. *

* If the three argument constructor is used, and {@code order} is specified as * {@code true}, the iteration will be in the order that entries were accessed. * The access order gets affected by put(), get(), putAll() operations, but not * by operations on the collection views. *

* Null elements are allowed, and all the optional map operations are supported. *

* Note: The implementation of {@code LinkedHashMap} is not synchronized. * If one thread of several threads accessing an instance modifies the map * structurally, access to the map needs to be synchronized. For * insertion-ordered instances a structural modification is an operation that * removes or adds an entry. Access-ordered instances also are structurally * modified by put(), get() and putAll() since these methods change the order of * the entries. Changes in the value of an entry are not structural changes. *

* The Iterator that can be created by calling the {@code iterator} method * throws a {@code ConcurrentModificationException} if the map is structurally * changed while an iterator is used to iterate over the elements. Only the * {@code remove} method that is provided by the iterator allows for removal of * elements during iteration. It is not possible to guarantee that this * mechanism works in all cases of unsynchronized concurrent modification. It * should only be used for debugging purposes. * * @since Android 1.0 */ public class LinkedHashMap extends HashMap { private static final long serialVersionUID = 3801124242820219131L; private final boolean accessOrder; transient private LinkedHashMapEntry head, tail; /** * Constructs a new empty {@code LinkedHashMap} instance. * * @since Android 1.0 */ public LinkedHashMap() { super(); accessOrder = false; head = null; } /** * Constructs a new {@code LinkedHashMap} instance with the specified * capacity. * * @param s * the initial capacity of this map. * @exception IllegalArgumentException * when the capacity is less than zero. * @since Android 1.0 */ public LinkedHashMap(int s) { super(s); accessOrder = false; head = null; } /** * Constructs a new {@code LinkedHashMap} instance with the specified * capacity and load factor. * * @param s * the initial capacity of this map. * @param lf * the initial load factor. * @throws IllegalArgumentException * when the capacity is less than zero or the load factor is * less or equal to zero. * @since Android 1.0 */ public LinkedHashMap(int s, float lf) { super(s, lf); accessOrder = false; head = null; tail = null; } /** * Constructs a new {@code LinkedHashMap} instance with the specified * capacity, load factor and a flag specifying the ordering behavior. * * @param s * the initial capacity of this hash map. * @param lf * the initial load factor. * @param order * {@code true} if the ordering should be done based on the last * access (from least-recently accessed to most-recently * accessed), and {@code false} if the ordering should be the * order in which the entries were inserted. * @throws IllegalArgumentException * when the capacity is less than zero or the load factor is * less or equal to zero. * @since Android 1.0 */ public LinkedHashMap(int s, float lf, boolean order) { super(s, lf); accessOrder = order; head = null; tail = null; } /** * Constructs a new {@code LinkedHashMap} instance containing the mappings * from the specified map. The order of the elements is preserved. * * @param m * the mappings to add. * @since Android 1.0 */ public LinkedHashMap(Map m) { accessOrder = false; head = null; tail = null; putAll(m); } static final class LinkedHashIterator extends HashMapIterator { LinkedHashIterator(MapEntry.Type value, LinkedHashMap hm) { super(value, hm); entry = hm.head; } @Override public boolean hasNext() { return (entry != null); } @Override public E next() { checkConcurrentMod(); if (!hasNext()) { throw new NoSuchElementException(); } E result = type.get(entry); lastEntry = entry; entry = ((LinkedHashMapEntry)entry).chainForward; canRemove = true; return result; } @Override public void remove() { checkConcurrentMod(); if (!canRemove) { throw new IllegalStateException(); } canRemove = false; associatedMap.modCount++; int index = (lastEntry.key == null)? 0 : (lastEntry.key.hashCode() & 0x7FFFFFFF) % associatedMap.elementData.length; LinkedHashMapEntry m = (LinkedHashMapEntry) associatedMap.elementData[index]; if (m == lastEntry) { associatedMap.elementData[index] = lastEntry.next; } else { while (m.next != null) { if (m.next == lastEntry) { break; } m = (LinkedHashMapEntry) m.next; } // assert m.next == entry m.next = lastEntry.next; } LinkedHashMapEntry lhme = (LinkedHashMapEntry) lastEntry; LinkedHashMapEntry p = lhme.chainBackward; LinkedHashMapEntry n = lhme.chainForward; LinkedHashMap lhm = (LinkedHashMap) associatedMap; if (p != null) { p.chainForward = n; if (n != null) { n.chainBackward = p; } else { lhm.tail = p; } } else { lhm.head = n; if (n != null) { n.chainBackward = null; } else { lhm.tail = null; } } associatedMap.elementCount--; expectedModCount++; } } static final class LinkedHashMapEntrySet extends HashMapEntrySet { public LinkedHashMapEntrySet(LinkedHashMap lhm) { super(lhm); } @Override public Iterator> iterator() { return new LinkedHashIterator,KT,VT>(new MapEntry.Type, KT, VT>() { public Map.Entry get(MapEntry entry) { return entry; } }, (LinkedHashMap) hashMap()); } } static final class LinkedHashMapEntry extends Entry { LinkedHashMapEntry chainForward, chainBackward; LinkedHashMapEntry(K theKey, V theValue) { super(theKey, theValue); chainForward = null; chainBackward = null; } LinkedHashMapEntry(K theKey, int hash) { super(theKey, hash); chainForward = null; chainBackward = null; } @Override @SuppressWarnings("unchecked") public Object clone() { LinkedHashMapEntry entry = (LinkedHashMapEntry) super.clone(); entry.chainBackward = chainBackward; entry.chainForward = chainForward; LinkedHashMapEntry lnext = (LinkedHashMapEntry) entry.next; if (lnext != null) { entry.next = (LinkedHashMapEntry) lnext.clone(); } return entry; } } @Override @SuppressWarnings("unchecked") Entry[] newElementArray(int s) { return new LinkedHashMapEntry[s]; } /** * Returns the value of the mapping with the specified key. * * @param key * the key. * @return the value of the mapping with the specified key, or {@code null} * if no mapping for the specified key is found. * @since Android 1.0 */ @Override public V get(Object key) { LinkedHashMapEntry m; if (key == null) { m = (LinkedHashMapEntry)findNullKeyEntry(); } else { int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % elementData.length; m = (LinkedHashMapEntry)findNonNullKeyEntry(key, index, hash); } if (m == null) { return null; } if (accessOrder && tail != m) { // BEGIN android-added modCount++; // END android-added LinkedHashMapEntry p = m.chainBackward; LinkedHashMapEntry n = m.chainForward; n.chainBackward = p; if (p != null) { p.chainForward = n; } else { head = n; } m.chainForward = null; m.chainBackward = tail; tail.chainForward = m; tail = m; } return m.value; } /* * @param key @param index @return Entry */ @Override Entry createEntry(K key, int index, V value) { LinkedHashMapEntry m = new LinkedHashMapEntry(key, value); m.next = elementData[index]; elementData[index] = m; linkEntry(m); return m; } Entry createHashedEntry(K key, int index, int hash) { LinkedHashMapEntry m = new LinkedHashMapEntry(key, hash); m.next = elementData[index]; elementData[index] = m; linkEntry(m); return m; } // BEGIN android-changed // copied from newer version of harmony /** * Maps the specified key to the specified value. * * @param key * the key. * @param value * the value. * @return the value of any previous mapping with the specified key or * {@code null} if there was no such mapping. * @since Android 1.0 */ @Override public V put(K key, V value) { V result = putImpl(key,value); if (removeEldestEntry(head)) { remove(head.key); } return result; } V putImpl(K key, V value){ LinkedHashMapEntry m; if (elementCount == 0){ head = tail = null; } if (key == null) { m = (LinkedHashMapEntry)findNullKeyEntry(); if (m == null) { modCount++; // Check if we need to remove the oldest entry // The check includes accessOrder since an accessOrder LinkedHashMap // does not record // the oldest member in 'head'. if (++elementCount > threshold) { rehash(); } m = (LinkedHashMapEntry) createHashedEntry(null, 0, 0); } else { linkEntry(m); } } else { int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % elementData.length; m = (LinkedHashMapEntry)findNonNullKeyEntry(key, index, hash); if (m == null) { modCount++; if (++elementCount > threshold) { rehash(); index = (hash & 0x7FFFFFFF) % elementData.length; } m = (LinkedHashMapEntry) createHashedEntry(key, index, hash); } else { linkEntry(m); } } V result = m.value; m.value = value; return result; } // END android-changed /* * @param m */ void linkEntry(LinkedHashMapEntry m) { if (tail == m) { return; } if (head == null) { // Check if the map is empty head = tail = m; return; } // we need to link the new entry into either the head or tail // of the chain depending on if the LinkedHashMap is accessOrder or not LinkedHashMapEntry p = m.chainBackward; LinkedHashMapEntry n = m.chainForward; if (p == null) { if (n != null) { // The entry must be the head but not the tail if (accessOrder) { head = n; n.chainBackward = null; m.chainBackward = tail; m.chainForward = null; tail.chainForward = m; tail = m; } } else { // This is a new entry m.chainBackward = tail; m.chainForward = null; tail.chainForward = m; tail = m; } return; } if (n == null) { // The entry must be the tail so we can't get here return; } // The entry is neither the head nor tail if (accessOrder) { p.chainForward = n; n.chainBackward = p; m.chainForward = null; m.chainBackward = tail; tail.chainForward = m; tail = m; } } /** * Returns a set containing all of the mappings in this map. Each mapping is * an instance of {@link Map.Entry}. As the set is backed by this map, * changes in one will be reflected in the other. * * @return a set of the mappings. * @since Android 1.0 */ @Override public Set> entrySet() { return new LinkedHashMapEntrySet(this); } /** * Returns a set of the keys contained in this map. The set is backed by * this map so changes to one are reflected by the other. The set does not * support adding. * * @return a set of the keys. * @since Android 1.0 */ @Override public Set keySet() { if (keySet == null) { keySet = new AbstractSet() { @Override public boolean contains(Object object) { return containsKey(object); } @Override public int size() { return LinkedHashMap.this.size(); } @Override public void clear() { LinkedHashMap.this.clear(); } @Override public boolean remove(Object key) { if (containsKey(key)) { LinkedHashMap.this.remove(key); return true; } return false; } @Override public Iterator iterator() { return new LinkedHashIterator(new MapEntry.Type() { public K get(MapEntry entry) { return entry.key; } }, LinkedHashMap.this); } }; } return keySet; } /** * Returns a collection of the values contained in this map. The collection * is backed by this map so changes to one are reflected by the other. The * collection supports remove, removeAll, retainAll and clear operations, * and it does not support add or addAll operations. *

* This method returns a collection which is the subclass of * AbstractCollection. The iterator method of this subclass returns a * "wrapper object" over the iterator of map's entrySet(). The size method * wraps the map's size method and the contains method wraps the map's * containsValue method. *

*

* The collection is created when this method is called for the first time * and returned in response to all subsequent calls. This method may return * different collections when multiple concurrent calls occur, since no * synchronization is performed. *

* * @return a collection of the values contained in this map. * @since Android 1.0 */ @Override public Collection values() { if (valuesCollection == null) { valuesCollection = new AbstractCollection() { @Override public boolean contains(Object object) { return containsValue(object); } @Override public int size() { return LinkedHashMap.this.size(); } @Override public void clear() { LinkedHashMap.this.clear(); } @Override public Iterator iterator() { return new LinkedHashIterator(new MapEntry.Type() { public V get(MapEntry entry) { return entry.value; } }, LinkedHashMap.this); } }; } return valuesCollection; } /** * Removes the mapping with the specified key from this map. * * @param key * the key of the mapping to remove. * @return the value of the removed mapping or {@code null} if no mapping * for the specified key was found. * @since Android 1.0 */ @Override public V remove(Object key) { LinkedHashMapEntry m = (LinkedHashMapEntry) removeEntry(key); if (m == null) { return null; } LinkedHashMapEntry p = m.chainBackward; LinkedHashMapEntry n = m.chainForward; if (p != null) { p.chainForward = n; } else { head = n; } if (n != null) { n.chainBackward = p; } else { tail = p; } return m.value; } /** * This method is queried from the put and putAll methods to check if the * eldest member of the map should be deleted before adding the new member. * If this map was created with accessOrder = true, then the result of * removeEldestEntry is assumed to be false. * * @param eldest * the entry to check if it should be removed. * @return {@code true} if the eldest member should be removed. * @since Android 1.0 */ protected boolean removeEldestEntry(Map.Entry eldest) { return false; } // BEGIN android-changed /** * Removes all elements from this map, leaving it empty. * * @see #isEmpty() * @see #size() * @since Android 1.0 */ @Override public void clear() { internalClear(); } @Override void internalClear() { super.internalClear(); head = tail = null; } // END android-changed // BEGIN android-removed // copied from newer version of harmony // /** // * Answers a new HashMap with the same mappings and size as this HashMap. // * // * @return a shallow copy of this HashMap // * // * @see java.lang.Cloneable // */ // @Override // @SuppressWarnings("unchecked") // public Object clone() { // LinkedHashMap map = (LinkedHashMap) super.clone(); // map.clear(); // for (Map.Entry entry : entrySet()) { // map.put(entry.getKey(), entry.getValue()); // } // return map; // } // END android-removed }