/*
* 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.util;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import org.drools.Cheese;
import org.drools.base.ClassFieldAccessorCache;
import org.drools.base.ClassFieldAccessorStore;
import org.drools.base.ClassObjectType;
import org.drools.base.ValueType;
import org.drools.base.evaluators.EqualityEvaluatorsDefinition;
import org.drools.base.evaluators.Operator;
import org.drools.common.DefaultFactHandle;
import org.drools.common.InternalFactHandle;
import org.drools.core.util.AbstractHashTable;
import org.drools.core.util.Entry;
import org.drools.core.util.RightTupleIndexHashTable;
import org.drools.core.util.RightTupleList;
import org.drools.core.util.AbstractHashTable.FieldIndex;
import org.drools.reteoo.LeftTupleImpl;
import org.drools.reteoo.RightTuple;
import org.drools.rule.Declaration;
import org.drools.rule.Pattern;
import org.drools.spi.InternalReadAccessor;
public class RightTupleIndexHashTableTest {
EqualityEvaluatorsDefinition equals = new EqualityEvaluatorsDefinition();
ClassFieldAccessorStore store = new ClassFieldAccessorStore();
@Before
public void setUp() throws Exception {
store.setClassFieldAccessorCache( new ClassFieldAccessorCache( Thread.currentThread().getContextClassLoader() ) );
store.setEagerWire( true );
}
@Test
public void testSingleEntry() throws Exception {
final InternalReadAccessor extractor = store.getReader( Cheese.class,
"type",
getClass().getClassLoader() );
final Pattern pattern = new Pattern( 0,
new ClassObjectType( Cheese.class ) );
final Declaration declaration = new Declaration( "typeOfCheese",
extractor,
pattern );
final FieldIndex fieldIndex = new FieldIndex( extractor,
declaration,
equals.getEvaluator( ValueType.STRING_TYPE,
Operator.EQUAL ) );
final RightTupleIndexHashTable map = new RightTupleIndexHashTable( new FieldIndex[]{fieldIndex} );
final Cheese cheddar = new Cheese( "cheddar",
10 );
final InternalFactHandle cheddarHandle1 = new DefaultFactHandle( 0,
cheddar );
assertEquals( 0,
map.size() );
assertNull( map.get( new LeftTupleImpl( cheddarHandle1,
null,
true ), cheddarHandle1 ) );
final Cheese stilton1 = new Cheese( "stilton",
35 );
RightTuple stiltonRighTuple = new RightTuple( new DefaultFactHandle( 1,
stilton1 ),
null );
map.add( stiltonRighTuple );
assertEquals( 1,
map.size() );
assertEquals( 1,
tablePopulationSize( map ) );
final Cheese stilton2 = new Cheese( "stilton",
80 );
final InternalFactHandle stiltonHandle2 = new DefaultFactHandle( 2,
stilton2 );
final RightTupleList list = map.get( new LeftTupleImpl( stiltonHandle2,
null,
true ), stiltonHandle2 );
assertSame( stiltonRighTuple.getFactHandle(),
list.first.getFactHandle() );
assertNull( list.first.getNext() );
}
@Test
public void testTwoDifferentEntries() throws Exception {
final InternalReadAccessor extractor = store.getReader( Cheese.class,
"type",
getClass().getClassLoader() );
final Pattern pattern = new Pattern( 0,
new ClassObjectType( Cheese.class ) );
final Declaration declaration = new Declaration( "typeOfCheese",
extractor,
pattern );
final FieldIndex fieldIndex = new FieldIndex( extractor,
declaration,
equals.getEvaluator( ValueType.STRING_TYPE,
Operator.EQUAL ) );
final RightTupleIndexHashTable map = new RightTupleIndexHashTable( new FieldIndex[]{fieldIndex} );
assertEquals( 0,
map.size() );
final Cheese stilton1 = new Cheese( "stilton",
35 );
final InternalFactHandle stiltonHandle1 = new DefaultFactHandle( 1,
stilton1 );
map.add( new RightTuple( stiltonHandle1,
null ) );
final Cheese cheddar1 = new Cheese( "cheddar",
35 );
final InternalFactHandle cheddarHandle1 = new DefaultFactHandle( 2,
cheddar1 );
map.add( new RightTuple( cheddarHandle1,
null ) );
assertEquals( 2,
map.size() );
assertEquals( 2,
tablePopulationSize( map ) );
final Cheese stilton2 = new Cheese( "stilton",
77 );
final InternalFactHandle stiltonHandle2 = new DefaultFactHandle( 2,
stilton2 );
RightTupleList list = map.get( new LeftTupleImpl( stiltonHandle2,
null,
true ),stiltonHandle2 );
assertSame( stiltonHandle1,
list.first.getFactHandle() );
assertNull( list.first.getNext() );
final Cheese cheddar2 = new Cheese( "cheddar",
5 );
final InternalFactHandle cheddarHandle2 = new DefaultFactHandle( 2,
cheddar2 );
list = map.get( new LeftTupleImpl( cheddarHandle2,
null,
true ), cheddarHandle2 );
assertSame( cheddarHandle1,
list.first.getFactHandle() );
assertNull( list.first.getNext() );
}
@Test
public void testTwoEqualEntries() throws Exception {
final InternalReadAccessor extractor = store.getReader( Cheese.class,
"type",
getClass().getClassLoader() );
final Pattern pattern = new Pattern( 0,
new ClassObjectType( Cheese.class ) );
final Declaration declaration = new Declaration( "typeOfCheese",
extractor,
pattern );
final FieldIndex fieldIndex = new FieldIndex( extractor,
declaration,
equals.getEvaluator( ValueType.STRING_TYPE,
Operator.EQUAL ) );
final RightTupleIndexHashTable map = new RightTupleIndexHashTable( new FieldIndex[]{fieldIndex} );
assertEquals( 0,
map.size() );
final Cheese stilton1 = new Cheese( "stilton",
35 );
final InternalFactHandle stiltonHandle1 = new DefaultFactHandle( 1,
stilton1 );
map.add( new RightTuple( stiltonHandle1,
null ) );
final Cheese cheddar1 = new Cheese( "cheddar",
35 );
final InternalFactHandle cheddarHandle1 = new DefaultFactHandle( 2,
cheddar1 );
map.add( new RightTuple( cheddarHandle1,
null ) );
final Cheese stilton2 = new Cheese( "stilton",
81 );
final InternalFactHandle stiltonHandle2 = new DefaultFactHandle( 3,
stilton2 );
map.add( new RightTuple( stiltonHandle2,
null ) );
assertEquals( 3,
map.size() );
assertEquals( 2,
tablePopulationSize( map ) );
// Check they are correctly chained to the same FieldIndexEntry
final Cheese stilton3 = new Cheese( "stilton",
89 );
final InternalFactHandle stiltonHandle3 = new DefaultFactHandle( 4,
stilton2 );
final RightTupleList list = map.get( new LeftTupleImpl( stiltonHandle3,
null,
true ), stiltonHandle3 );
assertSame( stiltonHandle1,
list.first.getFactHandle() );
assertSame( stiltonHandle2,
((RightTuple) list.first.getNext()).getFactHandle() );
}
@Test
public void testTwoDifferentEntriesSameHashCode() throws Exception {
final InternalReadAccessor extractor = store.getReader( TestClass.class,
"object",
getClass().getClassLoader() );
final Pattern pattern = new Pattern( 0,
new ClassObjectType( TestClass.class ) );
final Declaration declaration = new Declaration( "theObject",
extractor,
pattern );
final FieldIndex fieldIndex = new FieldIndex( extractor,
declaration,
equals.getEvaluator( ValueType.OBJECT_TYPE,
Operator.EQUAL ) );
final RightTupleIndexHashTable map = new RightTupleIndexHashTable( new FieldIndex[]{fieldIndex} );
final TestClass c1 = new TestClass( 0,
new TestClass( 20,
"stilton" ) );
final InternalFactHandle ch1 = new DefaultFactHandle( 1,
c1 );
map.add( new RightTuple( ch1,
null ) );
final TestClass c2 = new TestClass( 0,
new TestClass( 20,
"cheddar" ) );
final InternalFactHandle ch2 = new DefaultFactHandle( 2,
c2 );
map.add( new RightTuple( ch2,
null ) );
// same hashcode, but different values, so it should result in a size of 2
assertEquals( 2,
map.size() );
// however both are in the same table bucket
assertEquals( 1,
tablePopulationSize( map ) );
// this table bucket will have two FieldIndexEntries, as they are actually two different values
Entry[] entries = getEntries( map );
assertEquals( 1,
entries.length );
RightTupleList list = (RightTupleList) entries[0];
assertSame( ch2,
list.first.getFactHandle() );
assertNull( list.first.getNext() );
assertSame( ch1,
((RightTupleList) list.next).first.getFactHandle() );
assertNull( ((RightTupleList) list.next).first.getNext() );
assertNull( ((RightTupleList) list.next).next );
}
@Test
public void testRemove() throws Exception {
final InternalReadAccessor extractor = store.getReader( Cheese.class,
"type",
getClass().getClassLoader() );
final Pattern pattern = new Pattern( 0,
new ClassObjectType( Cheese.class ) );
final Declaration declaration = new Declaration( "typeOfCheese",
extractor,
pattern );
final FieldIndex fieldIndex = new FieldIndex( extractor,
declaration,
equals.getEvaluator( ValueType.STRING_TYPE,
Operator.EQUAL ) );
final RightTupleIndexHashTable map = new RightTupleIndexHashTable( new FieldIndex[]{fieldIndex} );
assertEquals( 0,
map.size() );
final Cheese stilton1 = new Cheese( "stilton",
35 );
final InternalFactHandle stiltonHandle1 = new DefaultFactHandle( 1,
stilton1 );
RightTuple stiltonRightTuple1 = new RightTuple( stiltonHandle1,
null );
map.add( stiltonRightTuple1 );
final Cheese cheddar1 = new Cheese( "cheddar",
35 );
final InternalFactHandle cheddarHandle1 = new DefaultFactHandle( 2,
cheddar1 );
RightTuple cheddarRightTuple1 = new RightTuple( cheddarHandle1,
null );
map.add( cheddarRightTuple1 );
final Cheese stilton2 = new Cheese( "stilton",
81 );
final InternalFactHandle stiltonHandle2 = new DefaultFactHandle( 3,
stilton2 );
RightTuple stiltonRightTuple2 = new RightTuple( stiltonHandle2,
null );
map.add( stiltonRightTuple2 );
assertEquals( 3,
map.size() );
assertEquals( 2,
tablePopulationSize( map ) );
// cheddar is in its own bucket, which should be removed once empty. We cannot have
// empty FieldIndexEntries in the Map, as they get their value from the first FactEntry.
map.remove( cheddarRightTuple1 );
assertEquals( 2,
map.size() );
assertEquals( 1,
tablePopulationSize( map ) );
// We remove t he stiltonHandle2, but there is still one more stilton, so size should be the same
map.remove( stiltonRightTuple2 );
assertEquals( 1,
map.size() );
assertEquals( 1,
tablePopulationSize( map ) );
// No more stiltons, so the table should be empty
map.remove( stiltonRightTuple1 );
assertEquals( 0,
map.size() );
assertEquals( 0,
tablePopulationSize( map ) );
}
@Test
public void testResize() throws Exception {
final InternalReadAccessor extractor = store.getReader( Cheese.class,
"type",
getClass().getClassLoader() );
final Pattern pattern = new Pattern( 0,
new ClassObjectType( Cheese.class ) );
final Declaration declaration = new Declaration( "typeOfCheese",
extractor,
pattern );
final FieldIndex fieldIndex = new FieldIndex( extractor,
declaration,
equals.getEvaluator( ValueType.STRING_TYPE,
Operator.EQUAL ) );
final RightTupleIndexHashTable map = new RightTupleIndexHashTable( 16,
0.75f,
new FieldIndex[]{fieldIndex} );
assertEquals( 0,
map.size() );
final Cheese stilton1 = new Cheese( "stilton",
35 );
map.add( newRightTuple( 1,
stilton1 ) );
final Cheese stilton2 = new Cheese( "stilton",
81 );
map.add( newRightTuple( 2,
stilton2 ) );
final Cheese cheddar1 = new Cheese( "cheddar",
35 );
map.add( newRightTuple( 3,
cheddar1 ) );
final Cheese cheddar2 = new Cheese( "cheddar",
38 );
map.add( newRightTuple( 4,
cheddar2 ) );
final Cheese brie = new Cheese( "brie",
293 );
map.add( newRightTuple( 5,
brie ) );
final Cheese mozerella = new Cheese( "mozerella",
15 );
map.add( newRightTuple( 6,
mozerella ) );
final Cheese dolcelatte = new Cheese( "dolcelatte",
284 );
map.add( newRightTuple( 7,
dolcelatte ) );
final Cheese camembert1 = new Cheese( "camembert",
924 );
map.add( newRightTuple( 8,
camembert1 ) );
final Cheese camembert2 = new Cheese( "camembert",
765 );
map.add( newRightTuple( 9,
camembert2 ) );
final Cheese redLeicestor = new Cheese( "red leicestor",
23 );
map.add( newRightTuple( 10,
redLeicestor ) );
final Cheese wensleydale = new Cheese( "wensleydale",
20 );
map.add( newRightTuple( 11,
wensleydale ) );
final Cheese edam = new Cheese( "edam",
12 );
map.add( newRightTuple( 12,
edam ) );
final Cheese goude1 = new Cheese( "goude",
93 );
map.add( newRightTuple( 13,
goude1 ) );
final Cheese goude2 = new Cheese( "goude",
88 );
map.add( newRightTuple( 14,
goude2 ) );
final Cheese gruyere = new Cheese( "gruyere",
82 );
map.add( newRightTuple( 15,
gruyere ) );
final Cheese emmental = new Cheese( "emmental",
98 );
map.add( newRightTuple( 16,
emmental ) );
// At this point we have 16 facts but only 12 different types of cheeses
// so no table resize and thus its size is 16
assertEquals( 16,
map.size() );
Entry[] table = map.getTable();
assertEquals( 16,
table.length );
final Cheese feta = new Cheese( "feta",
48 );
map.add( newRightTuple( 2,
feta ) );
// This adds our 13th type of cheese. The map is set with an initial capacity of 16 and
// a threshold of 75%, that after 12 it should resize the map to 32.
assertEquals( 17,
map.size() );
table = map.getTable();
assertEquals( 32,
table.length );
final Cheese haloumi = new Cheese( "haloumi",
48 );
map.add( newRightTuple( 2,
haloumi ) );
final Cheese chevre = new Cheese( "chevre",
48 );
map.add( newRightTuple( 2,
chevre ) );
}
private RightTuple newRightTuple(int id,
Object object) {
return new RightTuple( new DefaultFactHandle( id,
object ),
null );
}
public static class TestClass {
private int hashCode;
private Object object;
public TestClass() {
}
public TestClass(final int hashCode,
final Object object) {
this.hashCode = hashCode;
this.object = object;
}
public Object getObject() {
return this.object;
}
public void setObject(final Object object) {
this.object = object;
}
public void setHashCode(final int hashCode) {
this.hashCode = hashCode;
}
public int hashCode() {
return this.hashCode;
}
public boolean equals(final Object obj) {
if ( this == obj ) {
return true;
}
if ( obj == null ) {
return false;
}
if ( getClass() != obj.getClass() ) {
return false;
}
final TestClass other = (TestClass) obj;
if ( this.object == null ) {
if ( other.object != null ) {
return false;
}
} else if ( !this.object.equals( other.object ) ) {
return false;
}
return true;
}
}
private int tablePopulationSize(final AbstractHashTable map) throws Exception {
final Field field = AbstractHashTable.class.getDeclaredField( "table" );
field.setAccessible( true );
final Entry[] array = (Entry[]) field.get( map );
int size = 0;
for ( int i = 0, length = array.length; i < length; i++ ) {
if ( array[i] != null ) {
size++;
}
}
return size;
}
private Entry[] getEntries(final AbstractHashTable map) throws Exception {
final Field field = AbstractHashTable.class.getDeclaredField( "table" );
field.setAccessible( true );
final List list = new ArrayList();
final Entry[] array = (Entry[]) field.get( map );
for ( int i = 0, length = array.length; i < length; i++ ) {
if ( array[i] != null ) {
list.add( array[i] );
}
}
return (Entry[]) list.toArray( new Entry[list.size()] );
}
@Test
public void testEmptyIterator() {
final InternalReadAccessor extractor = store.getReader( Cheese.class,
"type",
getClass().getClassLoader() );
final Pattern pattern = new Pattern( 0,
new ClassObjectType( Cheese.class ) );
final Declaration declaration = new Declaration( "typeOfCheese",
extractor,
pattern );
final FieldIndex fieldIndex = new FieldIndex( extractor,
declaration,
equals.getEvaluator( ValueType.STRING_TYPE,
Operator.EQUAL ) );
final RightTupleIndexHashTable map = new RightTupleIndexHashTable( new FieldIndex[]{fieldIndex} );
final Cheese stilton = new Cheese( "stilton",
55 );
final InternalFactHandle stiltonHandle = new DefaultFactHandle( 2,
stilton );
assertNull( map.getFirst( new LeftTupleImpl( stiltonHandle,
null,
true ), stiltonHandle ) );
}
}