/** * Copyright 2010 JBoss Inc * * 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.drools.core.util; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import org.drools.reteoo.LeftTuple; import org.drools.reteoo.LeftTupleMemory; import org.drools.reteoo.RightTuple; public class LeftTupleIndexHashTable extends AbstractHashTable implements LeftTupleMemory { private static final long serialVersionUID = 510l; public static final int PRIME = 31; private int startResult; private transient FieldIndexHashTableFullIterator tupleValueFullIterator; private int factSize; private Index index; public LeftTupleIndexHashTable() { // constructor for serialisation } public LeftTupleIndexHashTable(final FieldIndex[] index) { this( 128, 0.75f, index ); } public LeftTupleIndexHashTable(final int capacity, final float loadFactor, final FieldIndex[] index) { super( capacity, loadFactor ); this.startResult = LeftTupleIndexHashTable.PRIME; for ( int i = 0, length = index.length; i < length; i++ ) { this.startResult += LeftTupleIndexHashTable.PRIME * this.startResult + index[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(); } public void writeExternal(ObjectOutput out) throws IOException { super.writeExternal( out ); out.writeInt( startResult ); out.writeInt( factSize ); out.writeObject( index ); } public Iterator iterator() { if ( this.tupleValueFullIterator == null ) { this.tupleValueFullIterator = new FieldIndexHashTableFullIterator( this ); } else { this.tupleValueFullIterator.reset(); } return this.tupleValueFullIterator; } public LeftTuple getFirst(final RightTuple rightTuple) { LeftTupleList bucket = get( rightTuple ); if ( bucket != null ) { return bucket.getFirst( (LeftTuple) null ); } else { return null; } } public LeftTuple getFirst(final LeftTuple leftTuple) { final LeftTupleList bucket = get( leftTuple ); if ( bucket != null ) { return bucket.getFirst( (LeftTuple) null ); } else { return null; } } public boolean isIndexed() { return true; } public Index getIndex() { return this.index; } public Entry getBucket(final Object object) { final int hashCode = this.index.hashCodeOf( object ); final int index = indexOf( hashCode, this.table.length ); return this.table[index]; } public static class FieldIndexHashTableFullIterator implements Iterator { private AbstractHashTable hashTable; private Entry[] table; private int row; private int length; private LeftTupleList list; private LeftTuple leftTuple; public FieldIndexHashTableFullIterator(final AbstractHashTable hashTable) { this.hashTable = hashTable; reset(); } /* (non-Javadoc) * @see org.drools.util.Iterator#next() */ public Object next() { while ( this.row < this.length ) { // check if there is a current bucket while ( this.list == null ) { // iterate while there is no current bucket, trying each array position this.list = (LeftTupleList) this.table[this.row]; this.row++; if ( this.list != null ) { // we have a bucket so assign the first LeftTuple and return this.leftTuple = (LeftTuple) this.list.getFirst( (LeftTuple) null ); return this.leftTuple; } else if ( this.row >= this.length ) { // we've scanned the whole table and nothing is left, so return null return null; } } this.leftTuple = (LeftTuple) this.leftTuple.getNext(); if ( this.leftTuple != null ) { // we have a next tuple so return return this.leftTuple; } else { this.list = (LeftTupleList) 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.leftTuple = (LeftTuple) this.list.getFirst( (LeftTuple) null ); return this.leftTuple; } } } return null; } public void remove() { throw new UnsupportedOperationException( "FieldIndexHashTableFullIterator does not support remove()." ); } /* (non-Javadoc) * @see org.drools.util.Iterator#reset() */ public void reset() { this.table = this.hashTable.getTable(); this.length = this.table.length; this.row = 0; this.list = null; this.leftTuple = null; } } public LeftTuple[] toArray() { LeftTuple[] result = new LeftTuple[this.factSize]; int index = 0; for ( int i = 0; i < this.table.length; i++ ) { LeftTupleList bucket = (LeftTupleList) this.table[i]; while ( bucket != null ) { LeftTuple entry = (LeftTuple) bucket.getFirst( (LeftTuple) null ); while ( entry != null ) { result[index++] = entry; entry = (LeftTuple) entry.getNext(); } bucket = (LeftTupleList) bucket.getNext(); } } return result; } public void add(final LeftTuple tuple) { final LeftTupleList entry = getOrCreate( tuple ); tuple.setMemory( entry ); entry.add( tuple ); this.factSize++; } public void remove(final LeftTuple leftTuple) { if ( leftTuple.getMemory() != null ) { LeftTupleList memory = leftTuple.getMemory(); memory.remove( leftTuple ); this.factSize--; if ( memory.first == null ) { final int index = indexOf( memory.hashCode(), this.table.length ); LeftTupleList previous = null; LeftTupleList current = (LeftTupleList) this.table[index]; while ( current != memory ) { previous = current; current = (LeftTupleList) current.getNext(); } if ( previous != null ) { previous.next = current.next; } else { this.table[index] = current.next; } this.size--; } return; } final int hashCode = this.index.hashCodeOf( leftTuple ); final int index = indexOf( hashCode, this.table.length ); // search the table for the Entry, we need to track previous, so if the Entry // is empty we can remove it. LeftTupleList previous = null; LeftTupleList current = (LeftTupleList) this.table[index]; while ( current != null ) { if ( current.matches( leftTuple, hashCode ) ) { current.remove( leftTuple ); this.factSize--; if ( current.first == null ) { if ( previous != null ) { previous.next = current.next; } else { this.table[index] = current.next; } this.size--; } break; } previous = current; current = (LeftTupleList) current.next; } leftTuple.setNext( null ); leftTuple.setPrevious( null ); } public boolean contains(final LeftTuple tuple) { final int hashCode = this.index.hashCodeOf( tuple ); final int index = indexOf( hashCode, this.table.length ); LeftTupleList current = (LeftTupleList) this.table[index]; while ( current != null ) { if ( current.matches( tuple, hashCode ) ) { return true; } current = (LeftTupleList) current.next; } return false; } public LeftTupleList get(final RightTuple rightTuple) { final Object object = rightTuple.getFactHandle().getObject(); final int hashCode = this.index.hashCodeOf( object ); final int index = indexOf( hashCode, this.table.length ); LeftTupleList entry = (LeftTupleList) this.table[index]; while ( entry != null ) { if ( entry.matches( object, hashCode ) ) { return entry; } entry = (LeftTupleList) entry.getNext(); } return entry; } /** * 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. * * @param value * @return */ private LeftTupleList getOrCreate(final LeftTuple tuple) { final int hashCode = this.index.hashCodeOf( tuple ); final int index = indexOf( hashCode, this.table.length ); LeftTupleList entry = (LeftTupleList) this.table[index]; // search to find an existing entry while ( entry != null ) { if ( entry.matches( tuple, hashCode ) ) { return entry; } entry = (LeftTupleList) entry.next; } // entry does not exist, so create if ( entry == null ) { entry = new LeftTupleList( this.index, hashCode ); entry.next = this.table[index]; this.table[index] = entry; if ( this.size++ >= this.threshold ) { resize( 2 * this.table.length ); } } return entry; } private LeftTupleList get(final LeftTuple tuple) { final int hashCode = this.index.hashCodeOf( tuple ); final int index = indexOf( hashCode, this.table.length ); LeftTupleList entry = (LeftTupleList) this.table[index]; // search to find an existing entry while ( entry != null ) { if ( entry.matches( tuple, hashCode ) ) { return entry; } entry = (LeftTupleList) entry.next; } return entry; } public int size() { return this.factSize; } public String toString() { StringBuilder builder = new StringBuilder(); Iterator it = iterator(); for ( LeftTuple leftTuple = (LeftTuple) it.next(); leftTuple != null; leftTuple = (LeftTuple) it.next() ) { builder.append( leftTuple + "\n" ); } return builder.toString(); } public void clear() { LeftTuple leftTuple = null; while((leftTuple = (LeftTuple) iterator().next()) != null) { remove(leftTuple); } } }