/*
 * Copyright 2008 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.
 */
package java.util;


/**
 * Hash table implementation of the Map interface with predictable iteration
 * order. <a
 * href="http://java.sun.com/j2se/1.5.0/docs/api/java/util/LinkedHashMap.html">[Sun
 * docs]</a>
 * 
 * @param <K> key type.
 * @param <V> value type.
 */
public class LinkedHashMap<K, V> extends HashMap<K, V> implements Map<K, V> {

  /**
   * The entry we use includes next/prev pointers for a doubly-linked circular
   * list with a head node. This reduces the special cases we have to deal with
   * in the list operations.
   * 
   * Note that we duplicate the key from the underlying hash map so we can find
   * the eldest entry. The alternative would have been to modify HashMap so more
   * of the code was directly usable here, but this would have added some
   * overhead to HashMap, or to reimplement most of the HashMap code here with
   * small modifications. Paying a small storage cost only if you use
   * LinkedHashMap and minimizing code size seemed like a better tradeoff
   */
  private class ChainEntry extends MapEntryImpl<K, V> {
    private transient ChainEntry next;
    private transient ChainEntry prev;

    public ChainEntry() {
      this(null, null);
    }

    public ChainEntry(K key, V value) {
      super(key, value);
      next = prev = null;
    }

    /**
     * Add this node to the end of the chain.
     */
    public void addToEnd() {
      ChainEntry tail = head.prev;

      // Chain is valid.
      assert (head != null && tail != null);

      // This entry is not in the list.
      assert (next == null) && (prev == null);

      // Update me.
      prev = tail;
      next = head;
      tail.next = head.prev = this;
    }

    /**
     * Remove this node from any list it may be a part of.
     */
    public void remove() {
      next.prev = prev;
      prev.next = next;
      next = prev = null;
    }
  }

  private final class EntrySet extends AbstractSet<Map.Entry<K, V>> {

    private final class EntryIterator implements Iterator<Entry<K, V>> {
      // The last entry that was returned from this iterator.
      private ChainEntry last;

      // The next entry to return from this iterator.
      private ChainEntry next;

      public EntryIterator() {
        next = head.next;
      }

      public boolean hasNext() {
        return next != head;
      }

      public Map.Entry<K, V> next() {
        if (next == head) {
          throw new NoSuchElementException();
        }
        last = next;
        next = next.next;
        return last;
      }

      public void remove() {
        if (last == null) {
          throw new IllegalStateException("No current entry");
        }
        last.remove();
        map.remove(last.getKey());
        last = null;
      }
    }

    @Override
    public void clear() {
      LinkedHashMap.this.clear();
    }

    @Override
    public boolean contains(Object o) {
      if (!(o instanceof Map.Entry)) {
        return false;
      }
      Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o;
      Object key = entry.getKey();
      if (LinkedHashMap.this.containsKey(key)) {
        Object value = LinkedHashMap.this.get(key);
        return Utility.equalsWithNullCheck(entry.getValue(), value);
      }
      return false;
    }

    @Override
    public Iterator<Map.Entry<K, V>> iterator() {
      return new EntryIterator();
    }

    @Override
    public int size() {
      return map.size();
    }
  }

  // True if we should use the access order (ie, for LRU caches) instead of
  // insertion order.
  private transient boolean accessOrder;

  /*
   * The head of the LRU/insert order chain, which is a doubly-linked circular
   * list. The key and value of head should never be read.
   * 
   * The most recently inserted/accessed node is at the end of the chain, ie.
   * chain.prev.
   */
  private final transient ChainEntry head = new ChainEntry();

  /*
   * The hashmap that keeps track of our entries and the chain. Note that we
   * duplicate the key here to eliminate changes to HashMap and minimize the
   * code here, at the expense of additional space.
   */
  private final transient HashMap<K, ChainEntry> map = new HashMap<K, ChainEntry>();

  {
    // Initialize the empty linked list.
    head.prev = head;
    head.next = head;
  }

  public LinkedHashMap() {
  }

  public LinkedHashMap(int ignored) {
    super(ignored);
  }

  public LinkedHashMap(int ignored, float alsoIgnored) {
    super(ignored, alsoIgnored);
  }

  public LinkedHashMap(int ignored, float alsoIgnored, boolean accessOrder) {
    super(ignored, alsoIgnored);
    this.accessOrder = accessOrder;
  }

  public LinkedHashMap(Map<? extends K, ? extends V> toBeCopied) {
    this.putAll(toBeCopied);
  }

  @Override
  public void clear() {
    map.clear();
    head.prev = head;
    head.next = head;
  }

  @Override
  public boolean containsKey(Object key) {
    return map.containsKey(key);
  }

  @Override
  public boolean containsValue(Object value) {
    ChainEntry node = head.next;
    while (node != head) {
      if (Utility.equalsWithNullCheck(node.getValue(), value)) {
        return true;
      }
      node = node.next;
    }
    return false;
  }

  @Override
  public Set<Map.Entry<K, V>> entrySet() {
    return new EntrySet();
  }

  @Override
  public V get(Object key) {
    ChainEntry entry = map.get(key);
    if (entry != null) {
      recordAccess(entry);
      return entry.getValue();
    }
    return null;
  }

  @Override
  public V put(K key, V value) {
    ChainEntry old = map.get(key);
    if (old == null) {
      ChainEntry newEntry = new ChainEntry(key, value);
      map.put(key, newEntry);
      newEntry.addToEnd();
      ChainEntry eldest = head.next;
      if (removeEldestEntry(eldest)) {
        eldest.remove();
        map.remove(eldest.getKey());
      }
      return null;
    } else {
      V oldValue = old.getValue();
      old.setValue(value);
      recordAccess(old);
      return oldValue;
    }
  }

  @Override
  public V remove(Object key) {
    ChainEntry entry = map.remove(key);
    if (entry != null) {
      entry.remove();
      return entry.getValue();
    }
    return null;
  }

  @Override
  public int size() {
    return map.size();
  }

  @SuppressWarnings("unused")
  protected boolean removeEldestEntry(Entry<K, V> eldest) {
    return false;
  }

  private void recordAccess(ChainEntry entry) {
    if (accessOrder) {
      // Move to the tail of the chain on access.
      entry.remove();
      entry.addToEnd();
    }
  }
}
