A MultiViewMap is a generalization of the VarSrcTokTable in OoOJava. It is cumbersom...
authorjjenista <jjenista>
Wed, 21 Sep 2011 21:08:35 +0000 (21:08 +0000)
committerjjenista <jjenista>
Wed, 21 Sep 2011 21:08:35 +0000 (21:08 +0000)
Robust/src/Util/JoinOp.java [new file with mode: 0644]
Robust/src/Util/JoinOpInteger.java [new file with mode: 0644]
Robust/src/Util/MultiKey.java [new file with mode: 0644]
Robust/src/Util/MultiViewMap.java [new file with mode: 0644]
Robust/src/Util/MultiViewMapBuilder.java [new file with mode: 0644]
Robust/src/Util/UnitTests/MultiViewMapTest.java [new file with mode: 0644]
Robust/src/Util/UnitTests/makefile [new file with mode: 0644]

diff --git a/Robust/src/Util/JoinOp.java b/Robust/src/Util/JoinOp.java
new file mode 100644 (file)
index 0000000..360347c
--- /dev/null
@@ -0,0 +1,20 @@
+///////////////////////////////////////////
+//
+//  Just an interface for specifying
+//  how to join two instances of a
+//  type.  This is useful within a
+//  generic structure, like MultiViewMap.
+//
+///////////////////////////////////////////
+package Util;
+
+public interface JoinOp<T> {
+
+  ////////////////////////////////////////
+  //
+  //  join() should accept null values for
+  //  the arguments!
+  //
+  ////////////////////////////////////////
+  T join( T a, T b );
+}
diff --git a/Robust/src/Util/JoinOpInteger.java b/Robust/src/Util/JoinOpInteger.java
new file mode 100644 (file)
index 0000000..3cf03d5
--- /dev/null
@@ -0,0 +1,15 @@
+package Util;
+
+public class JoinOpInteger implements JoinOp<Integer> {
+  public Integer join( Integer a, Integer b ) {
+    int aVal = 0;
+    if( a != null ) {
+      aVal = a;
+    }
+    int bVal = 0;
+    if( b != null ) {
+      bVal = b;
+    }
+    return aVal + bVal;
+  }
+}
diff --git a/Robust/src/Util/MultiKey.java b/Robust/src/Util/MultiKey.java
new file mode 100644 (file)
index 0000000..16fd999
--- /dev/null
@@ -0,0 +1,94 @@
+////////////////////////////////////////////
+//
+//  A MultiKey is a vector of Objects that
+//  serves as a key.  One MultiKey is equal
+//  to another if they have the same number
+//  of Objects, and Objects in corresponding
+//  positions are equal.
+//
+////////////////////////////////////////////
+package Util;
+
+
+public class MultiKey {
+
+  static public MultiKey factory( Object... keys ) {
+    return new MultiKey( keys );
+  }
+
+
+  private Object[] keys;
+
+  public MultiKey( Object[] keys ) {
+    if( keys.length == 0 ) {
+      throw new IllegalArgumentException( "A MultiKey must have at least one key." );
+    }
+    for( int i = 0; i < keys.length; ++i ) {
+      if( keys[i] == null ) {
+        throw new IllegalArgumentException( "A MultiKey cannot have null elements." );
+      }
+    }
+    this.keys = keys.clone();
+  }
+
+  public boolean typesMatch( Class[] keyTypes ) {
+    if( keys.length != keyTypes.length ) {
+      return false;
+    }
+    for( int i = 0; i < keys.length; ++i ) {
+      if( !(keyTypes[i].isInstance( keys[i] )) ) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  public Object get( int index ) {
+    if( index < 0 || index >= keys.length ) {
+      throw new IllegalArgumentException( "Index "+index+" is out of range for this MultiKey." );
+    }
+    return keys[index];
+  }
+
+  public boolean equals( Object o ) {
+    if( this == o ) {
+      return true;
+    }
+    if( o == null ) {
+      return false;
+    }
+    if( !(o instanceof MultiKey) ) {
+      return false;
+    }
+    MultiKey mk = (MultiKey) o;
+    if( this.keys.length != mk.keys.length ) {
+      return false;
+    }
+    for( int i = 0; i < keys.length; ++i ) {
+      if( !this.keys[i].equals( mk.keys[i] ) ) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  public int hashCode() {
+    int hash = 1;
+    for( Object key : keys ) {
+      hash = hash*31 + key.hashCode();
+    }
+    return hash;
+  }
+
+  public String toString() {
+    StringBuilder s = new StringBuilder( "MK[" );
+    for( int i = 0; i < keys.length; ++i ) {
+      s.append( keys[i].toString() );
+      if( i < keys.length - 1 ) {
+        s.append( ", " );
+      }
+    }
+    s.append( "]" );
+    return s.toString();
+  }
+}
diff --git a/Robust/src/Util/MultiViewMap.java b/Robust/src/Util/MultiViewMap.java
new file mode 100644 (file)
index 0000000..2cf626d
--- /dev/null
@@ -0,0 +1,288 @@
+///////////////////////////////////////////////////////////\r
+//\r
+//  MultiViewMap is like a straight-forward multiple-key\r
+//  map except that it supports retrieval and delete by\r
+//  subset views of the multikey.\r
+//\r
+//  Ex:\r
+//  mvm.put(<X, Y, Z>, V);\r
+//  mvm.put(<X, W, T>, U);\r
+//  print( mvm.get(<*, Y, *>) );  // prints "<X, Y, Z> -> V"\r
+//  mvm.remove(<X, *, *>);        // removes both entries\r
+//\r
+///////////////////////////////////////////////////////////\r
+package Util;\r
+\r
+import java.util.Set;\r
+import java.util.HashSet;\r
+import java.util.BitSet;\r
+import java.util.Vector;\r
+import java.util.Map;\r
+import java.util.HashMap;\r
+\r
+\r
+public class MultiViewMap<T> {\r
+\r
+  private Class[]        keyTypes;\r
+  private Vector<BitSet> partialViews;\r
+  private BitSet         fullView;\r
+\r
+  private JoinOp<T> joinOp;\r
+\r
+  private boolean checkTypes;\r
+  private boolean checkConsistency;\r
+\r
+  //  If the entire contents of this map are fullKey -> value:\r
+  //    <a,b> -> 1\r
+  //    <c,b> -> 2\r
+  //    <d,e> -> 3\r
+  private Map<MultiKey, T> fullKey2value;\r
+\r
+  //  ...then this structure captures the partial views:\r
+  //     view[1, 0] ->\r
+  //       <a,*> -> {<a,b>}\r
+  //       <c,*> -> {<c,b>}\r
+  //       <d,*> -> {<d,e>}\r
+  //     view[0, 1] ->\r
+  //       <*,b> -> {<a,b>, <c,b>}\r
+  //       <*,e> -> {<d,e>}\r
+  private Map<BitSet, Map<MultiKey, Set<MultiKey>>>\r
+    view2partialKey2fullKeys;\r
+\r
+\r
+  //  NOTE!  Use MultiViewMapBuilder to get an\r
+  //  instantiation of this class.\r
+  protected MultiViewMap( Class[]        keyTypes,\r
+                          JoinOp<T>      joinOp,\r
+                          BitSet         fullView,\r
+                          Vector<BitSet> partialViews,\r
+                          boolean        checkTypes,\r
+                          boolean        checkConsistency ) {\r
+\r
+    this.keyTypes         = keyTypes;\r
+    this.joinOp           = joinOp;\r
+    this.partialViews     = partialViews;\r
+    this.fullView         = fullView;\r
+    this.checkTypes       = checkTypes;\r
+    this.checkConsistency = checkConsistency;\r
+\r
+    fullKey2value = new HashMap<MultiKey, T>();\r
+\r
+    view2partialKey2fullKeys = \r
+      new HashMap<BitSet, Map<MultiKey, Set<MultiKey>>>(); \r
+  }\r
+\r
+\r
+  public int size() {\r
+    return fullKey2value.size();\r
+  }\r
+\r
\r
+  public void put( MultiKey fullKey, T value ) {\r
+    assert( typesMatch( fullKey ) );\r
+\r
+    fullKey2value.put( fullKey, value );\r
+\r
+    for( BitSet view : partialViews ) {\r
+      MultiKey partialKey = makePartialKey( view, fullKey );\r
+      getFullKeys( view, partialKey ).add( fullKey );\r
+    }\r
+\r
+    assert( isConsistent() );\r
+  }\r
+\r
+\r
+  public Map<MultiKey, T> get( final BitSet view, MultiKey partialKey ) {\r
+    checkView( view );\r
+\r
+    Map<MultiKey, T> fullKey2valueBYVIEW = new HashMap<MultiKey, T>();\r
+\r
+    Set<MultiKey> fullKeys = getFullKeys( view, partialKey );\r
+    for( MultiKey fullKey : fullKeys ) {\r
+      fullKey2valueBYVIEW.put( fullKey, \r
+                               fullKey2value.get( fullKey ) );\r
+    }\r
+\r
+    return fullKey2valueBYVIEW;\r
+  }\r
+\r
\r
+  public void remove( final BitSet viewForRemove, MultiKey fullOrPartialKey ) {    \r
+    checkView( viewForRemove );\r
+\r
+    Set<MultiKey> fullKeysToRemove = new HashSet<MultiKey>();\r
+    \r
+    if( viewForRemove.equals( fullView ) ) {\r
+      fullKeysToRemove.add( fullOrPartialKey );\r
+    } else {\r
+      fullKeysToRemove.addAll( getFullKeys( viewForRemove, fullOrPartialKey ) );\r
+    }\r
+\r
+    for( MultiKey fullKeyToRemove : fullKeysToRemove ) {\r
+      for( BitSet view : partialViews ) {\r
+        MultiKey partialKey = makePartialKey( view, fullKeyToRemove );\r
+        getFullKeys( view, partialKey ).remove( fullKeyToRemove );\r
+      }\r
+      fullKey2value.remove( fullKeyToRemove );\r
+    }\r
+\r
+    assert( isConsistent() );\r
+  }\r
+  \r
+\r
+  public void merge( MultiViewMap<T> in ) {\r
+    assert( isHomogenous( in ) );\r
+\r
+    Set<MultiKey> mergedFullKeys = new HashSet<MultiKey>();\r
+    mergedFullKeys.addAll( this.fullKey2value.keySet() );\r
+    mergedFullKeys.addAll( in.fullKey2value.keySet() );\r
+\r
+    for( MultiKey fullKey : mergedFullKeys ) { \r
+      fullKey2value.put( fullKey, \r
+                         joinOp.join( this.fullKey2value.get( fullKey ), \r
+                                      in.fullKey2value.get( fullKey )\r
+                                      ) );\r
+    }\r
+\r
+    for( MultiKey fullKey : in.fullKey2value.keySet() ) { \r
+      for( BitSet view : partialViews ) {\r
+        MultiKey partialKey = makePartialKey( view, fullKey );\r
+        getFullKeys( view, partialKey ).add( fullKey );\r
+      }\r
+    }\r
+\r
+    assert( isConsistent() );\r
+  }\r
+\r
+\r
+  private \r
+    Set<MultiKey> getFullKeys( BitSet   view,\r
+                               MultiKey partialKey ) {\r
+\r
+    Map<MultiKey, Set<MultiKey>> partialKey2fullKeys =\r
+      getPartialKey2fullKeys( view );\r
+    return getFullKeys( partialKey2fullKeys, partialKey );\r
+  }\r
+\r
+\r
+  private \r
+    Map<MultiKey, Set<MultiKey>> getPartialKey2fullKeys( BitSet view ) {\r
+\r
+    Map<MultiKey, Set<MultiKey>> partialKey2fullKeys =\r
+      view2partialKey2fullKeys.get( view );\r
+    if( partialKey2fullKeys == null ) {\r
+      partialKey2fullKeys = new HashMap<MultiKey, Set<MultiKey>>();\r
+      view2partialKey2fullKeys.put( view, partialKey2fullKeys );\r
+    }\r
+    return partialKey2fullKeys;\r
+  }\r
+\r
+\r
+  private \r
+    Set<MultiKey> getFullKeys( Map<MultiKey, Set<MultiKey>> partialKey2fullKeys,\r
+                               MultiKey                     partialKey ) {\r
+    \r
+    Set<MultiKey> fullKeys = partialKey2fullKeys.get( partialKey );\r
+    if( fullKeys == null ) {\r
+      fullKeys = new HashSet<MultiKey>();\r
+      partialKey2fullKeys.put( partialKey, fullKeys );\r
+    }    \r
+    return fullKeys;\r
+  }\r
+\r
+\r
+  private MultiKey makePartialKey( BitSet view, MultiKey fullKey ) {\r
+    Object[] partialKeys = new Object[view.cardinality()];\r
+    int j = 0;\r
+    for( int i = 0; i < view.size(); ++i ) {\r
+      if( view.get( i ) ) {\r
+        partialKeys[j] = fullKey.get( i );\r
+        ++j;\r
+      }\r
+    }\r
+    assert( j == view.cardinality() );\r
+    return new MultiKey( partialKeys );\r
+  }\r
+\r
+\r
+  private void checkView( BitSet view ) {\r
+    if( view != fullView &&\r
+        !partialViews.contains( view ) ) {\r
+      throw new IllegalArgumentException( "This view is not supported." );\r
+    }\r
+  }\r
+\r
+\r
+  private boolean typesMatch( MultiKey multiKey ) {\r
+    if( !checkTypes ) {\r
+      return true;\r
+    }\r
+\r
+    return multiKey.typesMatch( keyTypes );\r
+  }\r
+\r
+\r
+  private boolean isHomogenous( MultiViewMap in ) {\r
+    if( this.keyTypes.length != in.keyTypes.length ) {\r
+      return false;\r
+    }\r
+    for( int i = 0; i < this.keyTypes.length; ++i ) {\r
+      if( !this.keyTypes[i].equals( in.keyTypes[i] ) ) {\r
+        return false;\r
+      }\r
+    }\r
+    return \r
+      this.partialViews.equals( in.partialViews ) &&\r
+      this.fullView.equals( in.fullView );\r
+  }\r
+\r
+\r
+  private boolean isConsistent() {\r
+    if( !checkConsistency ) {\r
+      return true;\r
+    }\r
+\r
+    // First, for every full key, make sure it is in every partial key\r
+    // set it should be in.\r
+    for( MultiKey fullKey : fullKey2value.keySet() ) {\r
+      for( BitSet view : partialViews ) {\r
+        MultiKey partialKey = makePartialKey( view, fullKey );\r
+        if( !getFullKeys( view, partialKey ).contains( fullKey ) ) {\r
+          System.out.println( "Expected full key="+fullKey+\r
+                              " to be in view="+view+\r
+                              " and partial key="+partialKey+\r
+                              " but it is missing." );\r
+          return false;\r
+        }\r
+      }\r
+    }\r
+\r
+    // Second, for each partial key set, make sure every full key is\r
+    //   a) a match for the partial key it is filed under and\r
+    //   b) also in the full key->value set\r
+    for( BitSet view : partialViews ) {\r
+      Map<MultiKey, Set<MultiKey>> partialKey2fullKeys = getPartialKey2fullKeys( view );\r
+      for( MultiKey partialKey : partialKey2fullKeys.keySet() ) {\r
+        Set<MultiKey> fullKeys = partialKey2fullKeys.get( partialKey );\r
+        for( MultiKey fullKey : fullKeys ) {\r
+          if( !partialKey.equals( makePartialKey( view, fullKey ) ) ) {\r
+            System.out.println( "Full key="+fullKey+\r
+                                " was filed under view="+view+\r
+                                " and partial key="+partialKey+\r
+                                " but the (fullKey, view)->partialKey mapping is inconsistent." );\r
+            return false;\r
+          }\r
+          if( !fullKey2value.containsKey( fullKey ) ) {\r
+            System.out.println( "Full key="+fullKey+\r
+                                " was filed under view="+view+\r
+                                " and partial key="+partialKey+\r
+                                " but the fullKey is not in the fullKey->value map." );\r
+            return false;\r
+          }\r
+        }\r
+      }\r
+    }\r
+\r
+    return true;\r
+  }\r
+}\r
diff --git a/Robust/src/Util/MultiViewMapBuilder.java b/Robust/src/Util/MultiViewMapBuilder.java
new file mode 100644 (file)
index 0000000..8cc74c8
--- /dev/null
@@ -0,0 +1,122 @@
+// This builder does several things:
+//
+//   1) The MultiViewMap constructor is private because
+//      there is a lot of consistency checking to do in
+//      the inputs (like whether a view is specified 
+//      twice) so do all that in a builder and the map
+//      itself can assume valid inputs at the constructor.
+//
+//   2) The inputs to construct a MultiViewMap are tedious
+//      to code, so this builder lets you write the 
+//      specifications succinctly.
+//
+//   3) If you are creating many MultiViewMap's of the same
+//      type and views, it is best to have one builder that
+//      generates each fresh map rather than build up all
+//      the small specification objects again.
+//
+package Util;
+
+import java.util.BitSet;
+import java.util.Vector;
+
+
+public class MultiViewMapBuilder<T> {
+
+  private Class[]        keyTypes;
+  private Vector<BitSet> partialViews;
+  private BitSet         fullView;
+
+  private JoinOp<T> joinOp;
+  
+  private boolean checkTypes;
+  private boolean checkConsistency;
+  
+
+  // define the types and ordering of the multikey
+  public MultiViewMapBuilder( Class[] keyTypes, JoinOp<T> joinOp ) {
+    assert( keyTypes != null );
+    assert( joinOp != null );
+
+    if( keyTypes.length == 0 ) {
+      throw new IllegalArgumentException( "The multikey must have at least one key type." );
+    }
+
+    this.keyTypes         = keyTypes;
+    this.partialViews     = new Vector<BitSet>();
+    this.joinOp           = joinOp;
+    this.checkTypes       = false;
+    this.checkConsistency = false;
+
+    fullView = new BitSet( keyTypes.length );
+    for( int i = 0; i < keyTypes.length; ++i ) {
+      fullView.set( i );
+    }
+  }
+
+
+  public final BitSet addPartialView( Integer... keyIndices ) { 
+    if( keyIndices.length == 0 ) {
+      throw new IllegalArgumentException( "A view must have at least one key index." );
+    }
+
+    // build a view from the arg list
+    BitSet view = new BitSet( keyTypes.length );
+    for( Integer i : keyIndices ) {
+      if( i < 0 || i >= keyTypes.length ) {
+        throw new IllegalArgumentException( "Key index in view is out of bounds." );
+      }
+      if( view.get( i ) ) {
+        throw new IllegalArgumentException( "Key index in view is specified more than once." );
+      }
+      view.set( i );
+    }
+
+    if( keyIndices.length  == keyTypes.length &&
+        view.cardinality() == keyTypes.length ) {
+      throw new IllegalArgumentException( "The full view is always included implicitly." );
+    }
+
+    for( BitSet existingView : partialViews ) {
+      if( view.equals( existingView ) ) {
+        throw new IllegalArgumentException( "View matches an existing view." );
+      }
+    }
+
+    return addPartialView( view );
+  }
+
+
+  public final BitSet addPartialView( BitSet view ) {
+    partialViews.add( view );
+    return (BitSet) view.clone();
+  }
+
+
+  public final BitSet getFullView() {
+    return fullView;
+  }
+
+
+  public void setCheckTypes( boolean checkTypes ) {
+    this.checkTypes = checkTypes;
+  }
+
+
+  public void setCheckConsistency( boolean checkConsistency ) {
+    this.checkConsistency = checkConsistency;
+  }
+
+
+  public MultiViewMap<T> build() {
+    if( partialViews.isEmpty() ) {
+      throw new IllegalArgumentException( "No partial views specified for this builder." );
+    }
+    return new MultiViewMap<T>( keyTypes,
+                                joinOp,
+                                fullView,
+                                partialViews, 
+                                checkTypes, 
+                                checkConsistency );
+  }
+}
diff --git a/Robust/src/Util/UnitTests/MultiViewMapTest.java b/Robust/src/Util/UnitTests/MultiViewMapTest.java
new file mode 100644 (file)
index 0000000..fc4a004
--- /dev/null
@@ -0,0 +1,368 @@
+package Util.UnitTests;
+
+import Util.*;
+
+import java.util.Set;
+import java.util.HashSet;
+import java.util.BitSet;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Random;
+import java.lang.reflect.Array;
+
+
+public class MultiViewMapTest {
+
+  private static JoinOpInteger joinOp;
+
+  private static Random random;
+
+
+  private static void p() { System.out.println( "  passed" ); }
+  private static void f() { System.out.println( "  !!!FAILED!!!" ); }
+
+  private static void verify( Map<MultiKey, Integer> expected, 
+                              Map<MultiKey, Integer> actual ) {
+    if( expected.equals( actual ) ) { 
+      p();
+    } else { 
+      f();
+      System.out.println( "expected = "+expected );
+      System.out.println( "actual   = "+actual );
+    }
+  }
+
+
+  public static void main(String[] args) {
+    
+    int randomSeed = 12345;
+    if( args.length > 0 ) {
+      randomSeed = Integer.parseInt( args[0] );
+    }
+    random = new Random( randomSeed );
+
+    joinOp = new JoinOpInteger();
+    
+    testBuilder();
+    System.out.println("");
+    testMap();
+    System.out.println("");
+    stressTest();
+    System.out.println("");
+  }
+
+
+  public static void testBuilder() {
+    System.out.println( "Testing MultiViewMapBuilder..." );
+
+    MultiViewMapBuilder builder;
+
+    try {
+      builder = new MultiViewMapBuilder<Integer>( new Class[]{}, joinOp );
+      f();
+    } catch( IllegalArgumentException expected ) {
+      p();
+    } catch( Exception e ) {
+      f();
+    }
+
+    try {
+      builder =
+        new MultiViewMapBuilder<Integer>( new Class[] 
+                                          {Integer.class,
+                                           Boolean.class,
+                                           Integer.class,
+                                           String.class},
+                                          joinOp );
+      p();
+    } catch( Exception e ) {
+      f();
+    }
+
+    
+    builder =
+      new MultiViewMapBuilder<Integer>( new Class[] 
+                                        {Integer.class,
+                                         Boolean.class,
+                                         Integer.class,
+                                         String.class},
+                                        joinOp );
+
+    try {
+      // can't build a map with no views yet
+      builder.build();
+      f();
+    } catch( Exception expected ) {
+      p();   
+    }
+
+    try {
+      builder.addPartialView();
+      f();
+    } catch( IllegalArgumentException expected ) {
+      p();   
+    } catch( Exception e ) {
+      f();
+    }
+
+    try {
+      // an index is out of bounds for this multikey (0-3)
+      builder.addPartialView( 1, 6 );
+      f();
+    } catch( IllegalArgumentException expected ) {
+      p();   
+    } catch( Exception e ) {
+      f();
+    }
+
+    try {
+      // an index is out of bounds for this multikey (0-3)
+      builder.addPartialView( 2, -1, 3 );
+      f();
+    } catch( IllegalArgumentException expected ) {
+      p();   
+    } catch( Exception e ) {
+      f();
+    }
+
+    try {
+      // an index is specified more than once
+      builder.addPartialView( 0, 3, 0 );
+      f();
+    } catch( IllegalArgumentException expected ) {
+      p();   
+    } catch( Exception e ) {
+      f();
+    }
+
+    try {
+      // the full view is implicit
+      builder.addPartialView( 0, 1, 2, 3 );
+      f();
+    } catch( IllegalArgumentException expected ) {
+      p();   
+    } catch( Exception e ) {
+      f();
+    }
+
+    try {
+      builder.addPartialView( 1, 3 );
+      p();
+    } catch( Exception e ) {
+      f();
+    }
+
+    try {
+      builder.addPartialView( 0, 1, 3 );
+      p();
+    } catch( Exception e ) {
+      f();
+    }
+
+    try {
+      // duplicate view
+      builder.addPartialView( 1, 3 );
+      f();
+    } catch( IllegalArgumentException expected ) {
+      p();   
+    } catch( Exception e ) {
+      f();
+    }
+    System.out.println( "DONE" );
+  }
+
+
+  public static void testMap() {    
+    System.out.println( "Testing MultiViewMap..." );
+    
+    Map<MultiKey, Integer> expected;
+
+    MultiViewMapBuilder<Integer> builder =
+      new MultiViewMapBuilder<Integer>( new Class[] 
+                                        {Integer.class,   // key0
+                                         Boolean.class,   // key1
+                                         String.class},   // key2
+                                        joinOp );
+    final BitSet view012 = builder.getFullView();
+    final BitSet view01  = builder.addPartialView( 0, 1 );
+    final BitSet view0   = builder.addPartialView( 0 );
+    final BitSet view2   = builder.addPartialView( 2 );
+    builder.setCheckTypes( true );
+    builder.setCheckConsistency( true );
+
+    MultiViewMap<Integer> mapA = builder.build();
+
+
+    // Simple put and remove
+    MultiKey partialKey4 = MultiKey.factory( 4 );
+    expected = new HashMap<MultiKey, Integer>();
+    verify( expected, mapA.get( view0, partialKey4 ) );
+
+    MultiKey vader = MultiKey.factory( 4, true, "Vader" );
+    mapA.put( vader, 1001 );
+    expected = new HashMap<MultiKey, Integer>();
+    expected.put( vader, 1001 );
+    verify( expected, mapA.get( view0, partialKey4 ) );
+
+    mapA.remove( view0, partialKey4 );
+    expected = new HashMap<MultiKey, Integer>();
+    verify( expected, mapA.get( view0, partialKey4 ) );
+
+    
+    // Try across a merge
+    mapA.put( vader, 1001 );
+    expected = new HashMap<MultiKey, Integer>();
+    expected.put( vader, 1001 );
+    verify( expected, mapA.get( view0, partialKey4 ) );
+
+    MultiViewMap<Integer> mapB = builder.build();
+    expected = new HashMap<MultiKey, Integer>();
+    verify( expected, mapB.get( view0, partialKey4 ) );
+
+    mapB.merge( mapA );
+    expected = new HashMap<MultiKey, Integer>();
+    expected.put( vader, 1001 );
+    verify( expected, mapB.get( view0, partialKey4 ) );
+
+    
+    // Remove the correct entries
+    MultiKey luke = MultiKey.factory( 5, true,  "Luke" );
+    MultiKey han  = MultiKey.factory( 4, true,  "Han Solo" );
+    MultiKey r2   = MultiKey.factory( 4, false, "R2-D2" );
+
+    mapA.put( luke, 1002 );
+    mapA.put( han,  1003 );
+    mapA.put( r2,   1004 );
+    expected = new HashMap<MultiKey, Integer>();
+    expected.put( vader, 1001 );
+    expected.put( han,   1003 );
+    expected.put( r2,    1004 );
+    verify( expected, mapA.get( view0, partialKey4 ) );
+
+    // removes vader and han
+    MultiKey partialKey4true = MultiKey.factory( 4, true );
+    mapA.remove( view01, partialKey4true );
+    
+    expected = new HashMap<MultiKey, Integer>();
+    expected.put( r2, 1004 );
+    verify( expected, mapA.get( view0, partialKey4 ) );
+
+    MultiKey partialKeyLuke = MultiKey.factory( "Luke" );
+    expected = new HashMap<MultiKey, Integer>();
+    expected.put( luke, 1002 );
+    verify( expected, mapA.get( view2, partialKeyLuke ) );
+
+
+    System.out.println( "DONE" );
+  }
+
+
+  public static void stressTest() {
+    System.out.println( "Stressing MultiViewMap..." );
+
+    // Just pound away with operations to see if we can crash anything.
+
+    MultiViewMapBuilder<Integer> builder =
+      new MultiViewMapBuilder<Integer>( new Class[] 
+                                        {Integer.class,   // key0
+                                         Boolean.class,   // key1
+                                         String.class},   // key2
+                                        joinOp );
+    builder.setCheckTypes( true );
+    builder.setCheckConsistency( true );
+
+    Integer[] ints = new Integer[] {
+      1, 2, 3, 4, 5, 6, 7,
+    };
+
+    Boolean[] bools = new Boolean[] {
+      true, false, false, // skew distribution
+    };
+
+    String[] strs = new String[] {
+      "Vader", "Han Solo", "R2-D2", "Luke", "Leia",
+    };
+
+    final BitSet view012 = builder.getFullView();
+    final BitSet view01  = builder.addPartialView( 0, 1 );
+    final BitSet view12  = builder.addPartialView( 1, 2 );
+    final BitSet view02  = builder.addPartialView( 0, 2 );
+    final BitSet view0   = builder.addPartialView( 0 );
+    final BitSet view1   = builder.addPartialView( 1 );
+    final BitSet view2   = builder.addPartialView( 2 );
+
+    // This might be the ugliest line of Java I've ever written.  BARF
+    @SuppressWarnings({"unchecked"})
+    MultiViewMap<Integer>[] maps = 
+      (MultiViewMap<Integer>[]) Array.newInstance( builder.build().getClass(), 8 );
+
+    for( int i = 0; i < maps.length; ++i ) {
+      maps[i] = builder.build();
+    }
+
+
+    System.out.println( "    Number of full keys in each table per op cycle:" );
+    
+    for( int reps = 0; reps < 100; ++reps ) {
+      int nextOp = random.nextInt( 100 );
+
+      System.out.print( "    Op: " );
+
+      if( nextOp < 15 ) {
+        // put some new values in
+        System.out.print( "PT  " );
+        int numNewValues = 1 + random.nextInt( 8 );
+        for( int i = 0; i < numNewValues; ++i ) {
+          MultiKey newKey = MultiKey.factory( getInt( ints ),
+                                              getBool( bools ),
+                                              getStr( strs ) );
+          getMap( maps ).put( newKey, random.nextInt() );
+        }
+
+      } else if( nextOp < 70 ) {
+        // remove values by a random view
+        System.out.print( "RM  " );
+        MultiViewMap<Integer> map = getMap( maps );
+
+        switch( random.nextInt( 6 ) ) {
+        case 0: { map.remove( view0,  MultiKey.factory( getInt (ints ) ) ); } break;
+        case 1: { map.remove( view1,  MultiKey.factory( getBool(bools) ) ); } break;
+        case 2: { map.remove( view2,  MultiKey.factory( getStr (strs ) ) ); } break;
+        case 3: { map.remove( view01, MultiKey.factory( getInt (ints ), getBool(bools) ) ); } break;
+        case 4: { map.remove( view12, MultiKey.factory( getBool(bools), getStr (strs ) ) ); } break;
+        case 5: { map.remove( view02, MultiKey.factory( getInt (ints ), getStr (strs ) ) ); } break;
+        }
+
+      } else {
+        // merge two tables
+        System.out.print( "MG  " );
+        getMap( maps ).merge( getMap( maps ) );
+      }   
+
+      for( int i = 0; i < maps.length - 1; ++i ) {
+        if( i < maps.length - 1 ) {
+          System.out.print( maps[i].size() + ", " );
+        }
+      }
+      System.out.println( maps[maps.length-1].size() );
+    }
+
+    System.out.println( "DONE" );
+  }
+
+  private static Integer getInt( Integer[] ints ) {
+    return ints[random.nextInt( ints.length )];
+  }
+
+  private static Boolean getBool( Boolean[] bools ) {
+    return bools[random.nextInt( bools.length )];
+  }
+
+  private static String getStr( String[] strs ) {
+    return strs[random.nextInt( strs.length )];
+  }
+
+  private static MultiViewMap<Integer> getMap( MultiViewMap<Integer>[] maps ) {
+    return maps[random.nextInt( maps.length )];
+  }
+}
diff --git a/Robust/src/Util/UnitTests/makefile b/Robust/src/Util/UnitTests/makefile
new file mode 100644 (file)
index 0000000..caf78a2
--- /dev/null
@@ -0,0 +1,12 @@
+CLASSPATH=../..
+MAINCLASS=MultiViewMapTest
+
+all: $(MAINCLASS).java
+       javac -classpath $(CLASSPATH) -Xlint $(MAINCLASS).java
+
+run:
+       java -classpath $(CLASSPATH) Util.UnitTests.$(MAINCLASS)
+
+clean:
+       rm -f *.class
+       rm -f *~