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

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

The newest version!
/*
 * Copyright 2014 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 static com.google.gwt.core.shared.impl.InternalPreconditions.checkElement;
import static com.google.gwt.core.shared.impl.InternalPreconditions.checkState;

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

import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;

/**
 * A simple wrapper around JavaScriptObject to provide {@link java.util.Map}-like semantics for any
 * key type.
 * 

* Implementation notes: *

* A key's hashCode is the index in backingMap 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. */ class InternalJsHashCodeMap { static class InternalJsHashCodeMapLegacy extends InternalJsHashCodeMap { @Override native JavaScriptObject createMap() /*-{ return {}; }-*/; @Override public native boolean containsValue(Object value) /*-{ var map = this.@InternalJsHashCodeMap::backingMap; for (var hashCode in map) { // sanity check that it's really an integer if (hashCode == parseInt(hashCode, 10)) { var array = map[hashCode]; for ( var i = 0, c = array.length; i < c; ++i) { var entry = array[i]; var entryValue = [email protected]::getValue()(); if (this.@InternalJsHashCodeMapLegacy::equalsBridge(*)(value, entryValue)) { return true; } } } } return false; }-*/; @Override public native Iterator> entries() /*-{ var list = this.@InternalJsHashCodeMapLegacy::newEntryList()(); var map = this.@InternalJsHashCodeMap::backingMap; for (var hashCode in map) { // sanity check that it's really an integer if (hashCode == parseInt(hashCode, 10)) { var array = map[hashCode]; for ( var i = 0, c = array.length; i < c; ++i) { list.@ArrayList::add(Ljava/lang/Object;)(array[i]); } } } return list.@ArrayList::iterator()(); }-*/; /** * Returns a custom ArrayList so that we could intercept removal to forward into our map. */ private ArrayList> newEntryList() { return new ArrayList>() { @Override public Entry remove(int index) { Entry removed = super.remove(index); InternalJsHashCodeMapLegacy.this.remove(removed.getKey()); return removed; } }; } /** * 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. */ private boolean equalsBridge(Object value1, Object value2) { return host.equals(value1, value2); } } private final JavaScriptObject backingMap = createMap(); AbstractHashMap host; native JavaScriptObject createMap() /*-{ return Object.create(null); }-*/; public V put(K key, V value) { Entry[] chain = ensureChain(hash(key)); for (Entry entry : chain) { if (host.equals(key, entry.getKey())) { // Found an exact match, just update the existing entry return entry.setValue(value); } } chain[chain.length] = new SimpleEntry(key, value); host.elementAdded(); return null; } public V remove(Object key) { String hashCode = hash(key); Entry[] chain = getChainOrEmpty(hashCode); for (int i = 0; i < chain.length; i++) { Entry entry = chain[i]; if (host.equals(key, entry.getKey())) { if (chain.length == 1) { // remove the whole array removeChain(hashCode); } else { // splice out the entry we're removing splice(chain, i); } host.elementRemoved(); return entry.getValue(); } } return null; } public Map.Entry getEntry(Object key) { for (Entry entry : getChainOrEmpty(hash(key))) { if (host.equals(key, entry.getKey())) { return entry; } } return null; } public boolean containsValue(Object value) { for (String hashCode : keys()) { for (Entry entry : getChain(hashCode)) { if (host.equals(value, entry.getValue())) { return true; } } } return false; } public Iterator> entries() { return new Iterator>() { final String[] keys = keys(); int chainIndex = -1, itemIndex = 0; Entry[] chain = new Entry[0]; Entry[] lastChain = null; Entry lastEntry = null; @Override public boolean hasNext() { if (itemIndex < chain.length) { return true; } if (chainIndex < keys.length - 1) { // Move to the beginning of next chain chain = getChain(keys[++chainIndex]); itemIndex = 0; return true; } return false; } @Override public Entry next() { checkElement(hasNext()); lastChain = chain; lastEntry = chain[itemIndex++]; return lastEntry; } @Override public void remove() { checkState(lastEntry != null); InternalJsHashCodeMap.this.remove(lastEntry.getKey()); // If we are sill in the same chain, our itemIndex just jumped an item. We can fix that // by decrementing the itemIndex. However there is an exception: if there is only one // item, the whole chain is simply dropped not the item. If we decrement in that case, as // the item is not drop from the chain, we will end up returning the same item twice. if (chain == lastChain && chain.length != 1) { itemIndex--; } lastEntry = null; } }; } private native String[] keys() /*-{ return Object.getOwnPropertyNames(this.@InternalJsHashCodeMap::backingMap); }-*/; private native Entry[] getChain(String hashCode) /*-{ return this.@InternalJsHashCodeMap::backingMap[hashCode]; }-*/; private native Entry[] getChainOrEmpty(String hashCode) /*-{ return this.@InternalJsHashCodeMap::backingMap[hashCode] || []; }-*/; private native Entry[] ensureChain(String hashCode) /*-{ var map = this.@InternalJsHashCodeMap::backingMap; return map[hashCode] || (map[hashCode] = []); }-*/; private native void removeChain(String hashCode) /*-{ delete this.@InternalJsHashCodeMap::backingMap[hashCode]; }-*/; /** * Returns hash code of the key as calculated by {@link AbstractMap#getHashCode(Object)} but also * handles null keys as well. */ private String hash(Object key) { return key == null ? "0" : String.valueOf(host.getHashCode(key)); } private static native void splice(Object arr, int index) /*-{ arr.splice(index, 1); }-*/; }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy