/*! ******************************************************************************
*
* Pentaho Data Integration
*
* Copyright (C) 2002-2013 by Pentaho : http://www.pentaho.com
*
*******************************************************************************
*
* 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 org.pentaho.di.core.hash;
import org.pentaho.di.core.exception.KettleValueException;
public class LongHashIndex {
private static final int STANDARD_INDEX_SIZE = 512;
private static final float STANDARD_LOAD_FACTOR = 0.78f;
private LongHashIndexEntry[] index;
private int size;
private int resizeThresHold;
/**
* Create a new long/long hash index
*
* @param size
* the initial size of the hash index
*/
public LongHashIndex( int size ) {
// Find a suitable capacity being a factor of 2:
int factor2Size = 1;
while ( factor2Size < size ) {
factor2Size <<= 1; // Multiply by 2
}
this.resizeThresHold = (int) ( factor2Size * STANDARD_LOAD_FACTOR );
index = new LongHashIndexEntry[factor2Size];
}
/**
* Create a new long/long hash index
*/
public LongHashIndex() {
this( STANDARD_INDEX_SIZE );
}
public int getSize() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public Long get( long key ) throws KettleValueException {
int hashCode = generateHashCode( key );
int indexPointer = indexFor( hashCode, index.length );
LongHashIndexEntry check = index[indexPointer];
while ( check != null ) {
if ( check.hashCode == hashCode && check.equalsKey( key ) ) {
return check.value;
}
check = check.nextEntry;
}
return null;
}
public void put( long key, Long value ) throws KettleValueException {
int hashCode = generateHashCode( key );
int indexPointer = indexFor( hashCode, index.length );
LongHashIndexEntry check = index[indexPointer];
LongHashIndexEntry previousCheck = null;
while ( check != null ) {
// If there is an identical entry in there, we replace the entry
// And then we just return...
//
if ( check.hashCode == hashCode && check.equalsKey( key ) ) {
check.value = value;
return;
}
previousCheck = check;
check = check.nextEntry;
}
// Don't forget to link to the previous check entry if there was any...
//
if ( previousCheck != null ) {
previousCheck.nextEntry = new LongHashIndexEntry( hashCode, key, value, null );
} else {
index[indexPointer] = new LongHashIndexEntry( hashCode, key, value, null );
}
// If required, resize the table...
//
resize();
}
private final void resize() {
// Increase the size of the index...
//
size++;
// See if we've reached our resize threshold...
//
if ( size >= resizeThresHold ) {
LongHashIndexEntry[] oldIndex = index;
// Double the size to keep the size of the index a factor of 2...
// Allocate the new array...
//
int newSize = 2 * index.length;
LongHashIndexEntry[] newIndex = new LongHashIndexEntry[newSize];
// Loop over the old index and re-distribute the entries
// We want to make sure that the calculation
// entry.hashCode & ( size - 1)
// ends up in the right location after re-sizing...
//
for ( int i = 0; i < oldIndex.length; i++ ) {
LongHashIndexEntry entry = oldIndex[i];
if ( entry != null ) {
oldIndex[i] = null;
// Make sure we follow all the linked entries...
// This is a bit of extra work, TODO: see how we can avoid it!
//
do {
LongHashIndexEntry next = entry.nextEntry;
int indexPointer = indexFor( entry.hashCode, newSize );
entry.nextEntry = newIndex[indexPointer];
newIndex[indexPointer] = entry;
entry = next;
} while ( entry != null );
}
}
// Replace the old index with the new one we just created...
//
index = newIndex;
// Also change the resize threshold...
//
resizeThresHold = (int) ( newSize * STANDARD_LOAD_FACTOR );
}
}
public static int generateHashCode( Long key ) throws KettleValueException {
return key.hashCode();
}
public static int indexFor( int hash, int length ) {
return hash & ( length - 1 );
}
private static final class LongHashIndexEntry {
private int hashCode;
private long key;
private long value;
private LongHashIndexEntry nextEntry;
/**
* @param hashCode
* @param key
* @param value
* @param nextEntry
*/
public LongHashIndexEntry( int hashCode, Long key, Long value, LongHashIndexEntry nextEntry ) {
this.hashCode = hashCode;
this.key = key;
this.value = value;
this.nextEntry = nextEntry;
}
public boolean equalsKey( long cmpKey ) {
return key == cmpKey;
}
}
}