// Copyright (c) 2003-present, Jodd Team (http://jodd.org) // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. package jodd.util.collection; import java.util.Arrays; /** * Storage for holding classes keys and values. * It is <b>NOT</b> a <code>Map</code> instance. It is very fast * on un-synchronized lookups, faster then <code>HashMap</code>. * Uses identity for checking if <code>Class</code> keys are equal. * <p> * The initial version of this class was provided by @zqq90, from * <a href="https://github.com/zqq90/webit-script/">WebIt-script</a> * project. Thank you! */ public final class ClassMap<V> { private static final int MAXIMUM_CAPACITY = 1 << 29; private Entry<V>[] table; private int threshold; private int size; /** * Creates new map with given initial capacity. */ @SuppressWarnings("unchecked") public ClassMap(int initialCapacity) { int initlen; if (initialCapacity > MAXIMUM_CAPACITY) { initlen = MAXIMUM_CAPACITY; } else { initlen = 16; while (initlen < initialCapacity) { initlen <<= 1; } } this.table = new Entry[initlen]; this.threshold = (int) (initlen * 0.75f); } public ClassMap() { this(64); } /** * Returns total number of stored classes. */ public int size() { return size; } /** * Returns a value associated to a key in unsafe, but very fast way. */ public V unsafeGet(final Class key) { final Entry<V>[] tab = table; Entry<V> e = (tab)[key.hashCode() & (tab.length - 1)]; while (e != null) { if (key == e.key) { return e.value; } e = e.next; } return null; } /** * Returns a value associated to a key in thread-safe way. */ public synchronized V get(Class key) { return unsafeGet(key); } @SuppressWarnings("unchecked") private void resize() { if (size < threshold) { return; } final Entry<V>[] oldTable = table; final int oldCapacity = oldTable.length; final int newCapacity = oldCapacity << 1; if (newCapacity > MAXIMUM_CAPACITY) { if (threshold == MAXIMUM_CAPACITY - 1) { throw new IllegalStateException("Capacity exhausted"); } threshold = MAXIMUM_CAPACITY - 1; return; } final int newMark = newCapacity - 1; final Entry<V>[] newTable = new Entry[newCapacity]; for (int i = oldCapacity; i-- > 0; ) { int index; for (Entry<V> old = oldTable[i], e; old != null; ) { e = old; old = old.next; index = e.id & newMark; e.next = newTable[index]; newTable[index] = e; } } this.threshold = (int) (newCapacity * 0.75f); // must be last this.table = newTable; } /** * Associates the specified value with the specified Class in this map. * Returns the previous value associated with key, or <code>null</code> * if there was no mapping for key. */ @SuppressWarnings("unchecked") public synchronized V put(Class key, V value) { final int id; int index; Entry<V>[] tab = table; id = key.hashCode(); index = (id) & (tab.length - 1); Entry<V> e = (tab)[index]; while (e != null) { if (key == e.key) { // identity check // key found, replace V existing = e.value; e.value = value; return existing; } e = e.next; } if (size >= threshold) { resize(); tab = table; index = id & (tab.length - 1); } // creates the new entry tab[index] = new Entry(id, key, value, tab[index]); size++; return null; } /** * Clears the class map. */ public synchronized void clear() { size = 0; Arrays.fill(table, null); } /** * Maps entry. */ private static final class Entry<V> { final int id; final Class key; V value; Entry<V> next; private Entry(int id, Class key, V value, Entry<V> next) { this.value = value; this.id = id; this.key = key; this.next = next; } } }