package org.oscim.utils; /* * Copyright 2014 Hannes Janetzek * * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). * * This program is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along with * this program. If not, see <http://www.gnu.org/licenses/>. */ /** * Stripped down HashMap making HashItem entries public - So you have your custom * 'Entry' holding key and value. HashItem must implement equals() and hashCode() * only for the 'key' part. Items may only be in one KeyMap at a time! * * KeyMap.put(HashItem, boolean replace) allows to get or add an item in one invocation. * * TODO add to NOTICE file * The VTM library includes software developed as part of the Apache * Harmony project which is copyright 2006, The Apache Software Foundation and * released under the Apache License 2.0. http://harmony.apache.org */ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ import java.util.Arrays; import org.oscim.utils.KeyMap.HashItem; import org.oscim.utils.pool.Inlist; /** * <p> * Note: the implementation of {@code KeyMap} is not synchronized. If one thread * of several threads accessing an instance modifies the map structurally, * access to the map needs to be synchronized. A structural modification is an * operation that adds or removes an entry. Changes in the value of an entry are * not structural changes. * * @param <K> the type of keys maintained by this map */ public class KeyMap<K extends HashItem> extends Inlist<KeyMap<K>> { /** * Min capacity (other than zero) for a HashMap. Must be a power of two * greater than 1 (and less than 1 << 30). */ private static final int MINIMUM_CAPACITY = 4; /** * Max capacity for a HashMap. Must be a power of two >= MINIMUM_CAPACITY. */ private static final int MAXIMUM_CAPACITY = 1 << 30; /** * An empty table shared by all zero-capacity maps (typically from default * constructor). It is never written to, and replaced on first put. Its size * is set to half the minimum, so that the first resize will create a * minimum-sized table. */ private static final HashItem[] EMPTY_TABLE = new HashItem[MINIMUM_CAPACITY >>> 1]; /** * The default load factor. Note that this implementation ignores the * load factor, but cannot do away with it entirely because it's * mentioned in the API. * * <p> * Note that this constant has no impact on the behavior of the program, but * it is emitted as part of the serialized form. The load factor of .75 is * hardwired into the program, which uses cheap shifts in place of expensive * division. */ static final float DEFAULT_LOAD_FACTOR = .75F; /** * The hash table. If this hash map contains a mapping for null, it is * not represented this hash table. */ HashItem[] table; /** * The number of mappings in this hash map. */ int size; /** * The table is rehashed when its size exceeds this threshold. * The value of this field is generally .75 * capacity, except when * the capacity is zero, as described in the EMPTY_TABLE declaration * above. */ private int threshold; /** * Constructs a new empty {@code HashMap} instance. */ public KeyMap() { table = (HashItem[]) EMPTY_TABLE; threshold = -1; // Forces first put invocation to replace EMPTY_TABLE } /** * Constructs a new {@code HashMap} instance with the specified capacity. * * @param capacity * the initial capacity of this hash map. * @throws IllegalArgumentException * when the capacity is less than zero. */ public KeyMap(int capacity) { if (capacity < 0) { throw new IllegalArgumentException("Capacity: " + capacity); } if (capacity == 0) { HashItem[] tab = (HashItem[]) EMPTY_TABLE; table = tab; threshold = -1; // Forces first put() to replace EMPTY_TABLE return; } if (capacity < MINIMUM_CAPACITY) { capacity = MINIMUM_CAPACITY; } else if (capacity > MAXIMUM_CAPACITY) { capacity = MAXIMUM_CAPACITY; } else { capacity = roundUpToPowerOfTwo(capacity); } makeTable(capacity); } /** * Constructs a new {@code HashMap} instance with the specified capacity and * load factor. * * @param capacity * the initial capacity of this hash map. * @param loadFactor * the initial load factor. * @throws IllegalArgumentException * when the capacity is less than zero or the load factor is * less or equal to zero or NaN. */ public KeyMap(int capacity, float loadFactor) { this(capacity); if (loadFactor <= 0 || Float.isNaN(loadFactor)) { throw new IllegalArgumentException("Load factor: " + loadFactor); } /* Note that this implementation ignores loadFactor; it always uses * a load factor of 3/4. This simplifies the code and generally * improves performance. */ } /** * Returns an appropriate capacity for the specified initial size. Does * not round the result up to a power of two; the caller must do this! * The returned value will be between 0 and MAXIMUM_CAPACITY (inclusive). */ static int capacityForInitSize(int size) { int result = (size >> 1) + size; // Multiply by 3/2 to allow for growth // boolean expr is equivalent to result >= 0 && result<MAXIMUM_CAPACITY return (result & ~(MAXIMUM_CAPACITY - 1)) == 0 ? result : MAXIMUM_CAPACITY; } /** * This method is called from the pseudo-constructors (clone and readObject) * prior to invoking constructorPut/constructorPutAll, which invoke the * overridden constructorNewEntry method. Normally it is a VERY bad idea to * invoke an overridden method from a pseudo-constructor (Effective Java * Item 17). In this case it is unavoidable, and the init method provides a * workaround. */ void init() { } /** * Returns whether this map is empty. * * @return {@code true} if this map has no elements, {@code false} * otherwise. * @see #size() */ public boolean isEmpty() { return size == 0; } /** * Returns the number of elements in this map. * * @return the number of elements in this map. */ public int size() { return size; } /** * Returns the value of the mapping with the specified key. * * @param key * the key. * @return the value of the mapping with the specified key, or {@code null} * if no mapping for the specified key is found. */ @SuppressWarnings("unchecked") public K get(HashItem key) { // Doug Lea's supplemental secondaryHash function (inlined) int hash = key.hashCode(); hash ^= (hash >>> 20) ^ (hash >>> 12); hash ^= (hash >>> 7) ^ (hash >>> 4); HashItem[] tab = table; for (HashItem e = tab[hash & (tab.length - 1)]; e != null; e = e.next) { HashItem eKey = e; if (eKey == key || (e.hash == hash && key.equals(eKey))) { return (K) e; } } return null; } /** * Maps the specified key to the specified value. * * @param key * the key. * @param value * the value. * @return the value of any previous mapping with the specified key or * {@code null} if there was no such mapping. */ public K put(K key) { return put(key, true); } @SuppressWarnings("unchecked") public K put(K key, boolean replace) { if (key.next != null) throw new IllegalStateException("item not unhooked"); int hash = secondaryHash(key.hashCode()); HashItem[] tab = table; int index = hash & (tab.length - 1); for (HashItem e = tab[index]; e != null; e = e.next) { if (e.hash == hash && key.equals(e)) { if (replace) { tab[index] = Inlist.remove(tab[index], e); tab[index] = Inlist.push(tab[index], key); } //V oldValue = e.value; //e.value = value; return (K) e; //oldValue; } } // No entry key is present; create one if (size++ > threshold) { tab = doubleCapacity(); index = hash & (tab.length - 1); } addNewEntry(key, hash, index); return null; } /** * Removes the mapping with the specified key from this map. * * @param key * the key of the mapping to remove. * @return the value of the removed mapping or {@code null} if no mapping * for the specified key was found. */ @SuppressWarnings("unchecked") public K remove(K key) { int hash = secondaryHash(key.hashCode()); HashItem[] tab = table; int index = hash & (tab.length - 1); for (HashItem e = tab[index], prev = null; e != null; prev = e, e = e.next) { if (e.hash == hash && key.equals(e)) { if (prev == null) { tab[index] = e.next; } else { prev.next = e.next; } e.next = null; //modCount++; size--; //postRemove(e); return (K) e; } } return null; } /** * Creates a new entry for the given key, value, hash, and index and * inserts it into the hash table. This method is called by put * (and indirectly, putAll), and overridden by LinkedHashMap. The hash * must incorporate the secondary hash function. */ void addNewEntry(K key, int hash, int index) { key.setIndex(hash, table[index]); table[index] = key; } /** * Allocate a table of the given capacity and set the threshold accordingly. * * @param newCapacity must be a power of two */ private HashItem[] makeTable(int newCapacity) { HashItem[] newTable = (HashItem[]) new HashItem[newCapacity]; table = newTable; threshold = (newCapacity >> 1) + (newCapacity >> 2); // 3/4 capacity return newTable; } /** * Doubles the capacity of the hash table. Existing entries are placed in * the correct bucket on the enlarged table. If the current capacity is, * MAXIMUM_CAPACITY, this method is a no-op. Returns the table, which * will be new unless we were already at MAXIMUM_CAPACITY. */ private HashItem[] doubleCapacity() { HashItem[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { return oldTable; } int newCapacity = oldCapacity * 2; HashItem[] newTable = makeTable(newCapacity); if (size == 0) { return newTable; } for (int j = 0; j < oldCapacity; j++) { /* Rehash the bucket using the minimum number of field writes. * This is the most subtle and delicate code in the class. */ HashItem e = oldTable[j]; if (e == null) { continue; } int highBit = e.hash & oldCapacity; HashItem broken = null; newTable[j | highBit] = e; for (HashItem n = e.next; n != null; e = n, n = n.next) { int nextHighBit = n.hash & oldCapacity; if (nextHighBit != highBit) { if (broken == null) newTable[j | nextHighBit] = n; else broken.next = n; broken = e; highBit = nextHighBit; } } if (broken != null) broken.next = null; } return newTable; } /** * Subclass overrides this method to unlink entry. */ void postRemove(HashItem e) { } /** * Removes all mappings from this hash map, leaving it empty. * * @see #isEmpty * @see #size */ public void clear() { if (size != 0) { Arrays.fill(table, null); size = 0; } } static final boolean STATS = false; @SuppressWarnings("unchecked") public K releaseItems() { if (size == 0) return null; int collisions = 0; int max = 0; int sum = 0; HashItem items = null; HashItem last; for (int i = 0, n = table.length; i < n; i++) { HashItem item = table[i]; if (item == null) continue; table[i] = null; if (STATS) { sum = 0; last = item; while (last != null) { if (last.next == null) break; sum++; last = last.next; } max = Math.max(max, sum); collisions += sum; } else { last = Inlist.last(item); } last.next = items; items = item; } if (STATS) System.out.println("collisions: " + collisions + " " + max + " " + size); Arrays.fill(table, null); size = 0; return (K) items; } public static class HashItem extends Inlist<HashItem> { int hash; public void setIndex(int hash, HashItem next) { this.hash = hash; this.next = next; } } /** * Applies a supplemental hash function to a given hashCode, which defends * against poor quality hash functions. This is critical because HashMap * uses power-of-two length hash tables, that otherwise encounter collisions * for hashCodes that do not differ in lower or upper bits. */ private static int secondaryHash(int h) { // Doug Lea's supplemental hash function h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } /** * Returns the smallest power of two >= its argument, with several caveats: * If the argument is negative but not Integer.MIN_VALUE, the method returns * zero. If the argument is > 2^30 or equal to Integer.MIN_VALUE, the method * returns Integer.MIN_VALUE. If the argument is zero, the method returns * zero. */ private static int roundUpToPowerOfTwo(int i) { i--; // If input is a power of two, shift its high-order bit right // "Smear" the high-order bit all the way to the right i |= i >>> 1; i |= i >>> 2; i |= i >>> 4; i |= i >>> 8; i |= i >>> 16; return i + 1; } // public K put(K key) { // // if (key == null) { // // return putValueForNullKey(value); // // } // // int hash = secondaryHash(key.hashCode()); // HashItem[] tab = table; // int index = hash & (tab.length - 1); // for (HashItem e = tab[index]; e != null; e = e.next) { // if (e.hash == hash && key.equals(e.key)) { // preModify(e); // //V oldValue = e.value; // //e.value = value; // return e.key; //oldValue; // } // } // // // No entry for (non-null) key is present; create one // modCount++; // if (size++ > threshold) { // tab = doubleCapacity(); // index = hash & (tab.length - 1); // } // addNewEntry(key, hash, index); // return null; // } // private V putValueForNullKey(V value) { // HashMapEntry<K> entry = entryForNullKey; // if (entry == null) { // addNewEntryForNullKey(value); // size++; // modCount++; // return null; // } else { // preModify(entry); // V oldValue = entry.value; // entry.value = value; // return oldValue; // } // } // /** // * Returns whether this map contains the specified key. // * // * @param key // * the key to search for. // * @return {@code true} if this map contains the specified key, // * {@code false} otherwise. // */ // //@Override // public boolean containsKey(Object key) { // if (key == null) { // return entryForNullKey != null; // } // // // Doug Lea's supplemental secondaryHash function (inlined) // int hash = key.hashCode(); // hash ^= (hash >>> 20) ^ (hash >>> 12); // hash ^= (hash >>> 7) ^ (hash >>> 4); // // HashItem[] tab = table; // for (HashItem e = tab[hash & (tab.length - 1)]; e != null; e = e.next) { // K eKey = e.key; // if (eKey == key || (e.hash == hash && key.equals(eKey))) { // return true; // } // } // return false; // } // /** // * Returns whether this map contains the specified value. // * // * @param value // * the value to search for. // * @return {@code true} if this map contains the specified value, // * {@code false} otherwise. // */ // @Override // public boolean containsValue(Object value) { // HashMapEntry[] tab = table; // int len = tab.length; // if (value == null) { // for (int i = 0; i < len; i++) { // for (HashMapEntry e = tab[i]; e != null; e = e.next) { // if (e.value == null) { // return true; // } // } // } // return entryForNullKey != null && entryForNullKey.value == null; // } // // // value is non-null // for (int i = 0; i < len; i++) { // for (HashMapEntry e = tab[i]; e != null; e = e.next) { // if (value.equals(e.value)) { // return true; // } // } // } // return entryForNullKey != null && value.equals(entryForNullKey.value); // } ///** // * Ensures that the hash table has sufficient capacity to store the // * specified number of mappings, with room to grow. If not, it increases the // * capacity as appropriate. Like doubleCapacity, this method moves existing // * entries to new buckets as appropriate. Unlike doubleCapacity, this method // * can grow the table by factors of 2^n for n > 1. Hopefully, a single call // * to this method will be faster than multiple calls to doubleCapacity. // * // * <p> // * This method is called only by putAll. // */ //private void ensureCapacity(int numMappings) { // int newCapacity = roundUpToPowerOfTwo(capacityForInitSize(numMappings)); // HashItem[] oldTable = table; // int oldCapacity = oldTable.length; // if (newCapacity <= oldCapacity) { // return; // } // if (newCapacity == oldCapacity * 2) { // doubleCapacity(); // return; // } // // // We're growing by at least 4x, rehash in the obvious way // HashItem[] newTable = makeTable(newCapacity); // if (size != 0) { // int newMask = newCapacity - 1; // for (int i = 0; i < oldCapacity; i++) { // for (HashItem e = oldTable[i]; e != null;) { // HashItem oldNext = e.next; // int newIndex = e.hash & newMask; // HashItem newNext = newTable[newIndex]; // newTable[newIndex] = e; // e.next = newNext; // e = oldNext; // } // } // } //} }