You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@uima.apache.org by sc...@apache.org on 2015/05/13 21:25:21 UTC

svn commit: r1679263 - in /uima/uimaj/trunk/uimaj-core/src: main/java/org/apache/uima/cas/impl/ test/java/org/apache/uima/cas/test/

Author: schor
Date: Wed May 13 19:25:21 2015
New Revision: 1679263

URL: http://svn.apache.org/r1679263
Log:
[UIMA-4392][UIMA-4391][UIMA-4393] subiterator fixes and improvements

Modified:
    uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/AnnotationIndexImpl.java
    uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSIndexFlat.java
    uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSIteratorAggregate.java
    uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSIteratorImplBase.java
    uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSIteratorWrapper.java
    uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSIteratorWrapperDoubleCheck.java
    uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FilteredIterator.java
    uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/Subiterator.java
    uima/uimaj/trunk/uimaj-core/src/test/java/org/apache/uima/cas/test/AnnotationIteratorTest.java

Modified: uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/AnnotationIndexImpl.java
URL: http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/AnnotationIndexImpl.java?rev=1679263&r1=1679262&r2=1679263&view=diff
==============================================================================
--- uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/AnnotationIndexImpl.java (original)
+++ uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/AnnotationIndexImpl.java Wed May 13 19:25:21 2015
@@ -34,7 +34,7 @@ import org.apache.uima.cas.text.Annotati
  *     customization for additional fns and
  *     a ref to it's enclosing instance of the FSIndexRepositoryImpl.
  */
-public class AnnotationIndexImpl<T extends AnnotationFS> implements AnnotationIndex<T> {
+public class AnnotationIndexImpl <T extends AnnotationFS> implements AnnotationIndex<T> {
 
   private FSIndex<T> index;
   
@@ -134,7 +134,10 @@ public class AnnotationIndexImpl<T exten
     if (ambiguous) {
       return (FSIterator<T>) this.index.iterator();
     }
-    return new Subiterator<T>((FSIterator<T>) this.index.iterator());
+    // return non-constrained, non-strict, unambiguous iterator
+    boolean strict = true;
+    boolean isBounded = false;
+    return new Subiterator<T>(this.index.iterator(), null, 0, 0, ambiguous, strict, isBounded, ((FSIndexRepositoryImpl.IndexImpl<T>)(this.index)).getFsRepositoryImpl());
   }
 
   /*
@@ -155,7 +158,8 @@ public class AnnotationIndexImpl<T exten
    */
   @Override
   public FSIterator<T> subiterator(AnnotationFS annot, boolean ambiguous, boolean strict) {
-    return new Subiterator<T>((FSIterator<T>)this.index.iterator(), annot, ambiguous, strict);
+    boolean isBounded = true;
+    return new Subiterator<T>(this.index.iterator(), annot, 0, 0, ambiguous, strict, isBounded, ((FSIndexRepositoryImpl.IndexImpl<T>)(this.index)).getFsRepositoryImpl());
   }
 
   /*

Modified: uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSIndexFlat.java
URL: http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSIndexFlat.java?rev=1679263&r1=1679262&r2=1679263&view=diff
==============================================================================
--- uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSIndexFlat.java (original)
+++ uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSIndexFlat.java Wed May 13 19:25:21 2015
@@ -20,6 +20,7 @@ package org.apache.uima.cas.impl;
 
 import java.lang.ref.SoftReference;
 import java.util.Arrays;
+import java.util.Comparator;
 import java.util.ConcurrentModificationException;
 import java.util.NoSuchElementException;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -29,6 +30,7 @@ import java.util.concurrent.atomic.Atomi
 import org.apache.uima.cas.FSIterator;
 import org.apache.uima.cas.FeatureStructure;
 import org.apache.uima.cas.impl.FSIndexRepositoryImpl.IndexIteratorCachePair;
+import org.apache.uima.cas.text.AnnotationFS;
 import org.apache.uima.internal.util.Int2IntArrayMapFixedSize;
 import org.apache.uima.jcas.cas.TOP;
 
@@ -107,6 +109,7 @@ public class FSIndexFlat<T extends Featu
    * The class can't be static - makes ref to "T" invalid
    ******************************************** */
   public static class FSIteratorFlat<TI extends FeatureStructure> extends FSIteratorImplBase<TI> implements LowLevelIterator {
+
     /**
      * iterator's Feature Structure array, points to the instance
      * in existence when the iterator was created, from the FSIndex
@@ -155,10 +158,10 @@ public class FSIndexFlat<T extends Featu
 
     @Override
     public TI get() throws NoSuchElementException {
-      if (!hasNext()) {
+      if (!isValid()) {
         throw new NoSuchElementException();
       }
-      final TI fs = ifsa[pos];
+      final TI fs = (TI) ifsa[pos];
       final int typeCode = ((FeatureStructureImpl)fs).getTypeCode();
       if (debugTypeCodeUnstable) {
         if ((fs instanceof TOP) &&  // insures jcas in use
@@ -205,15 +208,27 @@ public class FSIndexFlat<T extends Featu
 
     @Override
     public void moveTo(FeatureStructure fs) {
-      final FSLeafIndexImpl<TI> li = iicp.getFsLeafIndex();
-      @SuppressWarnings("unchecked")
-      final TI fsTI = (TI)fs;
-      pos = Arrays.binarySearch(ifsa, fsTI, li);
+      moveToCommon((Comparator<TI>) iicp.getFsLeafIndex(), (TI)fs);     
+    }
+    
+    /* 
+     * Version for subiterator where begin and end are specified without an FS
+     * (non-Javadoc)
+     * @see org.apache.uima.cas.impl.FSIteratorImplBase#moveTo(java.util.Comparator)
+     */
+    @Override
+    void moveTo(int begin, int end) {
+      moveToCommon((Comparator<TI>) (Subiterator.getAnnotationBeginEndComparator(begin, end)), null); 
+    }
+
+    //The comparator may embed the compare values, and in that case, fs may be null
+    private void moveToCommon(Comparator<TI> comparator, TI fs) {
+      pos = Arrays.binarySearch(ifsa, fs, comparator);
       if (pos < 0) {
         pos = (-pos) - 1;
         return;
       }
-      if (!hasNext()) {
+      if (!isValid()) {
         return;
       }
       TI foundFs = get();
@@ -221,15 +236,15 @@ public class FSIndexFlat<T extends Featu
       while (true) {
         moveToPrevious();
         if (isValid()) {
-          if (li.compare(get(), foundFs) != 0) {
-            moveToNext(); // go back
+          if (comparator.compare(get(), foundFs) != 0) {
+            moveToNext(); // go forwards back to the last valid one
             break;
           }
         } else {
           moveToFirst();  // went to before first, so go back to 1st
           break;
         }
-      }      
+      }        
     }
 
     @Override
@@ -275,6 +290,28 @@ public class FSIndexFlat<T extends Featu
     public LowLevelIndex ll_getIndex() {
       throw new UnsupportedOperationException();
     }
+    
+    /* (non-Javadoc)
+     * @see org.apache.uima.cas.impl.FSIteratorImplBase#getBegin()
+     */
+    @Override
+    int getBegin() {
+      // all callers validate position before call
+      final AnnotationFS fs = (AnnotationFS) ifsa[pos];
+      return fs.getBegin();
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.uima.cas.impl.FSIteratorImplBase#getEnd()
+     */
+    @Override
+    int getEnd() {
+      // all callers validate position before call
+      final AnnotationFS fs = (AnnotationFS) ifsa[pos];
+      return fs.getEnd();
+    }
+
+
   }
   
   /**

Modified: uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSIteratorAggregate.java
URL: http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSIteratorAggregate.java?rev=1679263&r1=1679262&r2=1679263&view=diff
==============================================================================
--- uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSIteratorAggregate.java (original)
+++ uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSIteratorAggregate.java Wed May 13 19:25:21 2015
@@ -21,11 +21,13 @@ package org.apache.uima.cas.impl;
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.List;
 import java.util.NoSuchElementException;
 
 import org.apache.uima.cas.FSIterator;
 import org.apache.uima.cas.FeatureStructure;
+import org.apache.uima.cas.text.AnnotationFS;
 
 /**
  * Aggregate several FS iterators.  Simply iterates over one after the other, no sorting or merging
@@ -39,7 +41,7 @@ class FSIteratorAggregate<T extends Feat
   // Internal contract for this class is that isValid() iff the current iterator isValid().
 
   // A list of iterators, unordered.
-  private final List<FSIterator<T>> iterators;
+  private final List<FSIteratorImplBase<T>> iterators;
   
   // The offset of the current index.
   private int iteratorIndex = 0;
@@ -48,9 +50,9 @@ class FSIteratorAggregate<T extends Feat
    * The one and only constructor.
    * @param c Collection of input iterators.
    */
-  public FSIteratorAggregate(Collection<FSIterator<T>> c) {
+  public FSIteratorAggregate(Collection<FSIteratorImplBase<T>> c) {
     super();
-    this.iterators = new ArrayList<FSIterator<T>>();
+    this.iterators = new ArrayList<FSIteratorImplBase<T>>();
     this.iterators.addAll(c);
     // The unwritten contract of FSIterators is that they point to the first element when they are
     // constructed.  This is also one way of checking that the new iterator is valid (non-empty).
@@ -58,9 +60,9 @@ class FSIteratorAggregate<T extends Feat
   }
 
   public FSIterator<T> copy() {
-    ArrayList<FSIterator<T>> itCopies = new ArrayList<FSIterator<T>>(this.iterators.size());
+    ArrayList<FSIteratorImplBase<T>> itCopies = new ArrayList<FSIteratorImplBase<T>>(this.iterators.size());
     for (int i = 0; i < this.iterators.size(); i++) {
-      itCopies.add(this.iterators.get(i).copy());
+      itCopies.add((FSIteratorImplBase<T>) this.iterators.get(i).copy());
     }
     FSIteratorAggregate<T> copy = new FSIteratorAggregate<T>(itCopies);
     copy.iteratorIndex = this.iteratorIndex;
@@ -162,4 +164,24 @@ class FSIteratorAggregate<T extends Feat
     this.iteratorIndex = this.iterators.size();
   }
 
+  @Override
+  int getBegin() {
+    return this.iterators.get(this.iteratorIndex).getBegin();
+  }
+
+  @Override
+  int getEnd() {
+    // TODO Auto-generated method stub
+    return super.getEnd();
+  }
+
+  /* (non-Javadoc)
+   * @see org.apache.uima.cas.impl.FSIteratorImplBase#moveTo(java.util.Comparator)
+   */
+  @Override
+  <TT extends AnnotationFS> void moveTo(int begin, int end) {
+    throw new UnsupportedOperationException();
+  }
+
+  
 }

Modified: uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSIteratorImplBase.java
URL: http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSIteratorImplBase.java?rev=1679263&r1=1679262&r2=1679263&view=diff
==============================================================================
--- uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSIteratorImplBase.java (original)
+++ uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSIteratorImplBase.java Wed May 13 19:25:21 2015
@@ -19,8 +19,11 @@
 
 package org.apache.uima.cas.impl;
 
+import java.util.Comparator;
+
 import org.apache.uima.cas.FSIterator;
 import org.apache.uima.cas.FeatureStructure;
+import org.apache.uima.cas.text.AnnotationFS;
 
 /**
  * Base class for FSIterator implementations. Defines the hasNext, next, and remove methods required
@@ -73,4 +76,23 @@ public abstract class FSIteratorImplBase
   public void remove() {
     throw new UnsupportedOperationException();
   }
+  
+  // methods to be overridden that can speed up this operation
+  int getBegin() {
+    return ((AnnotationFS)get()).getBegin();
+  }
+  
+  int getEnd() {
+    return ((AnnotationFS)get()).getEnd();
+  }
+  
+  /**
+   * A special version of moveTo for subtypes of AnnotationFS, which moves to a particular begin/end
+   * (no type priority). 
+   * @param begin the starting point (inclusive)
+   * @param end the ending point (inclusive)
+   */
+  <TT extends AnnotationFS> void moveTo(int begin, int end) {
+    throw new UnsupportedOperationException();
+  }
 }

Modified: uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSIteratorWrapper.java
URL: http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSIteratorWrapper.java?rev=1679263&r1=1679262&r2=1679263&view=diff
==============================================================================
--- uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSIteratorWrapper.java (original)
+++ uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSIteratorWrapper.java Wed May 13 19:25:21 2015
@@ -19,8 +19,11 @@
 
 package org.apache.uima.cas.impl;
 
+import java.util.Comparator;
+
 import org.apache.uima.cas.FSIterator;
 import org.apache.uima.cas.FeatureStructure;
+import org.apache.uima.cas.text.AnnotationFS;
 import org.apache.uima.internal.util.IntPointerIterator;
 
 public class FSIteratorWrapper<T extends FeatureStructure> extends FSIteratorImplBase<T> {
@@ -33,10 +36,17 @@ public class FSIteratorWrapper<T extends
   IntPointerIterator it;
 
   CASImpl casImpl;
+  
+  final int beginOffset;
+  final int endOffset;
+  
 
   FSIteratorWrapper(IntPointerIterator it, CASImpl casImpl) {
     this.it = it;
     this.casImpl = casImpl;
+    TypeSystemImpl tsi = casImpl.getTypeSystemImpl(); 
+    beginOffset = casImpl.getFeatureOffset(tsi.startFeatCode);
+    endOffset = casImpl.getFeatureOffset(tsi.endFeatCode);
   }
 
   /**
@@ -95,4 +105,21 @@ public class FSIteratorWrapper<T extends
     this.it.moveTo(((FeatureStructureImpl) fs).getAddress());
   }
 
+  @Override
+  int getBegin() {
+    return casImpl.getHeapValue(it.get() + beginOffset);
+  }
+
+  @Override
+  int getEnd() {
+    return casImpl.getHeapValue(it.get() + endOffset);
+  }
+
+  @Override
+  <TT extends AnnotationFS> void moveTo(int begin, int end) {
+    ((FSIteratorImplBase<TT>)it).moveTo(begin, end);
+  }
+  
+  
+
 }

Modified: uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSIteratorWrapperDoubleCheck.java
URL: http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSIteratorWrapperDoubleCheck.java?rev=1679263&r1=1679262&r2=1679263&view=diff
==============================================================================
--- uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSIteratorWrapperDoubleCheck.java (original)
+++ uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSIteratorWrapperDoubleCheck.java Wed May 13 19:25:21 2015
@@ -19,9 +19,12 @@
 
 package org.apache.uima.cas.impl;
 
+import java.util.Comparator;
+
 import org.apache.uima.cas.FSIterator;
 import org.apache.uima.cas.FeatureStructure;
 import org.apache.uima.cas.impl.FSIndexFlat.FSIteratorFlat;
+import org.apache.uima.cas.text.AnnotationFS;
 
 /**
  * Only used for debugging
@@ -31,6 +34,7 @@ import org.apache.uima.cas.impl.FSIndexF
  */
 public class FSIteratorWrapperDoubleCheck<T extends FeatureStructure> extends FSIteratorImplBase<T> {
   
+
   @Override
   public String toString() {
     return "FSIteratorWrapper [it=" + nonFlatIterator + "]";
@@ -134,6 +138,16 @@ public class FSIteratorWrapperDoubleChec
     this.nonFlatIterator.moveTo(fs);
     this.flatIterator.moveTo(fs);
   }
+  
+  /* (non-Javadoc)
+   * @see org.apache.uima.cas.impl.FSIteratorImplBase#moveTo(java.util.Comparator)
+   */
+  @Override
+  <TT extends AnnotationFS> void moveTo(int begin, int end) {
+    ((FSIteratorImplBase<TT>)(this.nonFlatIterator)).moveTo(begin, end);
+    this.flatIterator.moveTo(begin, end);
+  }
+
 
   private void error(String msg) {
     msg = flatIterator.verifyFsaSubsumes() + msg;

Modified: uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FilteredIterator.java
URL: http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FilteredIterator.java?rev=1679263&r1=1679262&r2=1679263&view=diff
==============================================================================
--- uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FilteredIterator.java (original)
+++ uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/FilteredIterator.java Wed May 13 19:25:21 2015
@@ -19,11 +19,13 @@
 
 package org.apache.uima.cas.impl;
 
+import java.util.Comparator;
 import java.util.NoSuchElementException;
 
 import org.apache.uima.cas.FSIterator;
 import org.apache.uima.cas.FSMatchConstraint;
 import org.apache.uima.cas.FeatureStructure;
+import org.apache.uima.cas.text.AnnotationFS;
 
 /**
  * Implements a filtered iterator.
@@ -58,34 +60,40 @@ class FilteredIterator<T extends Feature
     // can simply refer to the underlying iterator.
     return this.it.isValid();
   }
-
-  public void moveToFirst() {
-    this.it.moveToFirst();
+  
+  private void adjustForConstraintForward() {
     // If the iterator is valid, but doesn't match the constraint, advance.
     while (this.it.isValid() && !this.cons.match(this.it.get())) {
       this.it.moveToNext();
-    }
+    }    
+  }
+  
+  private void adjustForConstraintBackward() {
+    // If the iterator is valid, but doesn't match the constraint, advance.
+    while (this.it.isValid() && !this.cons.match(this.it.get())) {
+      this.it.moveToPrevious();
+    }    
+  }
+  
+
+  public void moveToFirst() {
+    this.it.moveToFirst();
+    adjustForConstraintForward();
   }
 
   public void moveToLast() {
     this.it.moveToLast();
-    while (this.it.isValid() && !this.cons.match(this.it.get())) {
-      this.it.moveToPrevious();
-    }
+    adjustForConstraintBackward();
   }
 
   public void moveToNext() {
     this.it.moveToNext();
-    while (this.it.isValid() && !this.cons.match(this.it.get())) {
-      this.it.moveToNext();
-    }
+    adjustForConstraintForward();
   }
 
   public void moveToPrevious() {
     this.it.moveToPrevious();
-    while (this.it.isValid() && !this.cons.match(this.it.get())) {
-      this.it.moveToPrevious();
-    }
+    adjustForConstraintBackward();
   }
 
   public T get() throws NoSuchElementException {
@@ -105,9 +113,15 @@ class FilteredIterator<T extends Feature
    */
   public void moveTo(FeatureStructure fs) {
     this.it.moveTo(fs);
-    if (!this.cons.match(this.it.get())) {
-      moveToNext();
-    }
+    adjustForConstraintForward();
   }
 
+  /* (non-Javadoc)
+   * @see org.apache.uima.cas.impl.FSIteratorImplBase#moveTo(java.util.Comparator)
+   */
+  @Override
+  <TT extends AnnotationFS> void moveTo(int begin, int end) {
+    ((FSIteratorImplBase<T>)(this.it)).moveTo(begin, end);
+    adjustForConstraintForward();
+  }
 }

Modified: uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/Subiterator.java
URL: http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/Subiterator.java?rev=1679263&r1=1679262&r2=1679263&view=diff
==============================================================================
--- uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/Subiterator.java (original)
+++ uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/cas/impl/Subiterator.java Wed May 13 19:25:21 2015
@@ -30,148 +30,242 @@ import org.apache.uima.cas.text.Annotati
 
 /**
  * Subiterator implementation.
+ * 
+ * There are two bounding styles and 2 underlying forms.
+ * 
+ * The 2nd form is produced lazily when needed, and 
+ * is made by a one-time forward traversal to compute unambigious subsets and store them into a list.
+ *   - The 2nd form is needed only for unambiguous style if backwards or moveto(fs) operation.
+ * 
+ * The 1st form uses the underlying iterator directly, and does skipping as needed, while iterating  
+ *   - going forward: 
+ *       skip if unambigious and start is within prev span
+ *       skip if strict and end lies outside of scope span
+ *     
+ *   - going backward:
+ *       if unambiguous - convert to form 2
+ *       skip if strict and end lies outside of scope span
+ *       
+ *   - going to particular fs (left most match)
+ *       if unambiguous - convert to form 2
+ *       skip (forward) if strict and end lies outside of scope span
+ *
+ *   - going to first:
+ *       unambiguous - no testing needed, no prior span 
+ *       skip if strict and end lies outside of scope span
+ *     
+ *   - going to last:
+ *       unambigious - convert to 2nd form
+ *       skip backwards if strict and end lies outside of scope span
+ *       
+ * There are two styles of the bounding information.
+ * 
+ *   - the traditional one uses the standard comparator for annotations: begin (ascending), end (descending) and
+ *     type priority ordering
+ *   - the 2nd style uses just a begin value and an end value, no type priority ordering.  
  */
 public class Subiterator<T extends AnnotationFS> extends FSIteratorImplBase<T> {
 
-  final private ArrayList<T> list;
+  private ArrayList<T> list = null; // used for form 2, lazily initialized
 
-  private int pos;
+  private int pos = 0;  // used for form 2
+  
+  private final FSIteratorImplBase<T> it;
 
-  private Comparator<AnnotationFS> annotationComparator = null;
+  private final FSIndexRepositoryImpl fsIndexRepo;
+  
+  private final AnnotationFS boundingAnnotation;  // the bounding annotation need not be a subtype of T
+  
+  private final int boundingBegin;
+  private final int boundingEnd;
+  private int prevEnd = 0;  // for unambiguous iterators
+  private final boolean ambiguous;
+  private final boolean strict;
+  private final boolean isBounded;
+  private boolean isListForm = false;
+  private final boolean isBeginEndCompare;
 
-  private Subiterator() {
-    super();
-    this.list = new ArrayList<T>();
-    this.pos = 0;
-  }
   
-  private Subiterator(ArrayList<T> list) {
+  /**
+   * Constructor called with annot == null and boundingBegin and boundingEnd specified, or
+   *             called with annot != null (boundingBegin/End ignored)
+   *             
+   *    if annot == null, then this implies the comparisons should use just begin and end (not type priorities) and
+   *                                        the range is inclusive with the begin/ end boundaries
+   *             != null, then this implies the comparisons use the normal Annotation compare and
+   *                                        the range is exclusive on the left with the boundaries.         
+   * @param it the iterator to use
+   * @param boundingAnnotation null or the bounding annotation
+   * @param boundingBegin if boundingAnnotation is null, this is used as the bounding begin (inclusive); 
+   *                      ignored if boundingAnnotation is not null
+   * @param boundingEnd   if annot is null, this is used as the bounding end (inclusive); 
+   *                      ignored if boundingAnnotation is not null
+   * @param ambiguous true means normal iteration, 
+   *                  false means to skip annotations whose begin lies between previous begin (inclusive) and end (exclusive)
+   * @param strict true means to skip annotations whose end is greater than the bounding end position (ignoring type priorities)
+   * @param isBounded false means its an unambiguous iterator with no bounds narrowing; ambiguous taken to be false
+   * @param fsIndexRepo the index repository for this iterator
+   */
+  Subiterator(
+      FSIterator<T> it, 
+      AnnotationFS boundingAnnotation, 
+      int boundingBegin, 
+      int boundingEnd, 
+      boolean ambiguous, 
+      boolean strict,
+      boolean isBounded, 
+      FSIndexRepositoryImpl fsIndexRepo
+      ) {
     super();
-    this.list = list;
-    this.pos = 0;
+    this.isBounded = isBounded;
+    // non bounded iterators don't use any begin/end compares
+    this.isBeginEndCompare = this.isBounded ? (boundingAnnotation == null) : false;
+    this.it = (FSIteratorImplBase<T>) it;
+    this.boundingAnnotation = this.isBounded ? boundingAnnotation : null;
+    this.boundingBegin = (boundingAnnotation == null) ? boundingBegin : boundingAnnotation.getBegin();
+    this.boundingEnd = (boundingAnnotation == null) ? boundingEnd : boundingAnnotation.getEnd();
+    this.ambiguous = ambiguous;
+    this.strict = strict;
+    this.fsIndexRepo = fsIndexRepo;
+    
+    moveToStart();
   }
-
+    
+  
   /**
-   * Create a disambiguated iterator.  No concurrent modification exception checking is done.
+   * Converting to list form - called for 
+   *   unambiguous iterator going backwards, 
+   *   unambiguous iterator doing a moveTo(fs) operation
+   *   iterator doing a moveToLast() operation
    * 
-   * @param it
-   *          The iterator to be disambiguated.
    */
-  Subiterator(FSIterator<T> it) {
-    this();
-    it.moveToFirst();
-    if (!it.isValid()) {
-      return;
-    }
-    T current, next;
-    current = it.get();
-    this.list.add(current);
-    it.moveToNext();
-    while (it.isValid()) {
-      next = it.get();
-      if (current.getEnd() <= next.getBegin()) {
-        current = next;
-        this.list.add(current);
-      }
+  private void convertToListForm() {
+    moveToStart();  // moves to the start annotation, including moving past equals for annot style, and accomodating strict
+    this.list = new ArrayList<T>();
+    while (isValid()) {
+      prevEnd = it.getEnd();
+      list.add(it.get());
       it.moveToNext();
-    }
+      movePastPrevAnnotation();
+      adjustForStrictForward();
+    }    
+    isListForm = true;  // do at end, so up to this point, iterator is not the list form style
   }
-
   
-  Subiterator(FSIterator<T> it, AnnotationFS annot, final boolean ambiguous, final boolean strict) {
-    this();
-    if (ambiguous) {
-      
-      initAmbiguousSubiterator(it, annot, strict);
-    } else {
-      initUnambiguousSubiterator(it, annot, strict);
-    }
-  }
-
-  // makes an extra copy of the items
-  private void initAmbiguousSubiterator(FSIterator<T> it, AnnotationFS annot, final boolean strict) {
-    final int start = annot.getBegin();
-    final int end = annot.getEnd();
-    it.moveTo(annot);  // to "earliest" equal, or if none are equal, to the one just later than annot
-    
-    // This is a little silly, it skips 1 of possibly many indexed annotations if the earliest one is "equal"
-    //    (just means matching the keys) to the control annot  4/2015
-    if (it.isValid() && it.get().equals(annot)) {
-      it.moveToNext();
-    }
-    // Skip annotations whose start is before the start parameter.
-    // should never have any???
-    while (it.isValid() && it.get().getBegin() < start) {
-      it.moveToNext();
-    }
-    T current;
-    while (it.isValid()) {
-      current = it.get();
-      // If the start of the current annotation is past the end parameter,
-      // we're done.
-      if (current.getBegin() > end) {
+  private void moveToExact(FeatureStructureImpl targetAnnotation) {
+    it.moveTo(targetAnnotation);  // move to left-most equal one
+    while (it.isValid()) {         // advance to the exact equal one
+      if (targetAnnotation.getAddress() == ((FeatureStructureImpl)(it.get())).getAddress()) {
         break;
       }
       it.moveToNext();
-      if (strict && current.getEnd() > end) {
-        continue;
-      }
-      this.list.add(current);
     }
   }
-
-  private void initUnambiguousSubiterator(FSIterator<T> it, AnnotationFS annot, final boolean strict) {
-    final int start = annot.getBegin();
-    final int end = annot.getEnd();
-    it.moveTo(annot);
+  /**
+   * Move to the starting position of the sub iterator
+   *   Annotation bounding:  move to the annotation, then move forwards until you're at an element not equal to the annot.
+   *    (by definition of the iterator)
+   *   and adjust for strict
+   */
+  private void moveToStart() {
+    if (!isBounded) {
+      it.moveToFirst();
+    } else if (isBeginEndCompare) {
+      it.moveTo(this.boundingBegin, this.boundingEnd);
+    } else {  // is annotation bounding
+      it.moveTo(boundingAnnotation);
+      movePastAnnot();  // only for the annot style, the range is exclusive by definition
+    }
     
-    if (it.isValid() && it.get().equals(annot)) {
+    adjustForStrictForward();
+    setPrevEnd();
+  }
+  
+  private void setPrevEnd() {
+    if (!ambiguous && it.isValid()) {
+      this.prevEnd = it.getEnd();
+    }    
+  }
+
+  /**
+   * For subiterators bounded by Annotations, the starting place is the 
+   * item past the elements that are equal to the the bounding annot,
+   * adjusted for strict exclusions
+   */
+  private void movePastAnnot() {
+    Comparator<AnnotationFS> annotationComparator = getAnnotationComparator();
+    while (isValid() && (0 == annotationComparator.compare(boundingAnnotation, it.get()))) {
       it.moveToNext();
     }
-    if (!it.isValid()) {
-      return;
-    }
-    annot = it.get();
-    // Skip annotations with begin positions before the given start
-    // position.
-    while (it.isValid() && ((start > annot.getBegin()) || (strict && annot.getEnd() > end))) {
-      it.moveToNext();
+  }
+  
+  private Comparator<AnnotationFS> getAnnotationComparator() {
+    return fsIndexRepo.getAnnotationFsComparator();
+  }
+  
+  /** 
+   * Called for begin/end compare, after moveTo(fs)
+   * to eliminate the effect of the type order comparison before any adjustment for strict.
+   * Move backwards while equal with begin/end iterator. 
+   */
+  private void adjustAfterMoveToForBeginEndComparator(AnnotationFS fs) {
+    final int begin = fs.getBegin();
+    final int end = fs.getEnd();
+    
+    while (it.isValid() && (it.getBegin() == begin) && (it.getEnd() == end)) {
+      it.moveToPrevious();  
     }
-    // Add annotations.
-    if (!it.isValid()) {
-      return;
+    // are one position too far, move back one
+    if (it.isValid()) {
+      it.moveToNext();  
+    } else {
+      moveToStart();
     }
-    T current = null;
-    while (it.isValid()) {
-      final T next = it.get();
-      // If the next annotation overlaps, skip it. Don't check while there is no "current" yet.
-      if ((current != null) && (next.getBegin() < current.getEnd())) {
+  }
+  
+  /**
+   * For strict mode, advance iterator until the end is within the bounding end 
+   */
+  private void adjustForStrictForward() {
+    if (strict && isBounded) {
+      while (it.isValid() && (it.getEnd() > this.boundingEnd)) {
         it.moveToNext();
-        continue;
       }
-      // If we're past the end, stop.
-      if (next.getBegin() > end) {
-        break;
+    }
+  }
+  
+  /**
+   * For unambiguous, going forwards
+   */
+  private void movePastPrevAnnotation() {
+    if (!ambiguous) {
+      while (it.isValid() && (it.getBegin() < this.prevEnd)) {
+        it.moveToNext();
       }
-      // We have an annotation that's within the boundaries and doesn't
-      // overlap
-      // with the previous annotation. We add this annotation if we're not
-      // strict, or the end position is within the limits.
-      if (!strict || next.getEnd() <= end) {
-        current = next;
-        this.list.add(current);
+    }
+  }
+  
+  private void adjustForStrictBackward() {
+    if (strict && isBounded) {
+      while (it.isValid() && (it.getEnd() > this.boundingEnd)) {
+        it.moveToPrevious();
       }
-      it.moveToNext();
     }
   }
-
+  
   /*
    * (non-Javadoc)
    * 
    * @see org.apache.uima.cas.FSIterator#isValid()
    */
   public boolean isValid() {
-    return (this.pos >= 0) && (this.pos < this.list.size());
+    return isListForm ? 
+        (this.pos >= 0) && (this.pos < this.list.size()) :
+          
+        // assume that it position is adjusted for strict and unambiguous already
+        (it.isValid() && 
+            ( isBounded? (it.getBegin() <= this.boundingEnd) : 
+              true));
   }
 
   /*
@@ -180,8 +274,14 @@ public class Subiterator<T extends Annot
    * @see org.apache.uima.cas.FSIterator#get()
    */
   public T get() throws NoSuchElementException {
-    if (isValid()) {
-      return this.list.get(this.pos);
+    if (isListForm) {
+      if ((this.pos >= 0) && (this.pos < this.list.size())) {
+        return this.list.get(this.pos);
+      }
+    } else {
+      if (isValid()) {
+        return it.get();
+      }
     }
     throw new NoSuchElementException();
   }
@@ -192,7 +292,20 @@ public class Subiterator<T extends Annot
    * @see org.apache.uima.cas.FSIterator#moveToNext()
    */
   public void moveToNext() {
-    ++this.pos;
+    if (isListForm) {
+      ++this.pos;
+      // setPrevEnd not needed because list form already accounted for unambiguous
+      return;
+    }
+   
+    it.moveToNext();
+    
+    if (!ambiguous) {
+        movePastPrevAnnotation();
+    }
+
+    adjustForStrictForward();
+    setPrevEnd();
   }
 
   /*
@@ -201,7 +314,22 @@ public class Subiterator<T extends Annot
    * @see org.apache.uima.cas.FSIterator#moveToPrevious()
    */
   public void moveToPrevious() {
-    --this.pos;
+    if (isListForm) {
+      --this.pos;
+      return;
+    }
+
+    if (!ambiguous) {
+      // Convert to list form
+      FeatureStructureImpl currentAnnotation = (FeatureStructureImpl) it.get();  // save to restore position
+      convertToListForm();
+      moveToExact(currentAnnotation);
+      --this.pos;
+      return;
+    }
+    
+    it.moveToPrevious();
+    adjustForStrictBackward();
   }
 
   /*
@@ -210,24 +338,53 @@ public class Subiterator<T extends Annot
    * @see org.apache.uima.cas.FSIterator#moveToFirst()
    */
   public void moveToFirst() {
-    this.pos = 0;
+    if (isListForm) {
+      this.pos = 0;
+    } else {
+      moveToStart();
+    }
   }
 
   /*
-   * (non-Javadoc)
+   * This operation is relatively expensive
    * 
    * @see org.apache.uima.cas.FSIterator#moveToLast()
    */
   public void moveToLast() {
-    this.pos = this.list.size() - 1;
+    if (isListForm) {
+      this.pos = this.list.size() - 1;
+    } else {
+      convertToListForm();
+      this.pos = this.list.size() - 1;
+    }
   }
 
-  private final Comparator<AnnotationFS> getAnnotationComparator(FeatureStructure fs) {
-    if (this.annotationComparator == null) {
-      this.annotationComparator = 
-          ((FSIndexRepositoryImpl)(fs.getCAS().getIndexRepository())).getAnnotationFsComparator(); 
-    }
-    return this.annotationComparator;
+  // default visibility - referred to by flat iterator
+  static Comparator<AnnotationFS> getAnnotationBeginEndComparator(final int boundingBegin, final int boundingEnd) {
+    return new Comparator<AnnotationFS>() {
+
+      @Override
+      public int compare(AnnotationFS o1, AnnotationFS o2) {
+        AnnotationFS a = (o1 == null) ? o2 : o1;
+        boolean isReverse = o1 == null;
+        final int b;
+        if ((b = a.getBegin()) < boundingBegin) {
+          return isReverse? 1 : -1;
+        }
+        if (b > boundingBegin) {
+          return isReverse? -1 : 1;
+        }
+        
+        final int e;
+        if ((e = a.getEnd()) < boundingEnd) {
+          return isReverse? -1 : 1;
+        }
+        if (e > boundingEnd) {
+          return isReverse? 1 : -1;
+        }
+        return 0;
+      }
+    };
   }
 
   /*
@@ -236,32 +393,103 @@ public class Subiterator<T extends Annot
    * @see org.apache.uima.cas.FSIterator#moveTo(org.apache.uima.cas.FeatureStructure)
    */
   public void moveTo(FeatureStructure fs) {
-    final Comparator<AnnotationFS> comparator = getAnnotationComparator(fs);
-    pos = Collections.binarySearch(this.list, (AnnotationFS) fs, comparator);
-    if (pos >= 0) {
-      if (!isValid()) {
-        return;
-      }
-      T foundFs = get();
-      // Go back until we find a FS that is really smaller
-      while (true) {
-        moveToPrevious();
-        if (isValid()) {
-          if (comparator.compare(get(), foundFs) != 0) {
-            moveToNext(); // go back
-            break;
-          }
+    AnnotationFS fsa = (AnnotationFS) fs;
+    if (!ambiguous && !isListForm) {  // unambiguous must be in list form
+      convertToListForm();
+    }
+    if (isListForm) {
+      Comparator<AnnotationFS> annotationComparator = getAnnotationComparator();
+      pos = Collections.binarySearch(this.list, (AnnotationFS) fs, annotationComparator);
+      if (pos >= 0) {
+        if (!isValid()) {
+          return;
+        }
+        T foundFs = get();
+        // Go back until we find a FS that is really smaller
+        if (isBeginEndCompare) {
+          adjustAfterMoveToForBeginEndComparator(fsa);
         } else {
-          moveToFirst();  // went to before first, so go back to 1st
-          break;
+          while (true) {
+            moveToPrevious();
+            if (isValid()) {
+              if (annotationComparator.compare(get(), foundFs) != 0) {
+                moveToNext(); // backed up too far, so go back
+                break;
+              }
+            } else {
+              moveToFirst();  // went to before first, so go back to 1st
+              break;
+            }
+          }   
         }
-      }       
-      return;
+      } else {
+        // element wasn't found, 
+        //set the position to the next (greater) element in the list or to an invalid position 
+        //   if no element is greater
+        pos = (-pos) - 1;
+        if (isBeginEndCompare) {
+          pos-- ;  // check to see if previous element, unequal with annotation compare, might be equal with begin-end compare
+          adjustAfterMoveToForBeginEndComparator(fsa);
+        }
+      }
     } else {
-      pos = (-pos) - 1;
-      return;
+      // is ambiguous, may be strict, always bounded (either by annotation or begin / end
+      it.moveTo(fs);
+      if (isBeginEndCompare) {
+        it.moveTo(fs);
+        adjustAfterMoveToForBeginEndComparator(fsa);
+      } else {  // is begin/end bounding
+        it.moveTo(fs);
+      }
+      adjustForStrictForward();
     }
   }
+  
+  /* 
+   * @see org.apache.uima.cas.impl.FSIteratorImplBase#moveTo(java.util.Comparator)
+   */
+  @Override
+  <TT extends AnnotationFS> void moveTo(final int begin, final int end) {
+    if (!ambiguous && !isListForm) {  // unambiguous must be in list form
+      convertToListForm();
+    }
+    if (isListForm) {
+      pos = Collections.binarySearch(this.list, null, getAnnotationBeginEndComparator(begin, end));
+      if (pos >= 0) {
+        if (!isValid()) {
+          return;
+        }
+        
+        // Go back to leftmost 
+        while (it.isValid() && (it.getBegin() == begin) && (it.getEnd() == end)) {
+          it.moveToPrevious();  
+        }
+        // are one position too far, move back one
+        if (it.isValid()) {
+          it.moveToNext();  
+        } else {
+          it.moveToFirst();
+        }
+      } else {
+        // element wasn't found, 
+        //set the position to the next (greater) element in the list or to an invalid position 
+        //   if no element is greater
+        pos = (-pos) - 1;
+      }
+    } else {
+      // is ambiguous, may be strict, always bounded (either by annotation or begin / end
+      it.moveTo(begin, end);
+      // Go back to leftmost 
+      while (isValid() && (it.getBegin() == begin) && (it.getEnd() == end)) {
+        it.moveToPrevious();  
+      }
+      adjustForStrictForward();
+    }
+    
+    
+  }
+
+
 
   /*
    * (non-Javadoc)
@@ -269,9 +497,96 @@ public class Subiterator<T extends Annot
    * @see org.apache.uima.cas.FSIterator#copy()
    */
   public FSIterator<T> copy() {
-    Subiterator<T> copy = new Subiterator<T>(this.list);;
-    copy.pos = this.pos;
+    Subiterator<T> copy = new Subiterator<T>(
+        this.it, this.boundingAnnotation, this.boundingBegin, this.boundingEnd, this.ambiguous, this.strict, this.isBounded, this.fsIndexRepo);
+    copy.list = this.list;  // non-final things
+    copy.pos  = this.pos;
     return copy;
   }
 
+
+
+
+
+//  // makes an extra copy of the items
+//  private void initAmbiguousSubiterator(AnnotationFS annot, final boolean strict) {
+//    final int start = (annot == null) ? 0 : annot.getBegin();
+//    final int boundingEnd = (annot == null) ? Integer.MAX_VALUE : annot.getEnd();
+//    if (annot == null) {
+//      it.moveToFirst();
+//    } else {
+//      it.moveTo(annot);  // to "earliest" equal, or if none are equal, to the one just later than annot
+//    }
+//    
+//    // This is a little silly, it skips 1 of possibly many indexed annotations if the earliest one is "equal"
+//    //    (just means matching the keys) to the control annot  4/2015
+//    if (it.isValid() && it.get().equals(annot)) {
+//      it.moveToNext();
+//    }
+//    // Skip annotations whose start is before the start parameter.
+//    // should never have any???
+//    while (it.isValid() && it.getBegin() < start) {
+//      it.moveToNext();
+//    }
+//    T current;
+//    while (it.isValid()) {
+//      current = it.get();
+//      // If the start of the current annotation is past the boundingEnd parameter,
+//      // we're done.
+//      if (current.getBegin() > boundingEnd) {
+//        break;
+//      }
+//      it.moveToNext();
+//      if (strict && current.getEnd() > boundingEnd) {
+//        continue;
+//      }
+//      this.list.add(current);
+//    }
+//  }
+//
+//  private void initUnambiguousSubiterator(AnnotationFS annot, final boolean strict) {
+//    final int start = annot.getBegin();
+//    final int boundingEnd = annot.getEnd();
+//    it.moveTo(annot);
+//    
+//    if (it.isValid() && it.get().equals(annot)) {
+//      it.moveToNext();
+//    }
+//    if (!it.isValid()) {
+//      return;
+//    }
+//    annot = it.get();
+//    // Skip annotations with begin positions before the given start
+//    // position.
+//    while (it.isValid() && ((start > annot.getBegin()) || (strict && annot.getEnd() > boundingEnd))) {
+//      it.moveToNext();
+//    }
+//    // Add annotations.
+//    if (!it.isValid()) {
+//      return;
+//    }
+//    T current = null;
+//    while (it.isValid()) {
+//      final T next = it.get();
+//      // If the next annotation overlaps, skip it. Don't check while there is no "current" yet.
+//      if ((current != null) && (next.getBegin() < current.getEnd())) {
+//        it.moveToNext();
+//        continue;
+//      }
+//      // If we're past the boundingEnd, stop.
+//      if (next.getBegin() > boundingEnd) {
+//        break;
+//      }
+//      // We have an annotation that's within the boundaries and doesn't
+//      // overlap
+//      // with the previous annotation. We add this annotation if we're not
+//      // strict, or the end position is within the limits.
+//      if (!strict || next.getEnd() <= boundingEnd) {
+//        current = next;
+//        this.list.add(current);
+//      }
+//      it.moveToNext();
+//    }
+//  }
+
 }

Modified: uima/uimaj/trunk/uimaj-core/src/test/java/org/apache/uima/cas/test/AnnotationIteratorTest.java
URL: http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-core/src/test/java/org/apache/uima/cas/test/AnnotationIteratorTest.java?rev=1679263&r1=1679262&r2=1679263&view=diff
==============================================================================
--- uima/uimaj/trunk/uimaj-core/src/test/java/org/apache/uima/cas/test/AnnotationIteratorTest.java (original)
+++ uima/uimaj/trunk/uimaj-core/src/test/java/org/apache/uima/cas/test/AnnotationIteratorTest.java Wed May 13 19:25:21 2015
@@ -26,6 +26,7 @@ import junit.framework.TestCase;
 
 import org.apache.uima.cas.CAS;
 import org.apache.uima.cas.CASRuntimeException;
+import org.apache.uima.cas.FSIndexRepository;
 import org.apache.uima.cas.FSIterator;
 import org.apache.uima.cas.Feature;
 import org.apache.uima.cas.Type;
@@ -34,12 +35,15 @@ import org.apache.uima.cas.impl.FSIndexF
 import org.apache.uima.cas.impl.FSIteratorWrapper;
 import org.apache.uima.cas.text.AnnotationFS;
 import org.apache.uima.cas.text.AnnotationIndex;
+import org.apache.uima.jcas.tcas.Annotation;
 
 /**
  * Class comment for FilteredIteratorTest.java goes here.
  * 
  */
 public class AnnotationIteratorTest extends TestCase {
+  
+  private static final boolean showFSs = false;
 
   private CAS cas;
 
@@ -73,6 +77,14 @@ public class AnnotationIteratorTest exte
 
   private Type sentenceType;
 
+  private boolean isSave;
+
+  private List<AnnotationFS> fss;
+
+  private List<Integer> fssStarts = new ArrayList<Integer>();
+
+  private int callCount = -1;
+
   /**
    * Constructor for FilteredIteratorTest.
    * 
@@ -85,6 +97,7 @@ public class AnnotationIteratorTest exte
   @Override
   public void setUp() {
     try {
+      // make a cas with various types, fairly complex -- see CASTestSetup class
       this.cas = CASInitializer.initCas(new CASTestSetup());
       assertTrue(this.cas != null);
       this.ts = this.cas.getTypeSystem();
@@ -143,9 +156,18 @@ public class AnnotationIteratorTest exte
   }
 
   public void testIterator1() {
-
+//   Tokens                +---+
+//                        +---+
+//                       +---+
+//   BigBound                      +-----------------------------+
     final String text = "aaaa bbbb cccc dddd aaaa bbbb cccc dddd aaaa bbbb cccc dddd ";
-
+//                       +--------+
+//   Sentences                +--------+
+//                                 +----------+
+//
+//   bound4strict                   +------------------+            
+//   sentence4strict                 +-----------------------------+
+    
     try {
       this.cas.setDocumentText(text);
     } catch (CASRuntimeException e) {
@@ -153,135 +175,172 @@ public class AnnotationIteratorTest exte
     }
 
     /***************************************************
-     * Create and index tokens an sentences 
+     * Create and index tokens and sentences 
      ***************************************************/
+    FSIndexRepository ir = this.cas.getIndexRepository();
     int annotCount = 1; // Init with document annotation.
     // create token and sentence annotations
     for (int i = 0; i < text.length() - 5; i++) {
       ++annotCount;
-      this.cas.getIndexRepository().addFS(this.cas.createAnnotation(this.tokenType, i, i + 5));
+      ir.addFS(this.cas.createAnnotation(this.tokenType, i, i + 5));
     }
     // for (int i = 0; i < text.length() - 5; i++) {
     // cas.getIndexRepository().addFS(cas.createAnnotation(tokenType, i, i+5));
     // }
     for (int i = 0; i < text.length() - 10; i += 5) {
       ++annotCount;
-      this.cas.getIndexRepository().addFS(this.cas.createAnnotation(this.sentenceType, i, i + 10));
+      ir.addFS(this.cas.createAnnotation(this.sentenceType, i, i + 10));
     }
+    
+    ++annotCount;
+    ir.addFS(this.cas.createAnnotation(this.sentenceType,  12, 31));
 
     /***************************************************
      * iterate over them
      ***************************************************/
     List fss = new ArrayList();
-    iterateOverAnnotations(annotCount, fss);
+    callCount = -1;
+    iterateOverAnnotations(annotCount, fss); // annotCount is the total number of sentences and tokens
     
+    callCount = -1;
     iterateOverAnnotations(annotCount, fss);  // should be using flattened version
+    
+    /***************************************************
+     * test skipping over multiple equal items at front
+     ***************************************************/
+    callCount = -1;
+    fss.clear();
+    isSave = true;
+    AnnotationFS a1, a2;
+    ir.addFS(a1 = this.cas.createAnnotation(this.tokenType, 1, 6));
+    a1.setStringValue(lemmaFeat, "lemma1");
+    ir.addFS(a2 = this.cas.createAnnotation(this.tokenType, 1, 6));
+    a2.setStringValue(lemmaFeat, "lemma2");
+    
+    FSIterator<AnnotationFS> it;
+    AnnotationIndex<AnnotationFS> tokenIndex = cas.getAnnotationIndex(tokenType);
+    it = tokenIndex.subiterator(a1);
+    assertCount("multi equal", 0, it);
+            
   }
-  
-  private void iterateOverAnnotations(int annotCount, List fss) {
-    boolean isSave = fss.size() == 0;
+  /**
+   * The tests include:
+   *   a) running with / w/o "flattened" indexes
+   *   b) running forwards and backwards (testing moveToLast, isValid)
+   *   c) testing strict and unambiguous variants
+   *   d) running over all annotations and restricting to just a particular subtype
+   *   
+   *   new tests:  
+   *     verifying bounding FS < all returned, including multiples of it
+   *     strict at 1st element, at last element
+   *     (not done yet) ConcurrentModificationException testing
+   *     
+   *     (not done yet) Testing with different bound styles
+   *     
+   * @param annotCount
+   * @param fss
+   */
+  // called twice, the 2nd time should be with flattened indexes (List fss non empty the 2nd time)
+  private void iterateOverAnnotations(int annotCount, List<AnnotationFS> fss) {
+    this.fss = fss;
+    isSave = fss.size() == 0;
     int count;
     AnnotationIndex<AnnotationFS> annotIndex = this.cas.getAnnotationIndex();
-    FSIterator<AnnotationFS> it = annotIndex.iterator(true);
-    assertTrue((isSave) ? it instanceof FSIteratorWrapper : it instanceof FSIndexFlat.FSIteratorFlat);
-    count = 0;
+    AnnotationIndex<AnnotationFS> sentIndex = this.cas.getAnnotationIndex(sentenceType);
+    FSIterator<AnnotationFS> it = annotIndex.iterator(true);  // a normal "ambiguous" iterator
+    assertTrue((isSave) ? it instanceof FSIteratorWrapper : it instanceof FSIndexFlat.FSIteratorFlat);   
+    assertCount("Normal ambiguous annot iterator", annotCount, it);
+    
+    it = annotIndex.iterator(false);  // false means create an unambiguous iterator
+    assertCount("Unambiguous annot iterator", 1, it);  // because of document Annotation
+    
+    AnnotationFS bigBound = this.cas.createAnnotation(this.sentenceType, 10, 41);
+    it = annotIndex.subiterator(bigBound, true, true);  // ambiguous, and strict
+    assertCount("Subiterator over annot with big bound, strict", 33, it);
+    
+    it = annotIndex.subiterator(bigBound, false, true);  // unambiguous, strict
+    assertCount("Subiterator over annot unambiguous strict", 3, it);
+
+    it = annotIndex.subiterator(bigBound, true, false);
+    assertCount("Subiterator over annot ambiguous not-strict", 40, it);
+    
+    it = annotIndex.subiterator(bigBound, false, false);  // unambiguous, not strict
+    assertCount("Subiterator over annot, unambiguous, not-strict", 4, it);
+    
+    AnnotationFS sent = this.cas.getAnnotationIndex(this.sentenceType).iterator().get();
+    it = annotIndex.subiterator(sent, false, true);
+    assertCount("Subiterator over annot unambiguous strict", 2, it);
+    
+    // strict skips first item
+    bigBound = this.cas.createAnnotation(this.sentenceType,  11, 30);
+    it = sentIndex.subiterator(bigBound, true, true);
+    assertCount("Subiteratover over sent ambiguous strict", 2, it);
+    it = sentIndex.subiterator(bigBound, true, false);
+    assertCount("Subiteratover over sent ambiguous", 5, it);
+    it = sentIndex.subiterator(bigBound, false, false);
+    assertCount("Subiteratover over sent unambiguous", 1, it); 
+  }
+  
+  private String flatStateMsg(String s) {
+    return s + (isSave ? "" : " with flattened index");
+  }
+  
+  private void assertCount(String msg, int expected,  FSIterator<AnnotationFS> it) {
+    msg = flatStateMsg(msg);
+    int count = 0;
+    callCount  ++;
+    int fssStart;
+    if (isSave) {
+      fssStarts.add(fssStart = fss.size());
+    } else {
+      fssStart = fssStarts.get(callCount);
+    }
     while (it.isValid()) {
       ++count;
       AnnotationFS fs = it.next();
+      if (showFSs) {
+        System.out.format("%d " + msg + " fs begin: %d end: %d type: %s%n", count, fs.getBegin(), fs.getEnd(), fs.getType().getName() );
+      }
       if (isSave) {
         fss.add(fs);
       } else {
-        assertEquals(fss.get(count -1).hashCode(), fs.hashCode());
+        assertEquals(msg, fss.get(fssStart + count -1).hashCode(), fs.hashCode());
       }
     }
-    assertEquals(annotCount, count);
-    // System.out.println("Size of ambiguous iterator: " + count);
-    it = annotIndex.iterator(false);  // false means create an unambiguous iterator
-    count = 0;
-    while (it.isValid()) {
-      ++count;
-      it.moveToNext();
+    assertEquals(msg, expected, count);
+    if (count > 0) {
+      // test moveTo(fs) in middle, first, and last
+      AnnotationFS posFs = fss.get(fssStart + (count >> 1));
+      it.moveTo(posFs);
+      assertEquals(msg, it.get().hashCode(), posFs.hashCode());
+      
+      posFs = fss.get(fssStart);
+      it.moveTo(posFs);
+      assertEquals(msg, it.get().hashCode(), posFs.hashCode());
+      it.moveToFirst();
+      assertEquals(msg, it.get().hashCode(), posFs.hashCode());
+      
+      posFs = fss.get(fssStart + count - 1);
+      it.moveTo(posFs);
+      assertEquals(msg, it.get().hashCode(), posFs.hashCode());
+      it.moveToLast();
+      assertEquals(msg, it.get().hashCode(), posFs.hashCode());
+    } else {
+      // count is 0
+      it.moveToFirst();
+      assertFalse(it.isValid());
+      it.moveToLast();
+      assertFalse(it.isValid());
+      it.moveTo(cas.getDocumentAnnotation());
+      assertFalse(it.isValid());    
     }
-    assertEquals(1, count); // because of document Annotation
-    // System.out.println("Size of unambiguous iterator: " + count);
-    AnnotationFS bigAnnot = this.cas.createAnnotation(this.sentenceType, 10, 41);
-    it = annotIndex.subiterator(bigAnnot, true, true);
-    count = 0;
-    while (it.isValid()) {
-      ++count;
-      // AnnotationFS a = (AnnotationFS) it.get();
-      // System.out.println("Annotation from " + a.getBegin() + " to " + a.getEnd());
-      it.moveToNext();
-    }
-    assertEquals(32, count);
-    count = 0;
-    for (it.moveToLast(); it.isValid(); it.moveToPrevious()) {
-      ++count;
-    }
-    assertEquals(32, count);
-    // System.out.println("Size of subiterator(true, true): " + count);
-    it = annotIndex.subiterator(bigAnnot, false, true);
-    count = 0;
-    while (it.isValid()) {
-      ++count;
-      // AnnotationFS a = (AnnotationFS) it.get();
-      // System.out.println("Annotation from " + a.getBegin() + " to " + a.getEnd());
-      it.moveToNext();
-    }
-    assertEquals(3, count);
-    // System.out.println("Size of subiterator(false, true): " + count);
-    count = 0;
-    for (it.moveToLast(); it.isValid(); it.moveToPrevious()) {
-      ++count;
-    }
-    assertEquals(3, count);
-    it = annotIndex.subiterator(bigAnnot, true, false);
-    count = 0;
-    while (it.isValid()) {
-      ++count;
-      // AnnotationFS a = (AnnotationFS) it.get();
-      // System.out.println("Annotation from " + a.getBegin() + " to " + a.getEnd());
-      it.moveToNext();
-    }
-    assertEquals(39, count);
-    // System.out.println("Size of subiterator(true, false): " + count);
-    count = 0;
-    for (it.moveToLast(); it.isValid(); it.moveToPrevious()) {
-      ++count;
-    }
-    assertEquals(39, count);
-    it = annotIndex.subiterator(bigAnnot, false, false);
-    count = 0;
-    while (it.isValid()) {
-      ++count;
-      // AnnotationFS a = (AnnotationFS) it.get();
-      // System.out.println("Annotation from " + a.getBegin() + " to " + a.getEnd());
-      it.moveToNext();
-    }
-    assertEquals(4, count);
-    // System.out.println("Size of subiterator(false, false): " + count);
-    count = 0;
-    for (it.moveToLast(); it.isValid(); it.moveToPrevious()) {
-      ++count;
-    }
-    assertEquals(4, count);
-    AnnotationFS sent = this.cas.getAnnotationIndex(this.sentenceType).iterator().get();
-    it = annotIndex.subiterator(sent, false, true);
-    count = 0;
-    while (it.isValid()) {
-      ++count;
-      // AnnotationFS a = (AnnotationFS) it.get();
-      // System.out.println("Annotation from " + a.getBegin() + " to " + a.getEnd());
-      it.moveToNext();
-    }
-    assertEquals(2, count);
-    // System.out.println("Size of subiterator(false, false): " + count);
+    
+    // test movetoLast, moving backwards
     count = 0;
     for (it.moveToLast(); it.isValid(); it.moveToPrevious()) {
       ++count;
     }
-    assertEquals(2, count);
-    
+    assertEquals(msg, expected, count);
   }
   
   public void testIncorrectIndexTypeException() {