All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.emul.java.util.AbstractHashMap Maven / Gradle / Ivy

There is a newer version: 2.10.0
Show newest version
/*
 * 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. [Sun
 * docs]
 * 
 * @param  key type
 * @param  value type
 */
abstract class AbstractHashMap extends AbstractMap {
  /*
   * 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> {

    @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> 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 EntrySetImpl.
   */
  private final class EntrySetIterator implements Iterator> {
    private final Iterator> iter;
    private Map.Entry last = null;

    /**
     * Constructor for EntrySetIterator.
     */
    public EntrySetIterator() {
      List> list = new ArrayList>();
      if (nullSlotLive) {
        list.add(new MapEntryNull());
      }
      addAllStringEntries(list);
      addAllHashEntries(list);
      this.iter = list.iterator();
    }

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

    public Map.Entry 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 {

    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 {

    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 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> 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 = [email protected]::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) {
          [email protected]::add(Ljava/lang/Object;)(array[i]);
        }
      }
    }
  }-*/;

  private native void addAllStringEntries(Collection dest) /*-{
    var stringMap = [email protected]::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));
        [email protected]::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 value.
   */
  private native boolean containsHashValue(Object value) /*-{
    var hashCodeMap = [email protected]::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 = [email protected]$Entry::getValue()();
          if ([email protected]::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
   * value.
   */
  private native boolean containsStringValue(Object value) /*-{
    var stringMap = [email protected]::stringMap;
    for (var key in stringMap) {
      // only keys that start with a colon ':' count
      if (key.charCodeAt(0) == 58) {
        var entryValue = stringMap[key];
        if ([email protected]::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 key,
   * provided that key's hash code is hashCode;
   * or null if no such Map.Entry exists at the specified
   * hashCode.
   */
  private native V getHashValue(Object key, int hashCode) /*-{
    var array = [email protected]::hashCodeMap[hashCode];
    if (array) {
      for (var i = 0, c = array.length; i < c; ++i) {
        var entry = array[i];
        var entryKey = [email protected]$Entry::getKey()();
        if ([email protected]::equalsBridge(Ljava/lang/Object;Ljava/lang/Object;)(key, entryKey)) {
          return [email protected]$Entry::getValue()();
        }
      }
    }
    return null;
  }-*/;

  /**
   * Returns the value for the given key in the stringMap. Returns
   * null if the specified key does not exist.
   */
  private native V getStringValue(String key) /*-{
    return [email protected]::stringMap[':' + key];
  }-*/;

  /**
   * Returns true if the a key exists in the hashCodeMap that is Object equal to
   * key, provided that key's hash code is
   * hashCode.
   */
  private native boolean hasHashValue(Object key, int hashCode) /*-{
    var array = [email protected]::hashCodeMap[hashCode];
    if (array) {
      for (var i = 0, c = array.length; i < c; ++i) {
        var entry = array[i];
        var entryKey = [email protected]$Entry::getKey()();
        if ([email protected]::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 [email protected]::stringMap;
  }-*/;

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

  /**
   * Removes the pair whose key is Object equal to key from
   * hashCodeMap, provided that key's hash code
   * is hashCode. 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 = [email protected]::hashCodeMap[hashCode];
    if (array) {
      for (var i = 0, c = array.length; i < c; ++i) {
        var entry = array[i];
        var entryKey = [email protected]$Entry::getKey()();
        if ([email protected]::equalsBridge(Ljava/lang/Object;Ljava/lang/Object;)(key, entryKey)) {
          if (array.length == 1) {
            // remove the whole array
            delete [email protected]::hashCodeMap[hashCode];
          } else {
            // splice out the entry we're removing
            array.splice(i, 1);
          }
          [email protected]::size;
          return [email protected]$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 null if the specified key does not
   * exist.
   */
  private native V removeStringValue(String key) /*-{
    var result, stringMap = [email protected]::stringMap;
    key = ':' + key;
    if (key in stringMap) {
      result = stringMap[key];
      [email protected]::size;
      delete stringMap[key];
    }
    return result;
  }-*/;
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy