/** * Copyright 2010 JogAmp Community. 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 JogAmp Community ``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 JogAmp Community 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. * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of JogAmp Community. */ /** * Created on Sunday, March 28 2010 21:01 */ package com.jogamp.common.util; import com.jogamp.common.JogampRuntimeException; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; /* * Note: this map is used as template for other maps. */ /** * Fast HashMap for primitive data. Optimized for being GC friendly. * Original code is based on the skorpios project * released under new BSD license. * * @author Michael Bien * @author Simon Goller * @author Sven Gothel * * @see IntObjectHashMap * @see IntLongHashMap * @see LongObjectHashMap * @see LongLongHashMap * @see LongIntHashMap */ public class /*name*/IntIntHashMap/*name*/ implements Cloneable, Iterable< /*name*/IntIntHashMap/*name*/.Entry > { private final float loadFactor; private Entry[] table; private int size; private int mask; private int capacity; private int threshold; private /*value*/int/*value*/ keyNotFoundValue = /*null*/-1/*null*/; private static final boolean isPrimitive; private static final Constructor entryConstructor; private static final Method equalsMethod; static class EntryCM { EntryCM() { c = null; m1 = null; } Constructor c; Method m1; } static { final Class valueClazz = /*value*/int/*value*/.class; final Class keyClazz = /*key*/int/*key*/.class; isPrimitive = valueClazz.isPrimitive(); if(!isPrimitive) { final EntryCM cm = AccessController.doPrivileged(new PrivilegedAction() { @Override @SuppressWarnings("unchecked") public EntryCM run() { final EntryCM r = new EntryCM(); r.c = (Constructor) ReflectionUtil.getConstructor(Entry.class, new Class[] { keyClazz, valueClazz, Entry.class } ); try { r.m1 = valueClazz.getDeclaredMethod("equals", Object.class); } catch (final NoSuchMethodException ex) { throw new JogampRuntimeException("Class "+valueClazz+" doesn't support equals(Object)"); } return r; } } ); entryConstructor = cm.c; equalsMethod = cm.m1; } else { entryConstructor = null; equalsMethod = null; } } public /*name*/IntIntHashMap/*name*/() { this(16, 0.75f); } public /*name*/IntIntHashMap/*name*/(final int initialCapacity) { this(initialCapacity, 0.75f); } public /*name*/IntIntHashMap/*name*/(final int initialCapacity, final float loadFactor) { if (initialCapacity > 1 << 30) { throw new IllegalArgumentException("initialCapacity is too large."); } if (initialCapacity < 0) { throw new IllegalArgumentException("initialCapacity must be greater than zero."); } if (loadFactor <= 0) { throw new IllegalArgumentException("loadFactor must be greater than zero."); } capacity = 1; while (capacity < initialCapacity) { capacity <<= 1; } this.loadFactor = loadFactor; this.threshold = (int) (capacity * loadFactor); this.table = new Entry[capacity]; this.mask = capacity - 1; } private /*name*/IntIntHashMap/*name*/(final float loadFactor, final int table_size, final int size, final int mask, final int capacity, final int threshold, final /*value*/int/*value*/ keyNotFoundValue) { this.loadFactor = loadFactor; this.table = new Entry[table_size]; this.size = size; this.mask = mask; this.capacity = capacity; this.threshold = threshold; this.keyNotFoundValue = keyNotFoundValue; } /** * Disclaimer: If the value type doesn't implement {@link Object#clone() clone()}, only the reference is copied. * Note: Due to private fields we cannot implement a copy constructor, sorry. * * @param source the primitive hash map to copy */ @Override public Object clone() { final /*name*/IntIntHashMap/*name*/ n = new /*name*/IntIntHashMap/*name*/(loadFactor, table.length, size, mask, capacity, threshold, keyNotFoundValue); final ArrayList entries = new ArrayList(); for(int i=table.length-1; i>=0; i--) { // single linked list -> ArrayList Entry se = table[i]; while(null != se) { entries.add(se); se = se.next; } // clone ArrayList -> single linked list (bwd) final int count = entries.size(); Entry de_next = null; for(int j=count-1; j>=0; j--) { se = entries.remove(j); if( isPrimitive ) { de_next = new Entry(se.key, se.value, de_next); } else { final Object v = ReflectionUtil.callMethod( se.value, getCloneMethod(se.value) ); de_next = (Entry) ReflectionUtil.createInstance(entryConstructor, se.key, v, de_next); } } // 1st elem of linked list is table entry n.table[i] = de_next; } return n; } public boolean containsValue(final /*value*/int/*value*/ value) { final Entry[] t = this.table; for (int i = t.length; i-- > 0;) { for (Entry e = t[i]; e != null; e = e.next) { if( isPrimitive ) { if (e.value == value) { return true; } } else { final Boolean b = (Boolean) ReflectionUtil.callMethod(value, equalsMethod, e.value); if(b.booleanValue()) { return true; } } } } return false; } // @SuppressWarnings(value="cast") public boolean containsKey(final /*key*/int/*key*/ key) { final Entry[] t = this.table; final int index = /*keyHash*/key/*keyHash*/ & mask; for (Entry e = t[index]; e != null; e = e.next) { if (e.key == key) { return true; } } return false; } /** * Returns the value to which the specified key is mapped, * or {@link #getKeyNotFoundValue} if this map contains no mapping for the key. */ // @SuppressWarnings(value="cast") public /*value*/int/*value*/ get(final /*key*/int/*key*/ key) { final Entry[] t = this.table; final int index = /*keyHash*/key/*keyHash*/ & mask; for (Entry e = t[index]; e != null; e = e.next) { if (e.key == key) { return e.value; } } return keyNotFoundValue; } /** * Maps the key to the specified value. If a mapping to this key already exists, * the previous value will be returned (otherwise {@link #getKeyNotFoundValue}). */ // @SuppressWarnings(value="cast") public /*value*/int/*value*/ put(final /*key*/int/*key*/ key, final /*value*/int/*value*/ value) { final Entry[] t = this.table; final int index = /*keyHash*/key/*keyHash*/ & mask; // Check if key already exists. for (Entry e = t[index]; e != null; e = e.next) { if (e.key != key) { continue; } final /*value*/int/*value*/ oldValue = e.value; e.value = value; return oldValue; } t[index] = new Entry(key, value, t[index]); if (size++ >= threshold) { // Rehash. final int newCapacity = 2 * capacity; final Entry[] newTable = new Entry[newCapacity]; final int newMask = newCapacity - 1; for (int j = 0; j < t.length; j++) { Entry e = t[j]; if (e != null) { t[j] = null; do { final Entry next = e.next; final int index2 = /*keyHash*/e.key/*keyHash*/ & newMask; e.next = newTable[index2]; newTable[index2] = e; e = next; } while (e != null); } } table = newTable; capacity = newCapacity; threshold = (int) (newCapacity * loadFactor); mask = newMask; } return keyNotFoundValue; } /** * Copies all of the mappings from the specified map to this map. */ public void putAll(final /*name*/IntIntHashMap/*name*/ source) { final Iterator itr = source.iterator(); while(itr.hasNext()) { final Entry e = itr.next(); put(e.key, e.value); } } /** * Removes the key-value mapping from this map. * Returns the previously mapped value or {@link #getKeyNotFoundValue} if no such mapping exists. */ // @SuppressWarnings(value="cast") public /*value*/int/*value*/ remove(final /*key*/int/*key*/ key) { final Entry[] t = this.table; final int index = /*keyHash*/key/*keyHash*/ & mask; Entry prev = t[index]; Entry e = prev; while (e != null) { final Entry next = e.next; if (e.key == key) { size--; if (prev == e) { t[index] = next; } else { prev.next = next; } return e.value; } prev = e; e = next; } return keyNotFoundValue; } /** * Returns the current number of key-value mappings in this map. */ public int size() { return size; } /** * Returns the current capacity (buckets) in this map. */ public int capacity() { return capacity; } /** * Clears the entire map. The size is 0 after this operation. */ public void clear() { Arrays.fill(table, null); size = 0; } /** * Returns a new {@link Iterator}. * Note: this Iterator does not yet support removal of elements. */ @Override public Iterator iterator() { return new EntryIterator(table); } /** * Sets the new key not found value. * For primitive types (int, long) the default is -1, * for Object types, the default is null. * * @return the previous key not found value * @see #get * @see #put */ public /*value*/int/*value*/ setKeyNotFoundValue(final /*value*/int/*value*/ newKeyNotFoundValue) { final /*value*/int/*value*/ t = keyNotFoundValue; keyNotFoundValue = newKeyNotFoundValue; return t; } /** * Returns the value which is returned if no value has been found for the specified key. * @see #get * @see #put */ public /*value*/int/*value*/ getKeyNotFoundValue() { return keyNotFoundValue; } /** * @param sb if null, a new StringBuilder is created * @return StringBuilder instance with appended string information of this Entry */ public StringBuilder toString(StringBuilder sb) { if(null == sb) { sb = new StringBuilder(); } sb.append("{"); final Iterator itr = iterator(); while(itr.hasNext()) { itr.next().toString(sb); if(itr.hasNext()) { sb.append(", "); } } sb.append("}"); return sb; } @Override public String toString() { return toString(null).toString(); } private final static class EntryIterator implements Iterator { private final Entry[] entries; private int index; private Entry next; private EntryIterator(final Entry[] entries){ this.entries = entries; // load next next(); } @Override public boolean hasNext() { return next != null; } @Override public Entry next() { final Entry current = next; if(current != null && current.next != null) { next = current.next; }else{ while(index < entries.length) { final Entry e = entries[index++]; if(e != null) { next = e; return current; } } next = null; } return current; } @Override public void remove() { throw new UnsupportedOperationException("Not supported yet."); } } /** * An entry mapping a key to a value. */ public final static class Entry { public final /*key*/int/*key*/ key; public /*value*/int/*value*/ value; Entry next; Entry(final /*key*/int/*key*/ k, final /*value*/int/*value*/ v, final Entry n) { key = k; value = v; next = n; } /** * Returns the key of this entry. */ public /*key*/int/*key*/ getKey() { return key; } /** * Returns the value of this entry. */ public /*value*/int/*value*/ getValue() { return value; } /** * Sets the value for this entry. */ public void setValue(final /*value*/int/*value*/ value) { this.value = value; } /** * @param sb if null, a new StringBuilder is created * @return StringBuilder instance with appended string information of this Entry */ public StringBuilder toString(StringBuilder sb) { if(null == sb) { sb = new StringBuilder(); } sb.append("[").append(key).append(":").append(value).append("]"); return sb; } @Override public String toString() { return toString(null).toString(); } } private static Method getCloneMethod(final Object obj) { final Class clazz = obj.getClass(); return AccessController.doPrivileged(new PrivilegedAction() { @Override public Method run() { try { return clazz.getDeclaredMethod("clone"); } catch (final NoSuchMethodException ex) { throw new JogampRuntimeException("Class "+clazz+" doesn't support clone()", ex); } } } ); } }