/*
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* 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.drools.core.util.index;
import org.drools.core.common.InternalFactHandle;
import org.drools.core.reteoo.TupleMemory;
import org.drools.core.spi.Tuple;
import org.drools.core.util.AbstractHashTable;
import org.drools.core.util.Entry;
import org.drools.core.util.FastIterator;
import org.drools.core.util.Iterator;
import org.drools.core.util.LinkedList;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class TupleIndexHashTable extends AbstractHashTable implements TupleMemory {
private static final long serialVersionUID = 510l;
public static final int PRIME = 31;
private int startResult;
private transient FieldIndexHashTableFullIterator tupleValueFullIterator;
private transient FullFastIterator fullFastIterator;
private int factSize;
private Index index;
private boolean left;
public TupleIndexHashTable() {
// constructor for serialisation
}
public TupleIndexHashTable( FieldIndex[] index, boolean left ) {
this( 128, 0.75f, index, left );
}
public TupleIndexHashTable( int capacity,
float loadFactor,
FieldIndex[] index,
boolean left ) {
super( capacity,
loadFactor );
this.left = left;
this.startResult = PRIME;
for ( FieldIndex i : index ) {
this.startResult += PRIME * this.startResult + i.getExtractor().getIndex();
}
switch ( index.length ) {
case 0 :
throw new IllegalArgumentException( "FieldIndexHashTable cannot use an index[] of length 0" );
case 1 :
this.index = new SingleIndex( index,
this.startResult );
break;
case 2 :
this.index = new DoubleCompositeIndex( index,
this.startResult );
break;
case 3 :
this.index = new TripleCompositeIndex( index,
this.startResult );
break;
default :
throw new IllegalArgumentException( "FieldIndexHashTable cannot use an index[] of length great than 3" );
}
}
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
super.readExternal( in );
startResult = in.readInt();
factSize = in.readInt();
index = (Index) in.readObject();
left = in.readBoolean();
}
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal( out );
out.writeInt( startResult );
out.writeInt( factSize );
out.writeObject( index );
out.writeBoolean( left );
}
public void init(Entry[] table, int size, int factSize) {
this.table = table;
this.size = size;
this.factSize = factSize;
}
public Iterator<Tuple> iterator() {
if ( this.tupleValueFullIterator == null ) {
this.tupleValueFullIterator = new FieldIndexHashTableFullIterator( this );
} else {
this.tupleValueFullIterator.reset();
}
return this.tupleValueFullIterator;
}
public FastIterator fastIterator() {
return LinkedList.fastIterator;
}
public FastIterator fullFastIterator() {
if ( fullFastIterator == null ) {
fullFastIterator = new FullFastIterator( this.table );
} else {
fullFastIterator.reset(this.table);
}
return fullFastIterator;
}
public FastIterator fullFastIterator(Tuple leftTuple) {
fullFastIterator.resume(leftTuple.getMemory(), this.table);
return fullFastIterator;
}
public static class FullFastIterator implements FastIterator {
private Entry[] table;
private int row;
public FullFastIterator(Entry[] table, int row) {
this.table = table;
this.row = row + 1;
}
public FullFastIterator(Entry[] table) {
this.table = table;
this.row = 0;
}
public void resume(Entry target, Entry[] table) {
this.table = table;
row = indexOf( target.hashCode(),
this.table.length );
row++; // row always points to the row after the current list
}
public Entry next(Entry object) {
Tuple tuple = ( Tuple ) object;
TupleList list = null;
if ( tuple != null ) {
list = tuple.getMemory(); // assumes you do not pass in a null RightTuple
}
int length = table.length;
while ( this.row <= length ) {
// check if there is a current bucket
while ( list == null ) {
if ( this.row < length ) {
// iterate while there is no current bucket, trying each array position
list = (TupleList) this.table[this.row];
this.row++;
} else {
// we've scanned the whole table and nothing is left, so return null
return null;
}
if ( list != null ) {
// we have a bucket so assign the frist LeftTuple and return
tuple = list.getFirst( );
return tuple;
}
}
tuple = tuple.getNext();
if ( tuple != null ) {
// we have a next tuple so return
return tuple;
} else {
list = list.getNext();
// try the next bucket if we have a shared array position
if ( list != null ) {
// if we have another bucket, assign the first LeftTuple and return
tuple = list.getFirst( );
return tuple;
}
}
}
return null;
}
public boolean isFullIterator() {
return true;
}
public void reset(Entry[] table) {
this.table = table;
this.row = 0;
}
}
public Tuple getFirst(final Tuple rightTuple) {
TupleList bucket = get( rightTuple, !left );
return bucket != null ? bucket.getFirst() : null;
}
public boolean isIndexed() {
return true;
}
public Index getIndex() {
return this.index;
}
@Override
public int getResizeHashcode(Entry entry) {
// Entry is always LeftTupleList which caches the hashcode, so just return it
return entry.hashCode();
}
public static class FieldIndexHashTableFullIterator
implements
Iterator<Tuple> {
private final AbstractHashTable hashTable;
private Entry[] table;
private int row;
private int length;
private TupleList list;
private Tuple tuple;
public FieldIndexHashTableFullIterator(final AbstractHashTable hashTable) {
this.hashTable = hashTable;
reset();
}
public Tuple next() {
while ( this.row <= this.length ) {
// check if there is a current bucket
while ( this.list == null ) {
if ( this.row < length ) {
// iterate while there is no current bucket, trying each array position
this.list = (TupleList) this.table[this.row];
this.row++;
} else {
// we've scanned the whole table and nothing is left, so return null
return null;
}
if ( this.list != null ) {
// we have a bucket so assign the first LeftTuple and return
this.tuple = this.list.getFirst( );
return this.tuple;
}
}
this.tuple = this.tuple.getNext();
if ( this.tuple != null ) {
// we have a next tuple so return
return this.tuple;
} else {
this.list = this.list.getNext();
// try the next bucket if we have a shared array position
if ( this.list != null ) {
// if we have another bucket, assign the first LeftTuple and return
this.tuple = this.list.getFirst( );
return this.tuple;
}
}
}
return null;
}
public void remove() {
throw new UnsupportedOperationException( "FieldIndexHashTableFullIterator does not support remove()." );
}
/* (non-Javadoc)
* @see org.kie.util.Iterator#reset()
*/
public void reset() {
this.table = this.hashTable.getTable();
this.length = this.table.length;
this.row = 0;
this.list = null;
this.tuple = null;
}
}
public Tuple[] toArray() {
Tuple[] result = new Tuple[this.factSize];
int index = 0;
for (Entry aTable : this.table) {
TupleList bucket = (TupleList) aTable;
while (bucket != null) {
Tuple entry = bucket.getFirst();
while (entry != null) {
result[index++] = entry;
entry = entry.getNext();
}
bucket = bucket.getNext();
}
}
return result;
}
public void removeAdd(Tuple tuple) {
TupleList memory = tuple.getMemory();
memory.remove( tuple );
final int newHashCode = this.index.hashCodeOf( tuple, left );
if ( newHashCode == memory.hashCode() ) {
// it's the same bucket, so re-use and return
memory.add( tuple );
return;
}
// bucket is empty so remove.
this.factSize--;
if ( memory.getFirst() == null ) {
final int index = indexOf( memory.hashCode(),
this.table.length );
TupleList previous = null;
TupleList current = (TupleList) this.table[index];
while ( current != memory ) {
previous = current;
current = current.getNext();
}
if ( previous != null ) {
previous.setNext( current.getNext() );
} else {
this.table[index] = current.getNext();
}
this.size--;
}
add( tuple );
}
public void add(final Tuple tuple) {
final TupleList entry = getOrCreate( tuple );
entry.add( tuple );
this.factSize++;
}
public void remove(final Tuple tuple) {
TupleList memory = tuple.getMemory();
memory.remove( tuple );
this.factSize--;
if ( memory.getFirst() == null ) {
final int index = indexOf( memory.hashCode(),
this.table.length );
TupleList previous = null;
TupleList current = (TupleList) this.table[index];
while ( current != memory ) {
previous = current;
current = current.getNext();
}
if ( previous != null ) {
previous.setNext( current.getNext() );
} else {
this.table[index] = current.getNext();
}
this.size--;
}
tuple.clear();
}
/**
* We use this method to aviod to table lookups for the same hashcode; which is what we would have to do if we did
* a get and then a create if the value is null.
*/
private TupleList getOrCreate(final Tuple tuple) {
final int hashCode = this.index.hashCodeOf( tuple, left );
final int index = indexOf( hashCode, this.table.length );
TupleList entry = (TupleList) this.table[index];
// search to find an existing entry
while ( entry != null ) {
if ( matches( entry, tuple, hashCode, !left ) ) {
return entry;
}
entry = entry.getNext();
}
// entry does not exist, so create
entry = this.index.createEntry( tuple, hashCode, left );
entry.setNext( (TupleList) this.table[index] );
this.table[index] = entry;
if ( this.size++ >= this.threshold ) {
resize( 2 * this.table.length );
}
return entry;
}
public boolean contains(final Tuple tuple) {
return get(tuple, left) != null;
}
private TupleList get(final Tuple tuple, boolean isLeftTuple) {
final int hashCode = this.index.hashCodeOf( tuple, isLeftTuple );
final int index = indexOf( hashCode, this.table.length );
TupleList entry = (TupleList) this.table[index];
while ( entry != null ) {
if ( matches(entry, tuple, hashCode, left ) ) {
return entry;
}
entry = entry.getNext();
}
return null;
}
private boolean matches( TupleList list, Tuple tuple, int tupleHashCode, boolean left ) {
if ( list.hashCode() != tupleHashCode ) {
return false;
}
return left ?
this.index.equal( list, tuple.getFactHandle().getObject() ) :
this.index.equal( list, tuple );
}
public int size() {
return this.factSize;
}
public String toString() {
StringBuilder builder = new StringBuilder();
Iterator it = iterator();
for ( Tuple leftTuple = (Tuple) it.next(); leftTuple != null; leftTuple = (Tuple) it.next() ) {
builder.append(leftTuple).append("\n");
}
return builder.toString();
}
public void clear() {
super.clear();
this.startResult = PRIME;
this.factSize = 0;
this.fullFastIterator = null;
this.tupleValueFullIterator = null;
}
public IndexType getIndexType() {
return IndexType.EQUAL;
}
public Tuple getFirst(Tuple leftTuple, InternalFactHandle factHandle) {
TupleList bucket = get( leftTuple, factHandle );
return bucket != null ? bucket.getFirst() : null;
}
private TupleList get(final Tuple tuple, InternalFactHandle factHandle) {
int hashCode = this.index.hashCodeOf( tuple, !left );
int index = indexOf( hashCode, this.table.length );
TupleList entry = (TupleList) this.table[index];
while ( entry != null ) {
if ( matches( entry, tuple, hashCode, factHandle ) ) {
return entry;
}
entry = entry.getNext();
}
return null;
}
private boolean matches(TupleList tupleList, Tuple tuple, int tupleHashCode, InternalFactHandle factHandle) {
if ( tupleList.hashCode() != tupleHashCode ) {
return false;
}
if ( tupleList.getFirst().getFactHandle() == factHandle ) {
Tuple rightTuple = tupleList.getFirst().getNext();
if ( rightTuple != null ) {
return this.index.equal( rightTuple.getFactHandle().getObject(),
tuple );
}
}
return this.index.equal( tupleList, tuple );
}
}