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

java.util.InternalHashCodeMap 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 java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;
import javaemul.internal.ArrayHelper;
import javaemul.internal.JsUtils;

/**
 * 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 InternalHashCodeMap implements Iterable> { private final InternalJsMap backingMap = new InternalJsMap(); private AbstractHashMap host; private int size; public InternalHashCodeMap(AbstractHashMap host) { this.host = host; } public V put(K key, V value) { int hashCode = hash(key); Entry[] chain = getChainOrEmpty(hashCode); if (chain.length == 0) { // This is a new chain, put it to the map. backingMap.set(hashCode, chain); } else { // Chain already exists, perhaps key also exists. Entry entry = findEntryInChain(key, chain); if (entry != null) { return entry.setValue(value); } } chain[chain.length] = new SimpleEntry(key, value); size++; host.structureChanged(); return null; } public V remove(Object key) { int 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) { ArrayHelper.setLength(chain, 0); // remove the whole array backingMap.delete(hashCode); } else { // splice out the entry we're removing ArrayHelper.removeFrom(chain, i, 1); } size--; host.structureChanged(); return entry.getValue(); } } return null; } public Map.Entry getEntry(Object key) { return findEntryInChain(key, getChainOrEmpty(hash(key))); } private Map.Entry findEntryInChain(Object key, Entry[] chain) { for (Entry entry : chain) { if (host.equals(key, entry.getKey())) { return entry; } } return null; } public int size() { return size; } @Override public Iterator> iterator() { return new Iterator>() { final InternalJsMap.Iterator chains = backingMap.entries(); int itemIndex = 0; Entry[] chain = newEntryChain(); Entry lastEntry = null; @Override public boolean hasNext() { if (itemIndex < chain.length) { return true; } InternalJsMap.IteratorEntry current = chains.next(); if (!current.isDone()) { // Move to the beginning of next chain chain = JsUtils.uncheckedCast(current.getValue()); itemIndex = 0; return true; } return false; } @Override public Entry next() { lastEntry = chain[itemIndex++]; return lastEntry; } @Override public void remove() { InternalHashCodeMap.this.remove(lastEntry.getKey()); // Unless we are in a new chain, all items have shifted so our itemIndex should as well... if (itemIndex != 0) { itemIndex--; } } }; } private Entry[] getChainOrEmpty(int hashCode) { Entry[] chain = JsUtils.uncheckedCast(backingMap.get(hashCode)); return chain == null ? newEntryChain() : chain; } private Entry[] newEntryChain() { return JsUtils.uncheckedCast(new Object[0]); } /** * Returns hash code of the key as calculated by {@link AbstractHashMap#getHashCode(Object)} but * also handles null keys as well. */ private int hash(Object key) { return host.getHashCode(key); } }