package gregtech.api.objects; import java.util.Arrays; /** * Object-2-object map based on IntIntMap4a */ public class ObjMap { private static final Object FREE_KEY = new Object(); private static final Object REMOVED_KEY = new Object(); /** Keys and values */ private Object[] m_data; /** Value for the null key (if inserted into a map) */ private Object m_nullValue; private boolean m_hasNull; /** Fill factor, must be between (0 and 1) */ private final float m_fillFactor; /** We will resize a map once it reaches this size */ private int m_threshold; /** Current map size */ private int m_size; /** Mask to calculate the original position */ private int m_mask; /** Mask to wrap the actual array pointer */ private int m_mask2; public ObjMap( final int size, final float fillFactor ) { if ( fillFactor <= 0 || fillFactor >= 1 ) throw new IllegalArgumentException( "FillFactor must be in (0, 1)" ); if ( size <= 0 ) throw new IllegalArgumentException( "Size must be positive!" ); final int capacity = arraySize(size, fillFactor); m_mask = capacity - 1; m_mask2 = capacity * 2 - 1; m_fillFactor = fillFactor; m_data = new Object[capacity * 2]; Arrays.fill( m_data, FREE_KEY ); m_threshold = (int) (capacity * fillFactor); } @SuppressWarnings("unchecked") public V get( final K key ) { if ( key == null ) return (V) m_nullValue; //we null it on remove, so safe not to check a flag here int ptr = (key.hashCode() & m_mask) << 1; Object k = m_data[ ptr ]; if ( k == FREE_KEY ) return null; //end of chain already if ( k.equals( key ) ) //we check FREE and REMOVED prior to this call return (V) m_data[ ptr + 1 ]; while ( true ) { ptr = (ptr + 2) & m_mask2; //that's next index k = m_data[ ptr ]; if ( k == FREE_KEY ) return null; if ( k.equals( key ) ) return (V) m_data[ ptr + 1 ]; } } @SuppressWarnings("unchecked") public V put( final K key, final V value ) { if ( key == null ) return insertNullKey(value); int ptr = getStartIndex(key) << 1; Object k = m_data[ptr]; if ( k == FREE_KEY ) //end of chain already { m_data[ ptr ] = key; m_data[ ptr + 1 ] = value; if ( m_size >= m_threshold ) rehash( m_data.length * 2 ); //size is set inside else ++m_size; return null; } else if ( k.equals( key ) ) //we check FREE and REMOVED prior to this call { final Object ret = m_data[ ptr + 1 ]; m_data[ ptr + 1 ] = value; return (V) ret; } int firstRemoved = -1; if ( k == REMOVED_KEY ) firstRemoved = ptr; //we may find a key later while ( true ) { ptr = ( ptr + 2 ) & m_mask2; //that's next index calculation k = m_data[ ptr ]; if ( k == FREE_KEY ) { if ( firstRemoved != -1 ) ptr = firstRemoved; m_data[ ptr ] = key; m_data[ ptr + 1 ] = value; if ( m_size >= m_threshold ) rehash( m_data.length * 2 ); //size is set inside else ++m_size; return null; } else if ( k.equals( key ) ) { final Object ret = m_data[ ptr + 1 ]; m_data[ ptr + 1 ] = value; return (V) ret; } else if ( k == REMOVED_KEY ) { if ( firstRemoved == -1 ) firstRemoved = ptr; } } } @SuppressWarnings("unchecked") public V remove( final K key ) { if ( key == null ) return removeNullKey(); int ptr = getStartIndex(key) << 1; Object k = m_data[ ptr ]; if ( k == FREE_KEY ) return null; //end of chain already else if ( k.equals( key ) ) //we check FREE and REMOVED prior to this call { --m_size; if ( m_data[ ( ptr + 2 ) & m_mask2 ] == FREE_KEY ) m_data[ ptr ] = FREE_KEY; else m_data[ ptr ] = REMOVED_KEY; final V ret = (V) m_data[ ptr + 1 ]; m_data[ ptr + 1 ] = null; return ret; } while ( true ) { ptr = ( ptr + 2 ) & m_mask2; //that's next index calculation k = m_data[ ptr ]; if ( k == FREE_KEY ) return null; else if ( k.equals( key ) ) { --m_size; if ( m_data[ ( ptr + 2 ) & m_mask2 ] == FREE_KEY ) m_data[ ptr ] = FREE_KEY; else m_data[ ptr ] = REMOVED_KEY; final V ret = (V) m_data[ ptr + 1 ]; m_data[ ptr + 1 ] = null; return ret; } } } @SuppressWarnings("unchecked") private V insertNullKey(final V value) { if ( m_hasNull ) { final Object ret = m_nullValue; m_nullValue = value; return (V) ret; } else { m_nullValue = value; ++m_size; return null; } } @SuppressWarnings("unchecked") private V removeNullKey() { if ( m_hasNull ) { final Object ret = m_nullValue; m_nullValue = null; m_hasNull = false; --m_size; return (V) ret; } else { return null; } } public int size() { return m_size; } @SuppressWarnings("unchecked") private void rehash( final int newCapacity ) { m_threshold = (int) (newCapacity/2 * m_fillFactor); m_mask = newCapacity/2 - 1; m_mask2 = newCapacity - 1; final int oldCapacity = m_data.length; final Object[] oldData = m_data; m_data = new Object[ newCapacity ]; Arrays.fill( m_data, FREE_KEY ); m_size = m_hasNull ? 1 : 0; for ( int i = 0; i < oldCapacity; i += 2 ) { final Object oldKey = oldData[ i ]; if( oldKey != FREE_KEY && oldKey != REMOVED_KEY ) put( (K)oldKey, (V)oldData[ i + 1 ]); } } public int getStartIndex( final Object key ) { //key is not null here return key.hashCode() & m_mask; } /** Taken from FastUtil implementation */ /** Return the least power of two greater than or equal to the specified value. * *

Note that this function will return 1 when the argument is 0. * * @param x a long integer smaller than or equal to 262. * @return the least power of two greater than or equal to the specified value. */ public static long nextPowerOfTwo( long x ) { if ( x == 0 ) return 1; x--; x |= x >> 1; x |= x >> 2; x |= x >> 4; x |= x >> 8; x |= x >> 16; return ( x | x >> 32 ) + 1; } /** Returns the least power of two smaller than or equal to 230 and larger than or equal to Math.ceil( expected / f ). * * @param expected the expected number of elements in a hash table. * @param f the load factor. * @return the minimum possible size for a backing array. * @throws IllegalArgumentException if the necessary size is larger than 230. */ public static int arraySize( final int expected, final float f ) { final long s = Math.max( 2, nextPowerOfTwo( (long)Math.ceil( expected / f ) ) ); if ( s > (1 << 30) ) throw new IllegalArgumentException( "Too large (" + expected + " expected elements with load factor " + f + ")" ); return (int)s; } //taken from FastUtil private static final int INT_PHI = 0x9E3779B9; public static int phiMix( final int x ) { final int h = x * INT_PHI; return h ^ (h >> 16); } }