java.util.AbstractHashMap.java Source code

Java tutorial

Introduction

Here is the source code for java.util.AbstractHashMap.java

Source

/*
 * 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;

import com.google.gwt.core.client.JavaScriptObject;

/**
 * Implementation of Map interface based on a hash table. <a
 * href="http://java.sun.com/j2se/1.5.0/docs/api/java/util/HashMap.html">[Sun
 * docs]</a>
 * 
 * @param <K> key type
 * @param <V> value type
 */
abstract class AbstractHashMap<K, V> extends AbstractMap<K, V> {
    /*
     * Implementation notes:
     * 
     * String keys are stored in a separate map from non-String keys. String keys
     * are mapped to their values via a JS associative map, stringMap. String keys
     * could collide with intrinsic properties (like watch, constructor) so we
     * prepend each key with a ':' inside of stringMap.
     * 
     * Integer keys are used to index all non-string keys. A key's hashCode is the
     * index in hashCodeMap which should contain that key. Since several keys may
     * have the same hash, each value in hashCodeMap is actually an array
     * containing all entries whose keys share the same hash.
     */
    private final class EntrySet extends AbstractSet<Entry<K, V>> {

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

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

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

        @Override
        public boolean remove(Object entry) {
            if (contains(entry)) {
                Object key = ((Map.Entry<?, ?>) entry).getKey();
                AbstractHashMap.this.remove(key);
                return true;
            }
            return false;
        }

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

    /**
     * Iterator for <code>EntrySetImpl</code>.
     */
    private final class EntrySetIterator implements Iterator<Entry<K, V>> {
        private final Iterator<Map.Entry<K, V>> iter;
        private Map.Entry<K, V> last = null;

        /**
         * Constructor for <code>EntrySetIterator</code>.
         */
        public EntrySetIterator() {
            List<Map.Entry<K, V>> list = new ArrayList<Map.Entry<K, V>>();
            if (nullSlotLive) {
                list.add(new MapEntryNull());
            }
            addAllStringEntries(list);
            addAllHashEntries(list);
            this.iter = list.iterator();
        }

        public boolean hasNext() {
            return iter.hasNext();
        }

        public Map.Entry<K, V> next() {
            return last = iter.next();
        }

        public void remove() {
            if (last == null) {
                throw new IllegalStateException("Must call next() before remove().");
            } else {
                iter.remove();
                AbstractHashMap.this.remove(last.getKey());
                last = null;
            }
        }
    }

    private final class MapEntryNull extends AbstractMapEntry<K, V> {

        public K getKey() {
            return null;
        }

        public V getValue() {
            return nullSlot;
        }

        public V setValue(V object) {
            return putNullSlot(object);
        }
    }

    // Instantiated from JSNI
    @SuppressWarnings("unused")
    private final class MapEntryString extends AbstractMapEntry<K, V> {

        private final String key;

        public MapEntryString(String key) {
            this.key = key;
        }

        @SuppressWarnings("unchecked")
        public K getKey() {
            return (K) key;
        }

        public V getValue() {
            return getStringValue(key);
        }

        public V setValue(V object) {
            return putStringValue(key, object);
        }
    }

    /**
     * A map of integral hashCodes onto entries.
     */
    // Used from JSNI.
    @SuppressWarnings("unused")
    private transient JavaScriptObject hashCodeMap;

    /**
     * This is the slot that holds the value associated with the "null" key.
     */
    private transient V nullSlot;

    private transient boolean nullSlotLive;

    private int size;

    /**
     * A map of Strings onto values.
     */
    // Used from JSNI.
    @SuppressWarnings("unused")
    private transient JavaScriptObject stringMap;

    {
        clearImpl();
    }

    public AbstractHashMap() {
    }

    public AbstractHashMap(int ignored) {
        // This implementation of HashMap has no need of initial capacities.
        this(ignored, 0);
    }

    public AbstractHashMap(int ignored, float alsoIgnored) {
        // This implementation of HashMap has no need of load factors or capacities.
        if (ignored < 0 || alsoIgnored < 0) {
            throw new IllegalArgumentException("initial capacity was negative or load factor was non-positive");
        }
    }

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

    @Override
    public void clear() {
        clearImpl();
    }

    public abstract Object clone();

    @Override
    public boolean containsKey(Object key) {
        return (key == null) ? nullSlotLive
                : (!(key instanceof String) ? hasHashValue(key, getHashCode(key)) : hasStringValue((String) key));
    }

    @Override
    public boolean containsValue(Object value) {
        if (nullSlotLive && equals(nullSlot, value)) {
            return true;
        } else if (containsStringValue(value)) {
            return true;
        } else if (containsHashValue(value)) {
            return true;
        }
        return false;
    }

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

    @Override
    public V get(Object key) {
        return (key == null) ? nullSlot
                : (!(key instanceof String) ? getHashValue(key, getHashCode(key)) : getStringValue((String) key));
    }

    @Override
    public V put(K key, V value) {
        return (key == null) ? putNullSlot(value)
                : (!(key instanceof String) ? putHashValue(key, value, getHashCode(key))
                        : putStringValue((String) key, value));
    }

    @Override
    public V remove(Object key) {
        return (key == null) ? removeNullSlot()
                : (!(key instanceof String) ? removeHashValue(key, getHashCode(key))
                        : removeStringValue((String) key));
    }

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

    /**
     * Subclasses must override to return a whether or not two keys or values are
     * equal.
     */
    protected abstract boolean equals(Object value1, Object value2);

    /**
     * Subclasses must override to return a hash code for a given key. The key is
     * guaranteed to be non-null and not a String.
     */
    protected abstract int getHashCode(Object key);

    private native void addAllHashEntries(Collection<?> dest) /*-{
                                                              var hashCodeMap = this.@java.util.AbstractHashMap::hashCodeMap;
                                                              for ( var hashCode in hashCodeMap) {
                                                              // sanity check that it's really an integer
                                                              var hashCodeInt = parseInt(hashCode, 10);
                                                              if (hashCode == hashCodeInt) {
                                                              var array = hashCodeMap[hashCodeInt];
                                                              for ( var i = 0, c = array.length; i < c; ++i) {
                                                              dest.@java.util.Collection::add(Ljava/lang/Object;)(array[i]);
                                                              }
                                                              }
                                                              }
                                                              }-*/;

    private native void addAllStringEntries(Collection<?> dest) /*-{
                                                                var stringMap = this.@java.util.AbstractHashMap::stringMap;
                                                                for (var key in stringMap) {
                                                                // only keys that start with a colon ':' count
                                                                if (key.charCodeAt(0) == 58) {
                                                                var entry = @java.util.AbstractHashMap$MapEntryString::new(Ljava/util/AbstractHashMap;Ljava/lang/String;)(this, key.substring(1));
                                                                dest.@java.util.Collection::add(Ljava/lang/Object;)(entry);
                                                                }
                                                                }
                                                                }-*/;

    private void clearImpl() {
        hashCodeMap = JavaScriptObject.createArray();
        stringMap = JavaScriptObject.createObject();
        nullSlotLive = false;
        nullSlot = null;
        size = 0;
    }

    /**
     * Returns true if hashCodeMap contains any Map.Entry whose value is Object
     * equal to <code>value</code>.
     */
    private native boolean containsHashValue(Object value) /*-{
                                                           var hashCodeMap = this.@java.util.AbstractHashMap::hashCodeMap;
                                                           for ( var hashCode in hashCodeMap) {
                                                           // sanity check that it's really one of ours
                                                           var hashCodeInt = parseInt(hashCode, 10);
                                                           if (hashCode == hashCodeInt) {
                                                           var array = hashCodeMap[hashCodeInt];
                                                           for ( var i = 0, c = array.length; i < c; ++i) {
                                                           var entry = array[i];
                                                           var entryValue = entry.@java.util.Map$Entry::getValue()();
                                                           if (this.@java.util.AbstractHashMap::equalsBridge(Ljava/lang/Object;Ljava/lang/Object;)(value, entryValue)) {
                                                           return true;
                                                           }
                                                           }
                                                           }
                                                           }
                                                           return false;
                                                           }-*/;

    /**
     * Returns true if stringMap contains any key whose value is Object equal to
     * <code>value</code>.
     */
    private native boolean containsStringValue(Object value) /*-{
                                                             var stringMap = this.@java.util.AbstractHashMap::stringMap;
                                                             for ( var key in stringMap) {
                                                             // only keys that start with a colon ':' count
                                                             if (key.charCodeAt(0) == 58) {
                                                             var entryValue = stringMap[key];
                                                             if (this.@java.util.AbstractHashMap::equalsBridge(Ljava/lang/Object;Ljava/lang/Object;)(value, entryValue)) {
                                                             return true;
                                                             }
                                                             }
                                                             }
                                                             return false;
                                                             }-*/;

    /**
     * Bridge method from JSNI that keeps us from having to make polymorphic calls
     * in JSNI. By putting the polymorphism in Java code, the compiler can do a
     * better job of optimizing in most cases.
     */
    @SuppressWarnings("unused")
    private boolean equalsBridge(Object value1, Object value2) {
        return equals(value1, value2);
    }

    /**
     * Returns the Map.Entry whose key is Object equal to <code>key</code>,
     * provided that <code>key</code>'s hash code is <code>hashCode</code>;
     * or <code>null</code> if no such Map.Entry exists at the specified
     * hashCode.
     */
    private native V getHashValue(Object key, int hashCode) /*-{
                                                            var array = this.@java.util.AbstractHashMap::hashCodeMap[hashCode];
                                                            if (array) {
                                                            for ( var i = 0, c = array.length; i < c; ++i) {
                                                            var entry = array[i];
                                                            var entryKey = entry.@java.util.Map$Entry::getKey()();
                                                            if (this.@java.util.AbstractHashMap::equalsBridge(Ljava/lang/Object;Ljava/lang/Object;)(key, entryKey)) {
                                                            return entry.@java.util.Map$Entry::getValue()();
                                                            }
                                                            }
                                                            }
                                                            return null;
                                                            }-*/;

    /**
     * Returns the value for the given key in the stringMap. Returns
     * <code>null</code> if the specified key does not exist.
     */
    private native V getStringValue(String key) /*-{
                                                return this.@java.util.AbstractHashMap::stringMap[':' + key];
                                                }-*/;

    /**
     * Returns true if the a key exists in the hashCodeMap that is Object equal to
     * <code>key</code>, provided that <code>key</code>'s hash code is
     * <code>hashCode</code>.
     */
    private native boolean hasHashValue(Object key, int hashCode) /*-{
                                                                  var array = this.@java.util.AbstractHashMap::hashCodeMap[hashCode];
                                                                  if (array) {
                                                                  for ( var i = 0, c = array.length; i < c; ++i) {
                                                                  var entry = array[i];
                                                                  var entryKey = entry.@java.util.Map$Entry::getKey()();
                                                                  if (this.@java.util.AbstractHashMap::equalsBridge(Ljava/lang/Object;Ljava/lang/Object;)(key, entryKey)) {
                                                                  return true;
                                                                  }
                                                                  }
                                                                  }
                                                                  return false;
                                                                  }-*/;

    /**
     * Returns true if the given key exists in the stringMap.
     */
    private native boolean hasStringValue(String key) /*-{
                                                      return (':' + key) in this.@java.util.AbstractHashMap::stringMap;
                                                      }-*/;

    /**
     * Sets the specified key to the specified value in the hashCodeMap. Returns
     * the value previously at that key. Returns <code>null</code> if the
     * specified key did not exist.
     */
    private native V putHashValue(K key, V value, int hashCode) /*-{
                                                                var array = this.@java.util.AbstractHashMap::hashCodeMap[hashCode];
                                                                if (array) {
                                                                for ( var i = 0, c = array.length; i < c; ++i) {
                                                                var entry = array[i];
                                                                var entryKey = entry.@java.util.Map$Entry::getKey()();
                                                                if (this.@java.util.AbstractHashMap::equalsBridge(Ljava/lang/Object;Ljava/lang/Object;)(key, entryKey)) {
                                                                // Found an exact match, just update the existing entry
                                                                var previous = entry.@java.util.Map$Entry::getValue()();
                                                                entry.@java.util.Map$Entry::setValue(Ljava/lang/Object;)(value);
                                                                return previous;
                                                                }
                                                                }
                                                                } else {
                                                                array = this.@java.util.AbstractHashMap::hashCodeMap[hashCode] = [];
                                                                }
                                                                var entry = @java.util.MapEntryImpl::new(Ljava/lang/Object;Ljava/lang/Object;)(key, value);
                                                                array.push(entry);
                                                                ++this.@java.util.AbstractHashMap::size;
                                                                return null;
                                                                }-*/;

    private V putNullSlot(V value) {
        V result = nullSlot;
        nullSlot = value;
        if (!nullSlotLive) {
            nullSlotLive = true;
            ++size;
        }
        return result;
    }

    /**
     * Sets the specified key to the specified value in the stringMap. Returns the
     * value previously at that key. Returns <code>null</code> if the specified
     * key did not exist.
     */
    private native V putStringValue(String key, V value) /*-{
                                                         var result, stringMap = this.@java.util.AbstractHashMap::stringMap;
                                                         key = ':' + key;
                                                         if (key in stringMap) {
                                                         result = stringMap[key];
                                                         } else {
                                                         ++this.@java.util.AbstractHashMap::size;
                                                         }
                                                         stringMap[key] = value;
                                                         return result;
                                                         }-*/;

    /**
     * Removes the pair whose key is Object equal to <code>key</code> from
     * <code>hashCodeMap</code>, provided that <code>key</code>'s hash code
     * is <code>hashCode</code>. Returns the value that was associated with the
     * removed key, or null if no such key existed.
     */
    private native V removeHashValue(Object key, int hashCode) /*-{
                                                               var array = this.@java.util.AbstractHashMap::hashCodeMap[hashCode];
                                                               if (array) {
                                                               for ( var i = 0, c = array.length; i < c; ++i) {
                                                               var entry = array[i];
                                                               var entryKey = entry.@java.util.Map$Entry::getKey()();
                                                               if (this.@java.util.AbstractHashMap::equalsBridge(Ljava/lang/Object;Ljava/lang/Object;)(key, entryKey)) {
                                                               if (array.length == 1) {
                                                               // remove the whole array
                                                               delete this.@java.util.AbstractHashMap::hashCodeMap[hashCode];
                                                               } else {
                                                               // splice out the entry we're removing
                                                               array.splice(i, 1);
                                                               }
                                                               --this.@java.util.AbstractHashMap::size;
                                                               return entry.@java.util.Map$Entry::getValue()();
                                                               }
                                                               }
                                                               }
                                                               return null;
                                                               }-*/;

    private V removeNullSlot() {
        V result = nullSlot;
        nullSlot = null;
        if (nullSlotLive) {
            nullSlotLive = false;
            --size;
        }
        return result;
    }

    /**
     * Removes the specified key from the stringMap and returns the value that was
     * previously there. Returns <code>null</code> if the specified key does not
     * exist.
     */
    private native V removeStringValue(String key) /*-{
                                                   var result, stringMap = this.@java.util.AbstractHashMap::stringMap;
                                                   key = ':' + key;
                                                   if (key in stringMap) {
                                                   result = stringMap[key];
                                                   --this.@java.util.AbstractHashMap::size;
                                                   delete stringMap[key];
                                                   }
                                                   return result;
                                                   }-*/;
}