You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@uima.apache.org by re...@apache.org on 2020/10/10 23:15:02 UTC

[uima-uimafit] branch UIMA-6270-Add-selectOverlapping-to-JCasUtil created (now 8add6c4)

This is an automated email from the ASF dual-hosted git repository.

rec pushed a change to branch UIMA-6270-Add-selectOverlapping-to-JCasUtil
in repository https://gitbox.apache.org/repos/asf/uima-uimafit.git.


      at 8add6c4  [UIMA-6270] Add selectOverlapping to (J)CasUtil

This branch includes the following new commits:

     new 8add6c4  [UIMA-6270] Add selectOverlapping to (J)CasUtil

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[uima-uimafit] 01/01: [UIMA-6270] Add selectOverlapping to (J)CasUtil

Posted by re...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rec pushed a commit to branch UIMA-6270-Add-selectOverlapping-to-JCasUtil
in repository https://gitbox.apache.org/repos/asf/uima-uimafit.git

commit 8add6c4b370366c42a56e6ceaede6bec49559aa5
Author: Richard Eckart de Castilho <re...@apache.org>
AuthorDate: Sun Oct 11 01:15:31 2020 +0200

    [UIMA-6270] Add selectOverlapping to (J)CasUtil
    
    - Added selectOverlapping
    - Added unit tests
    - Pull out check for annotation type into a helper method
    - Added AnnotationPredicates helper class
---
 .../apache/uima/fit/util/AnnotationPredicates.java | 217 ++++++++++++
 .../java/org/apache/uima/fit/util/CasUtil.java     | 107 ++++--
 .../java/org/apache/uima/fit/util/JCasUtil.java    |  39 +++
 .../uima/fit/util/AnnotationPredicatesTest.java    | 382 +++++++++++++++++++++
 .../org/apache/uima/fit/util/JCasUtilTest.java     | 274 ++++++++++++++-
 5 files changed, 992 insertions(+), 27 deletions(-)

diff --git a/uimafit-core/src/main/java/org/apache/uima/fit/util/AnnotationPredicates.java b/uimafit-core/src/main/java/org/apache/uima/fit/util/AnnotationPredicates.java
new file mode 100644
index 0000000..75c7385
--- /dev/null
+++ b/uimafit-core/src/main/java/org/apache/uima/fit/util/AnnotationPredicates.java
@@ -0,0 +1,217 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.uima.fit.util;
+
+import org.apache.uima.cas.text.AnnotationFS;
+
+public final class AnnotationPredicates {
+  private AnnotationPredicates() {
+    // No instances
+  }
+  
+  public static boolean coveredBy(int aXBegin, int aXEnd, int aYBegin, int aYEnd) {
+    return aYBegin <= aXBegin && aXEnd <= aYEnd && (aXBegin == aYBegin || aXBegin != aYEnd);
+  }
+
+  public static boolean coveredBy(AnnotationFS aX, int aYBegin, int aYEnd) {
+    int xBegin = aX.getBegin();
+    return aYBegin <= xBegin && (xBegin == aYBegin || xBegin != aYEnd) && aX.getEnd() <= aYEnd;
+  }
+
+  /**
+   * Y is starting before or at the same position as A and ends after or at the same position as X.
+   * 
+   * @param aX
+   *          X
+   * @param aY
+   *          Y
+   * @return whether X is covered by Y.
+   */
+  public static boolean coveredBy(AnnotationFS aX, AnnotationFS aY) {
+    int xBegin = aX.getBegin();
+    int yBegin = aY.getBegin();
+    int yEnd = aY.getEnd();
+    return yBegin <= xBegin && (xBegin == yBegin || xBegin != yEnd) && aX.getEnd() <= yEnd;
+  }
+
+  public static boolean covers(int aXBegin, int aXEnd, int aYBegin, int aYEnd) {
+    return aXBegin <= aYBegin && aYEnd <= aXEnd && (aYBegin == aXBegin || aYBegin != aXEnd);
+  }
+
+  public static boolean covers(AnnotationFS aX, int aYBegin, int aYEnd) {
+    int xBegin = aX.getBegin();
+    int xEnd = aX.getEnd();
+    return xBegin <= aYBegin && aYEnd <= xEnd && (aYBegin == xBegin || aYBegin != xEnd);
+  }
+
+  /**
+   * X is starting before or at the same position as Y and ends after or at the same position as Y.
+   * 
+   * @param aX
+   *          X
+   * @param aY
+   *          Y
+   * @return whether X is covering Y.
+   */
+  public static boolean covers(AnnotationFS aX, AnnotationFS aY) {
+    int xBegin = aX.getBegin();
+    int xEnd = aX.getEnd();
+    int yBegin = aY.getBegin();
+    return xBegin <= yBegin && (yBegin == xBegin || yBegin != xEnd) && aY.getEnd() <= xEnd;
+  }
+
+  public static boolean colocated(int aXBegin, int aXEnd, int aYBegin, int aYEnd) {
+    return aXBegin == aYBegin && aXEnd == aYEnd;
+  }
+
+  public static boolean colocated(AnnotationFS aX, int aYBegin, int aYEnd) {
+    return aX.getBegin() == aYBegin && aX.getEnd() == aYEnd;
+  }
+
+  /**
+   * X starts and ends at the same position as Y.
+   * 
+   * @param aX
+   *          X
+   * @param aY
+   *          Y
+   * @return whether X is at the same location as Y.
+   */
+  public static boolean colocated(AnnotationFS aX, AnnotationFS aY) {
+    return aX.getBegin() == aY.getBegin() && aX.getEnd() == aY.getEnd();
+  }
+
+  public static boolean overlaps(int aXBegin, int aXEnd, int aYBegin, int aYEnd) {
+    return aYBegin == aXBegin || (aXBegin < aYEnd && aYBegin < aXEnd);
+  }
+
+  public static boolean overlaps(AnnotationFS aX, int aYBegin, int aYEnd) {
+    int xBegin = aX.getBegin();
+    return aYBegin == xBegin || (xBegin < aYEnd && aYBegin < aX.getEnd());
+  }
+
+  /**
+   * The intersection of the spans X and Y is non-empty. If either X or Y have a zero-width, then
+   * the intersection is considered to be non-empty if the begin of X is either within Y or the same
+   * as the begin of Y - and vice versa.
+   * 
+   * @param aX
+   *          X
+   * @param aY
+   *          Y
+   * @return whether X overlaps with Y in any way.
+   */
+  public static boolean overlaps(AnnotationFS aX, AnnotationFS aY) {
+    int xBegin = aX.getBegin();
+    int yBegin = aY.getBegin();
+    return yBegin == xBegin || (xBegin < aY.getEnd() && yBegin < aX.getEnd());
+  }
+
+  public static boolean overlapsLeft(int aXBegin, int aXEnd, int aYBegin, int aYEnd) {
+    return aXBegin < aYBegin && aYBegin < aXEnd && aXEnd < aYEnd;
+  }
+
+  public static boolean overlapsLeft(AnnotationFS aX, int aYBegin, int aYEnd) {
+    int xEnd = aX.getEnd();
+    return aYBegin < xEnd && xEnd < aYEnd && aX.getBegin() < aYBegin;
+  }
+
+  /**
+   * X is starting before or at the same position as Y and ends before Y ends.
+   * 
+   * @param aX
+   *          X
+   * @param aY
+   *          Y
+   * @return whether X overlaps Y on the left.
+   */
+  public static boolean overlapsLeft(AnnotationFS aX, AnnotationFS aY) {
+    int xEnd = aX.getEnd();
+    int yBegin = aY.getBegin();
+    return yBegin < xEnd && xEnd < aY.getEnd() && aX.getBegin() < yBegin;
+  }
+
+  public static boolean overlapsRight(int aXBegin, int aXEnd, int aYBegin, int aYEnd) {
+    return aYBegin < aXBegin && aXBegin < aYEnd && aYEnd < aXEnd;
+  }
+
+  public static boolean overlapsRight(AnnotationFS aX, int aYBegin, int aYEnd) {
+    int xBegin = aX.getBegin();
+    return aYBegin < xBegin && xBegin < aYEnd && aYEnd < aX.getEnd();
+  }
+
+  /**
+   * X is starting after Y starts and ends after or at the same position as Y.
+   * 
+   * @param aX
+   *          X
+   * @param aY
+   *          Y
+   * @return whether X overlaps Y on the right.
+   */
+  public static boolean overlapsRight(AnnotationFS aX, AnnotationFS aY) {
+    int xBegin = aX.getBegin();
+    int yEnd = aY.getEnd();
+    return xBegin < yEnd && aY.getBegin() < xBegin && yEnd < aX.getEnd();
+  }
+
+  public static boolean rightOf(int aXBegin, int aXEnd, int aYBegin, int aYEnd) {
+    return aXBegin >= aYEnd && aXBegin != aYBegin;
+  }
+
+  public static boolean rightOf(AnnotationFS aX, int aYBegin, int aYEnd) {
+    int xBegin = aX.getBegin();
+    return xBegin >= aYEnd && xBegin != aYBegin;
+  }
+
+  /**
+   * X starts at or after the position that Y ends.
+   * 
+   * @param aX
+   *          X
+   * @param aY
+   *          Y
+   * @return whether X is right of Y.
+   */
+  public static boolean rightOf(AnnotationFS aX, AnnotationFS aY) {
+    int xBegin = aX.getBegin();
+    return xBegin >= aY.getEnd() && xBegin != aY.getBegin();
+  }
+
+  public static boolean leftOf(int aXBegin, int aXEnd, int aYBegin, int aYEnd) {
+    return aYBegin >= aXEnd && aXBegin != aYBegin;
+  }
+
+  public static boolean leftOf(AnnotationFS aX, int aYBegin, int aYEnd) {
+    return aYBegin >= aX.getEnd() && aX.getBegin() != aYBegin;
+  }
+
+  /**
+   * X ends before or at the position that Y starts.
+   * 
+   * @param aX
+   *          X
+   * @param aY
+   *          Y
+   * @return whether X left of Y.
+   */
+  public static boolean leftOf(AnnotationFS aX, AnnotationFS aY) {
+    return aY.getBegin() >= aX.getEnd() && aX.getBegin() != aY.getBegin();
+  }
+}
diff --git a/uimafit-core/src/main/java/org/apache/uima/fit/util/CasUtil.java b/uimafit-core/src/main/java/org/apache/uima/fit/util/CasUtil.java
index b622b54..187fe00 100644
--- a/uimafit-core/src/main/java/org/apache/uima/fit/util/CasUtil.java
+++ b/uimafit-core/src/main/java/org/apache/uima/fit/util/CasUtil.java
@@ -157,10 +157,7 @@ public final class CasUtil {
    */
   public static Type getAnnotationType(CAS aCas, Class<?> aJCasClass) {
     final Type type = getType(aCas, aJCasClass);
-    if (!aCas.getTypeSystem().subsumes(aCas.getAnnotationType(), type)) {
-      throw new IllegalArgumentException("Type [" + aJCasClass.getName()
-              + "] is not an annotation type");
-    }
+    requireAnnotationType(aCas, type);
     return type;
   }
 
@@ -175,9 +172,7 @@ public final class CasUtil {
    */
   public static Type getAnnotationType(CAS aCas, String aTypeName) {
     Type type = getType(aCas, aTypeName);
-    if (!aCas.getTypeSystem().subsumes(aCas.getAnnotationType(), type)) {
-      throw new IllegalArgumentException("Type [" + aTypeName + "] is not an annotation type");
-    }
+    requireAnnotationType(aCas, type);
     return type;
   }
 
@@ -208,9 +203,7 @@ public final class CasUtil {
   @SuppressWarnings({ "unchecked", "rawtypes" })
   public static List<AnnotationFS> select(ArrayFS array, Type type) {
     final CAS cas = array.getCAS();
-    if (!cas.getTypeSystem().subsumes(cas.getAnnotationType(), type)) {
-      throw new IllegalArgumentException("Type [" + type.getName() + "] is not an annotation type");
-    }
+    requireAnnotationType(cas, type);
     return (List) FSCollectionFactory.create(array, type);
   }
 
@@ -267,10 +260,8 @@ public final class CasUtil {
    */
   @SuppressWarnings({ "unchecked", "rawtypes" })
   public static Collection<AnnotationFS> select(final CAS cas, final Type type) {
-    if (!cas.getTypeSystem().subsumes(cas.getAnnotationType(), type)) {
-      throw new IllegalArgumentException("Type [" + type.getName() + "] is not an annotation type");
-    }
-    return (Collection) FSCollectionFactory.create(cas.getAnnotationIndex(type));
+    requireAnnotationType(cas, type);
+    return (Collection) cas.getAnnotationIndex(type).select().asList();
   }
   
   /**
@@ -909,7 +900,66 @@ public final class CasUtil {
 
     return unmodifiableMap(index);
   }
+  
+  /**
+   * Get a list of annotations of the given annotation type overlapping the given annotation. Does 
+   * not use subiterators and does not respect type prioritites.
+   * 
+   * @param aCas
+   *          a CAS.
+   * @param aType
+   *          a UIMA type.
+   * @param  aBoundaryAnnotation
+   *          the covering annotation.
+   * @return a list of overlapping annotations.
+   * @see <a href="package-summary.html#SortOrder">Order of selected feature structures</a>
+   */  
+  public static List<AnnotationFS> selectOverlapping(CAS aCas, Type aType,
+          AnnotationFS aBoundaryAnnotation) {
+    return selectOverlapping(aCas, aType, aBoundaryAnnotation.getBegin(),
+            aBoundaryAnnotation.getEnd());
+  }  
+  
+  /**
+   * Get a list of annotations of the given annotation type overlapping the given span. Does not use
+   * subiterators and does not respect type prioritites.
+   * 
+   * @param aCas
+   *          a CAS.
+   * @param aType
+   *          a UIMA type.
+   * @param aSelBegin
+   *          begin offset.
+   * @param aSelEnd
+   *          end offset.
+   * @return a list of overlapping annotations.
+   * @see <a href="package-summary.html#SortOrder">Order of selected feature structures</a>
+   */  
+  public static List<AnnotationFS> selectOverlapping(CAS aCas, Type aType, int aSelBegin,
+          int aSelEnd) {
+    requireAnnotationType(aCas, aType);
+    
+    List<AnnotationFS> annotations = new ArrayList<>();
+    for (AnnotationFS t : aCas.getAnnotationIndex(aType)) {
+      int begin = t.getBegin();
+      int end = t.getEnd();
 
+      // Annotation is fully right of selection (not overlapping)
+      if (aSelBegin != begin && begin >= aSelEnd) {
+        break;
+      }
+      
+      // not yet there
+      if (aSelBegin != begin && end <= aSelBegin) {
+        continue;
+      }
+      
+      annotations.add(t);
+    }
+
+    return annotations;
+  }
+  
   /**
    * This method exists simply as a convenience method for unit testing. It is not very efficient
    * and should not, in general be used outside the context of unit testing.
@@ -925,9 +975,8 @@ public final class CasUtil {
    * @see <a href="package-summary.html#SortOrder">Order of selected feature structures</a>
    */
   public static AnnotationFS selectByIndex(CAS cas, Type type, int index) {
-    if (!cas.getTypeSystem().subsumes(cas.getAnnotationType(), type)) {
-      throw new IllegalArgumentException("Type [" + type.getName() + "] is not an annotation type");
-    }
+    requireAnnotationType(cas, type);
+
     // withSnapshotIterators() not needed here since we return only one result   
     FSIterator<AnnotationFS> i = cas.getAnnotationIndex(type).iterator();
     int n = index;
@@ -1042,9 +1091,7 @@ public final class CasUtil {
    */
   public static AnnotationFS selectSingleRelative(CAS cas, Type type, AnnotationFS aAnchor,
           int aPosition) {
-    if (!cas.getTypeSystem().subsumes(cas.getAnnotationType(), type)) {
-      throw new IllegalArgumentException("Type [" + type.getName() + "] is not an annotation type");
-    }
+    requireAnnotationType(cas, type);
 
     // move to first previous annotation
     FSIterator<AnnotationFS> itr = cas.getAnnotationIndex(type).iterator();
@@ -1128,9 +1175,7 @@ public final class CasUtil {
    */
   public static List<AnnotationFS> selectPreceding(CAS cas, Type type, AnnotationFS annotation,
           int count) {
-    if (!cas.getTypeSystem().subsumes(cas.getAnnotationType(), type)) {
-      throw new IllegalArgumentException("Type [" + type.getName() + "] is not an annotation type");
-    }
+    requireAnnotationType(cas, type);
 
     List<AnnotationFS> precedingAnnotations = new ArrayList<AnnotationFS>();
 
@@ -1186,9 +1231,7 @@ public final class CasUtil {
    */
   public static List<AnnotationFS> selectFollowing(CAS cas, Type type, AnnotationFS annotation,
           int count) {
-    if (!cas.getTypeSystem().subsumes(cas.getAnnotationType(), type)) {
-      throw new IllegalArgumentException("Type [" + type.getName() + "] is not an annotation type");
-    }
+    requireAnnotationType(cas, type);
 
     // Seek annotation in index
     // withSnapshotIterators() not needed here since we copy the FSes to a list anyway    
@@ -1316,4 +1359,16 @@ public final class CasUtil {
     }
     return text;
   }
+
+  public static boolean isAnnotationType(CAS aCas, Type aType)
+  {
+    return aCas.getTypeSystem().subsumes(aCas.getAnnotationType(), aType);
+  }
+
+  public static void requireAnnotationType(CAS aCas, Type aType) {
+    if (!isAnnotationType(aCas, aType)) {
+      throw new IllegalArgumentException(
+              "Type [" + aType.getName() + "] is not an annotation type");
+    }
+  }
 }
diff --git a/uimafit-core/src/main/java/org/apache/uima/fit/util/JCasUtil.java b/uimafit-core/src/main/java/org/apache/uima/fit/util/JCasUtil.java
index c17c42f..7d8695a 100644
--- a/uimafit-core/src/main/java/org/apache/uima/fit/util/JCasUtil.java
+++ b/uimafit-core/src/main/java/org/apache/uima/fit/util/JCasUtil.java
@@ -541,6 +541,45 @@ public final class JCasUtil {
   }
 
   /**
+   * Get a list of annotations of the given annotation type overlapping the given annotation. Does 
+   * not use subiterators and does not respect type prioritites.
+   * 
+   * @param aJCas
+   *          a CAS.
+   * @param aType
+   *          a UIMA type.
+   * @param  aBoundaryAnnotation
+   *          the covering annotation.
+   * @return a list of overlapping annotations.
+   * @see <a href="package-summary.html#SortOrder">Order of selected feature structures</a>
+   */  
+  public static <T extends Annotation> List<T> selectOverlapping(JCas aJCas, Class<T> aType,
+          AnnotationFS aBoundaryAnnotation) {
+    return cast(
+            CasUtil.selectOverlapping(aJCas.getCas(), getType(aJCas, aType), aBoundaryAnnotation));
+  }  
+  
+  /**
+   * Get a list of annotations of the given annotation type overlapping the given span. Does not use
+   * subiterators and does not respect type prioritites.
+   * 
+   * @param aJCas
+   *          a CAS.
+   * @param aType
+   *          a UIMA type.
+   * @param aBegin
+   *          begin offset.
+   * @param aEnd
+   *          end offset.
+   * @return a list of overlapping annotations.
+   * @see <a href="package-summary.html#SortOrder">Order of selected feature structures</a>
+   */  
+  public static <T extends Annotation> List<T> selectOverlapping(JCas aJCas, Class<T> aType,
+          int aBegin, int aEnd) {    
+    return cast(CasUtil.selectOverlapping(aJCas.getCas(), getType(aJCas, aType), aBegin, aEnd));
+  }
+  
+  /**
    * Check if the given annotation contains any annotation of the given type.
    * 
    * @param jCas
diff --git a/uimafit-core/src/test/java/org/apache/uima/fit/util/AnnotationPredicatesTest.java b/uimafit-core/src/test/java/org/apache/uima/fit/util/AnnotationPredicatesTest.java
new file mode 100644
index 0000000..a5bdfb0
--- /dev/null
+++ b/uimafit-core/src/test/java/org/apache/uima/fit/util/AnnotationPredicatesTest.java
@@ -0,0 +1,382 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.uima.fit.util;
+
+import static java.lang.Integer.MAX_VALUE;
+import static java.util.Arrays.asList;
+import static org.apache.uima.fit.util.AnnotationPredicates.colocated;
+import static org.apache.uima.fit.util.AnnotationPredicates.coveredBy;
+import static org.apache.uima.fit.util.AnnotationPredicates.covers;
+import static org.apache.uima.fit.util.AnnotationPredicates.leftOf;
+import static org.apache.uima.fit.util.AnnotationPredicates.overlaps;
+import static org.apache.uima.fit.util.AnnotationPredicates.overlapsLeft;
+import static org.apache.uima.fit.util.AnnotationPredicates.overlapsRight;
+import static org.apache.uima.fit.util.AnnotationPredicates.rightOf;
+import static org.apache.uima.fit.util.AnnotationPredicatesTest.RelativePosition.COLOCATED;
+import static org.apache.uima.fit.util.AnnotationPredicatesTest.RelativePosition.COVERED_BY;
+import static org.apache.uima.fit.util.AnnotationPredicatesTest.RelativePosition.COVERING;
+import static org.apache.uima.fit.util.AnnotationPredicatesTest.RelativePosition.LEFT_OF;
+import static org.apache.uima.fit.util.AnnotationPredicatesTest.RelativePosition.OVERLAPPING;
+import static org.apache.uima.fit.util.AnnotationPredicatesTest.RelativePosition.OVERLAPPING_LEFT;
+import static org.apache.uima.fit.util.AnnotationPredicatesTest.RelativePosition.OVERLAPPING_RIGHT;
+import static org.apache.uima.fit.util.AnnotationPredicatesTest.RelativePosition.RIGHT_OF;
+
+import org.apache.uima.cas.CAS;
+import org.apache.uima.cas.Type;
+import org.apache.uima.fit.factory.CasFactory;
+import org.assertj.core.api.AutoCloseableSoftAssertions;
+import org.junit.Test;
+
+public class AnnotationPredicatesTest {
+    
+    static enum RelativePosition {
+      COLOCATED,
+      OVERLAPPING,
+      OVERLAPPING_LEFT,
+      OVERLAPPING_RIGHT,
+      COVERING,
+      COVERED_BY,
+      LEFT_OF,
+      RIGHT_OF
+}
+  
+    @Test
+    public void thatCoveringWithIntervalsWorks() throws Exception {
+      assertPosition(COVERING, AnnotationPredicates::covers);
+    }
+
+    @Test
+    public void thatCoveringWithAnnotationAndIntervalWorks() throws Exception {
+      CAS cas = CasFactory.createCas();
+      Type type = cas.getAnnotationType();
+      
+      assertPosition(COVERING, (beginA, endA, beginB,
+              endB) -> covers(cas.createAnnotation(type, beginA, endA), beginB, endB));
+    }
+
+    @Test
+    public void thatCoveringWithAnnotationsWorks() throws Exception {
+      CAS cas = CasFactory.createCas();
+      Type type = cas.getAnnotationType();
+      
+      assertPosition(COVERING,
+              (beginA, endA, beginB, endB) -> covers(cas.createAnnotation(type, beginA, endA),
+                      cas.createAnnotation(type, beginB, endB)));
+    }
+    
+    @Test
+    public void thatCoveredByWithIntervalsWorks() throws Exception {
+      assertPosition(COVERED_BY, AnnotationPredicates::coveredBy);
+    }
+
+    @Test
+    public void thatCoveredByWithAnnotationAndIntervalWorks() throws Exception {
+      CAS cas = CasFactory.createCas();
+      Type type = cas.getAnnotationType();
+      
+      assertPosition(COVERED_BY, (beginA, endA, beginB,
+              endB) -> coveredBy(cas.createAnnotation(type, beginA, endA), beginB, endB));
+    }
+
+    @Test
+    public void thatCoveredByWithAnnotationsWorks() throws Exception {
+      CAS cas = CasFactory.createCas();
+      Type type = cas.getAnnotationType();
+      
+      assertPosition(COVERED_BY,
+              (beginA, endA, beginB, endB) -> coveredBy(cas.createAnnotation(type, beginA, endA),
+                      cas.createAnnotation(type, beginB, endB)));
+    }
+
+    @Test
+    public void thatColocatedWithIntervalsWorks() throws Exception {
+      assertPosition(COLOCATED, AnnotationPredicates::colocated);
+      
+      // It must also work if we switch the of the spans
+      assertPosition(COLOCATED,
+              (beginA, endA, beginB, endB) -> colocated(beginB, endB, beginA, endA));
+    }
+
+    @Test
+    public void thatColocatedWithAnnotationAndIntervalWorks() throws Exception
+    {
+      CAS cas = CasFactory.createCas();
+      Type type = cas.getAnnotationType();
+      
+      assertPosition(COLOCATED, (beginA, endA, beginB,
+              endB) -> colocated(cas.createAnnotation(type, beginA, endA), beginB, endB));
+      
+      // It must also work if we switch the of the spans
+      assertPosition(COLOCATED, (beginA, endA, beginB,
+              endB) -> colocated(cas.createAnnotation(type, beginB, endB), beginA, endA));
+    }
+
+    @Test
+    public void thatColocatedWithAnnotationsWorks() throws Exception
+    {
+      CAS cas = CasFactory.createCas();
+      Type type = cas.getAnnotationType();
+      
+      assertPosition(COLOCATED,
+              (beginA, endA, beginB, endB) -> colocated(cas.createAnnotation(type, beginA, endA),
+                      cas.createAnnotation(type, beginB, endB)));
+      
+      // It must also work if we switch the of the spans
+      assertPosition(COLOCATED,
+              (beginA, endA, beginB, endB) -> colocated(cas.createAnnotation(type, beginB, endB),
+                      cas.createAnnotation(type, beginA, endA)));
+    }
+    
+    @Test
+    public void thatOverlapsLeftWithIntervalsWorks() throws Exception {
+      assertPosition(OVERLAPPING_LEFT, AnnotationPredicates::overlapsLeft);
+    }
+
+    @Test
+    public void thatOverlapsLeftWithAnnotationAndIntervalWorks() throws Exception {
+      CAS cas = CasFactory.createCas();
+      Type type = cas.getAnnotationType();
+      
+      assertPosition(OVERLAPPING_LEFT, (beginA, endA, beginB,
+              endB) -> overlapsLeft(cas.createAnnotation(type, beginA, endA), beginB, endB));
+    }
+
+    @Test
+    public void thatOverlapsLeftWithAnnotationsWorks() throws Exception {
+      CAS cas = CasFactory.createCas();
+      Type type = cas.getAnnotationType();
+      
+      assertPosition(OVERLAPPING_LEFT,
+              (beginA, endA, beginB, endB) -> overlapsLeft(cas.createAnnotation(type, beginA, endA),
+                      cas.createAnnotation(type, beginB, endB)));
+    }
+
+    @Test
+    public void thatOverlapsRightWithIntervalsWorks() throws Exception {
+      assertPosition(OVERLAPPING_RIGHT, AnnotationPredicates::overlapsRight);
+    }
+
+    @Test
+    public void thatOverlapsRightWithAnnotationAndIntervalWorks() throws Exception {
+      CAS cas = CasFactory.createCas();
+      Type type = cas.getAnnotationType();
+      
+      assertPosition(OVERLAPPING_RIGHT, (beginA, endA, beginB,
+              endB) -> overlapsRight(cas.createAnnotation(type, beginA, endA), beginB, endB));
+    }
+
+    @Test
+    public void thatOverlapsRightWithAnnotationsWorks() throws Exception {
+      CAS cas = CasFactory.createCas();
+      Type type = cas.getAnnotationType();
+      
+      assertPosition(OVERLAPPING_RIGHT,
+              (beginA, endA, beginB, endB) -> overlapsRight(cas.createAnnotation(type, beginA, endA),
+                      cas.createAnnotation(type, beginB, endB)));
+    }
+    
+    @Test
+    public void thatOverlapsWithIntervalsWorks() throws Exception {
+      assertPosition(OVERLAPPING, AnnotationPredicates::overlaps);
+
+      // It must also work if we switch the of the spans
+      assertPosition(OVERLAPPING,
+              (beginA, endA, beginB, endB) -> overlaps(beginB, endB, beginA, endA));
+    }
+
+    @Test
+    public void thatOverlapsWithAnnotationAndIntervalWorks() throws Exception
+    {
+      CAS cas = CasFactory.createCas();
+      Type type = cas.getAnnotationType();
+      
+      assertPosition(OVERLAPPING, (beginA, endA, beginB,
+              endB) -> overlaps(cas.createAnnotation(type, beginA, endA), beginB, endB));
+      
+      // It must also work if we switch the of the spans
+      assertPosition(OVERLAPPING, (beginA, endA, beginB,
+              endB) -> overlaps(cas.createAnnotation(type, beginB, endB), beginA, endA));
+    }
+
+    @Test
+    public void thatOverlapsWithAnnotationsWorks() throws Exception
+    {
+      CAS cas = CasFactory.createCas();
+      Type type = cas.getAnnotationType();
+      
+      assertPosition(OVERLAPPING,
+              (beginA, endA, beginB, endB) -> overlaps(cas.createAnnotation(type, beginA, endA),
+                      cas.createAnnotation(type, beginB, endB)));
+      
+      // It must also work if we switch the of the spans
+      assertPosition(OVERLAPPING,
+              (beginA, endA, beginB, endB) -> overlaps(cas.createAnnotation(type, beginB, endB),
+                      cas.createAnnotation(type, beginA, endA)));
+    }
+    
+    @Test
+    public void thatLeftOfWithIntervalsWorks() throws Exception {
+      assertPosition(LEFT_OF, AnnotationPredicates::leftOf);
+    }
+
+    @Test
+    public void thatLeftOfWithAnnotationAndIntervalWorks() throws Exception {
+      CAS cas = CasFactory.createCas();
+      Type type = cas.getAnnotationType();
+      
+      assertPosition(LEFT_OF, (beginA, endA, beginB,
+              endB) -> leftOf(cas.createAnnotation(type, beginA, endA), beginB, endB));
+    }
+
+    @Test
+    public void thatLeftOfWithAnnotationsWorks() throws Exception {
+      CAS cas = CasFactory.createCas();
+      Type type = cas.getAnnotationType();
+      
+      assertPosition(LEFT_OF,
+              (beginA, endA, beginB, endB) -> leftOf(cas.createAnnotation(type, beginA, endA),
+                      cas.createAnnotation(type, beginB, endB)));
+    }
+
+    @Test
+    public void thatRightOfWithIntervalsWorks() throws Exception {
+      assertPosition(RIGHT_OF, AnnotationPredicates::rightOf);
+    }
+
+    @Test
+    public void thatRightOfWithAnnotationAndIntervalWorks() throws Exception {
+      CAS cas = CasFactory.createCas();
+      Type type = cas.getAnnotationType();
+
+      assertPosition(RIGHT_OF, (beginA, endA, beginB,
+              endB) -> rightOf(cas.createAnnotation(type, beginA, endA), beginB, endB));
+    }
+
+    @Test
+    public void thatRightOfWithAnnotationsWorks() throws Exception {
+      CAS cas = CasFactory.createCas();
+      Type type = cas.getAnnotationType();
+
+      assertPosition(RIGHT_OF,
+              (beginA, endA, beginB, endB) -> rightOf(cas.createAnnotation(type, beginA, endA),
+                      cas.createAnnotation(type, beginB, endB)));
+    }
+
+    @FunctionalInterface
+    private static interface RelativePositionPredicate {
+      boolean apply(int beginA, int endA, int beginB, int endB);
+    }
+
+    public void assertPosition(RelativePosition aCondition, RelativePositionPredicate aPredicate)
+            throws Exception {
+        // Define a fixed interval around which we build most of the tests by applying different
+        // selections to it
+        int begin = 10;
+        int end = 20;
+
+        try (AutoCloseableSoftAssertions softly = new AutoCloseableSoftAssertions()) {
+          softly.assertThat(aPredicate.apply(begin, end, begin - 1, begin - 1))
+              .as("Zero-width B before A begins (| ###)")
+              .isEqualTo(asList(RIGHT_OF).contains(aCondition));
+        
+          softly.assertThat(aPredicate.apply(begin, end, 0, begin - 1))
+              .as("B begins and ends before A begins ([---] ###)")
+              .isEqualTo(asList(RIGHT_OF).contains(aCondition));
+        
+          softly.assertThat(aPredicate.apply(begin, end, 0, begin))
+              .as("B begins before and ends where A begins ([---]###)")
+              .isEqualTo(asList(RIGHT_OF).contains(aCondition));
+
+          softly.assertThat(aPredicate.apply(begin, end, begin, begin))
+              .as("Zero-width B where A begins (|###)")
+              .isEqualTo(asList(OVERLAPPING, COVERING).contains(aCondition));
+
+          softly.assertThat(aPredicate.apply(begin, end, 0, begin + 1))
+              .as("B begins before and ends within A ([--#]##)")
+              .isEqualTo(asList(OVERLAPPING, OVERLAPPING_RIGHT).contains(aCondition));
+        
+          softly.assertThat(aPredicate.apply(begin, end, begin + 1, end - 1))
+              .as("B begins and ends within A (#[#]#)")
+              .isEqualTo(asList(OVERLAPPING, COVERING).contains(aCondition));
+
+          softly.assertThat(aPredicate.apply(begin, end, begin + 1, end))
+              .as("B begins after and ends at A's boundries (#[##])")
+              .isEqualTo(asList(OVERLAPPING, COVERING).contains(aCondition));
+
+          softly.assertThat(aPredicate.apply(begin, end, begin - 1, end + 1))
+              .as("B begins and ends at A's boundries ([-###-])")
+              .isEqualTo(asList(OVERLAPPING, COVERED_BY).contains(aCondition));
+          
+          softly.assertThat(aPredicate.apply(begin, end, begin, end))
+              .as("B begins and ends at A's boundries ([###])")
+              .isEqualTo(asList(OVERLAPPING, COLOCATED, COVERED_BY, COVERING).contains(aCondition));
+        
+          softly.assertThat(aPredicate.apply(begin, end, begin + 1, begin + 1))
+              .as("Zero-width B within A (#|#)")
+              .isEqualTo(asList(OVERLAPPING, COVERING).contains(aCondition));
+
+          softly.assertThat(aPredicate.apply(begin, end, begin, end - 1))
+              .as("B begins at and ends before A's boundries ([##]#)")
+              .isEqualTo(asList(OVERLAPPING, COVERING).contains(aCondition));
+
+          softly.assertThat(aPredicate.apply(begin, end, end - 1 , MAX_VALUE))
+              .as("B begins before and ends within A (##[#--])")
+              .isEqualTo(asList(OVERLAPPING, OVERLAPPING_LEFT).contains(aCondition));
+
+          softly.assertThat(aPredicate.apply(begin, end, end, MAX_VALUE))
+              .as("B begins at A's end and ends after A (###[---])")
+              .isEqualTo(asList(LEFT_OF).contains(aCondition));
+
+          softly.assertThat(aPredicate.apply(begin, end, end, end))
+              .as("Zero-width B at A's end (###|)")
+              .isEqualTo(asList(LEFT_OF).contains(aCondition));
+
+          softly.assertThat(aPredicate.apply(begin, end, end + 1, MAX_VALUE))
+              .as("B begins and ends after A (### [---])")
+              .isEqualTo(asList(LEFT_OF).contains(aCondition));
+        
+          softly.assertThat(aPredicate.apply(begin, end, end + 1, end + 1))
+              .as("Zero-width B after A's end (### |)")
+              .isEqualTo(asList(LEFT_OF).contains(aCondition));
+        
+          begin = 10;
+          end = 10;
+          
+          softly.assertThat(aPredicate.apply(begin, end, 20, 30))
+              .as("Zero-width A before B start (# [---])")
+              .isEqualTo(asList(LEFT_OF).contains(aCondition));
+    
+          softly.assertThat(aPredicate.apply(begin, end, 10, 20))
+              .as("Zero-width A at B's start (#---])")
+              .isEqualTo(asList(OVERLAPPING, COVERED_BY).contains(aCondition));
+          
+          softly.assertThat(aPredicate.apply(begin, end, 0, 10))
+              .as("Zero-width A at B's end ([---#)")
+              .isEqualTo(asList(RIGHT_OF).contains(aCondition));
+    
+          softly.assertThat(aPredicate.apply(begin, end, 10, 10))
+              .as("Zero-width A matches zero-width B start/end (#)")
+              .isEqualTo(asList(OVERLAPPING, COVERED_BY, COVERING, COLOCATED).contains(aCondition));
+          
+          softly.assertThat(aPredicate.apply(begin, end, 0, 5))
+              .as("Zero-width A after B's end ([---] #)")
+              .isEqualTo(asList(RIGHT_OF).contains(aCondition));
+        }
+    }
+}
\ No newline at end of file
diff --git a/uimafit-core/src/test/java/org/apache/uima/fit/util/JCasUtilTest.java b/uimafit-core/src/test/java/org/apache/uima/fit/util/JCasUtilTest.java
index c5b964c..12290b6 100644
--- a/uimafit-core/src/test/java/org/apache/uima/fit/util/JCasUtilTest.java
+++ b/uimafit-core/src/test/java/org/apache/uima/fit/util/JCasUtilTest.java
@@ -22,7 +22,9 @@
 package org.apache.uima.fit.util;
 
 import static java.util.Arrays.asList;
+import static java.util.stream.Collectors.toList;
 import static org.apache.uima.fit.factory.TypeSystemDescriptionFactory.createTypeSystemDescription;
+import static org.apache.uima.fit.util.AnnotationPredicates.overlaps;
 import static org.apache.uima.fit.util.JCasUtil.contains;
 import static org.apache.uima.fit.util.JCasUtil.exists;
 import static org.apache.uima.fit.util.JCasUtil.getAnnotationType;
@@ -36,11 +38,13 @@ import static org.apache.uima.fit.util.JCasUtil.selectBetween;
 import static org.apache.uima.fit.util.JCasUtil.selectCovered;
 import static org.apache.uima.fit.util.JCasUtil.selectCovering;
 import static org.apache.uima.fit.util.JCasUtil.selectFollowing;
+import static org.apache.uima.fit.util.JCasUtil.selectOverlapping;
 import static org.apache.uima.fit.util.JCasUtil.selectPreceding;
 import static org.apache.uima.fit.util.JCasUtil.selectSingle;
 import static org.apache.uima.fit.util.JCasUtil.selectSingleAt;
 import static org.apache.uima.fit.util.JCasUtil.selectSingleRelative;
 import static org.apache.uima.fit.util.JCasUtil.toText;
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -55,12 +59,16 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Random;
+import java.util.stream.Collectors;
 
 import org.apache.uima.UIMAException;
 import org.apache.uima.cas.CAS;
 import org.apache.uima.cas.FeatureStructure;
 import org.apache.uima.cas.Type;
+import org.apache.uima.cas.text.AnnotationFS;
 import org.apache.uima.fit.ComponentTestBase;
+import org.apache.uima.fit.factory.CasFactory;
+import org.apache.uima.fit.factory.JCasFactory;
 import org.apache.uima.fit.type.AnalyzedText;
 import org.apache.uima.fit.type.Sentence;
 import org.apache.uima.fit.type.Token;
@@ -71,6 +79,7 @@ import org.apache.uima.jcas.cas.NonEmptyFSList;
 import org.apache.uima.jcas.cas.TOP;
 import org.apache.uima.jcas.tcas.Annotation;
 import org.apache.uima.util.CasCreationUtils;
+import org.assertj.core.api.AutoCloseableSoftAssertions;
 import org.junit.Test;
 
 /**
@@ -180,6 +189,261 @@ public class JCasUtilTest extends ComponentTestBase {
     }
   }
 
+  @Test 
+  public void thatSelectOverlappingWorks() throws Exception
+  {
+      assertOverlapBehavior(JCasUtil::selectOverlapping);
+  }
+
+  @Test 
+  public void thatSelectOverlappingNaiveWorks() throws Exception
+  {
+      assertOverlapBehavior(JCasUtilTest::selectOverlappingNaive);
+  }
+
+  @Test 
+  public void thatSelectOverlappingNaiveV3Works() throws Exception
+  {
+      assertOverlapBehavior(JCasUtilTest::selectOverlappingNaiveV3);
+  }
+
+  @Test 
+  public void thatSelectOverlappingV3Works() throws Exception
+  {
+      assertOverlapBehavior(JCasUtilTest::selectOverlappingV3);
+  }
+
+  private static void assertOverlapBehavior(TypeByOffsetSelector aSelector) throws Exception
+  {
+      JCas jcas = JCasFactory.createJCas();
+      Token t = new Token(jcas, 10, 20);
+      t.addToIndexes();
+      
+
+      try (AutoCloseableSoftAssertions softly = new AutoCloseableSoftAssertions()) {
+        softly.assertThat(aSelector.select(jcas, Token.class, t.getBegin() - 1, t.getBegin() - 1))
+            .as("Zero-width selection before annotation begin (| ###)")
+            .isEmpty();
+      
+        softly.assertThat(aSelector.select(jcas, Token.class, 0, t.getBegin() - 1))
+            .as("Selection begins and ends before annotation begin ([---] ###)")
+            .isEmpty();
+      
+        softly.assertThat(aSelector.select(jcas, Token.class, 0, t.getBegin()))
+            .as("Selection begins before and ends at annotation begin ([---]###)")
+            .isEmpty();
+
+        softly.assertThat(aSelector.select(jcas, Token.class, t.getBegin(), t.getBegin()))
+            .as("Zero-width selection at annotation begin (|###)")
+            .containsExactly(t);
+
+        softly.assertThat(aSelector.select(jcas, Token.class, 0, t.getBegin() + 1))
+            .as("Selection begins before and ends within annotation ([--#]##)")
+            .containsExactly(t);
+      
+        softly.assertThat(aSelector.select(jcas, Token.class, t.getBegin() + 1, t.getEnd() - 1))
+            .as("Selection begins and ends within annotation  (#[#]#)")
+            .containsExactly(t);
+
+        softly.assertThat(aSelector.select(jcas, Token.class, t.getBegin() + 1, t.getEnd()))
+            .as("Selection begins after and ends at annotation boundries (#[##])")
+            .containsExactly(t);
+
+        softly.assertThat(aSelector.select(jcas, Token.class, t.getBegin(), t.getEnd()))
+            .as("Selection begins and ends at annotation boundries ([###])")
+            .containsExactly(t);
+      
+        softly.assertThat(aSelector.select(jcas, Token.class, t.getBegin()+1, t.getBegin()+1))
+            .as("Zero-width selection within annotation (#|#)")
+            .containsExactly(t);
+
+        softly.assertThat(aSelector.select(jcas, Token.class, t.getBegin(), t.getEnd() - 1))
+            .as("Selection begins at and ends before annotation boundries ([##]#)")
+            .containsExactly(t);
+
+        softly.assertThat(aSelector.select(jcas, Token.class, t.getEnd() - 1 , Integer.MAX_VALUE))
+            .as("Selection begins before and ends within annotation (##[#--])")
+            .containsExactly(t);
+
+        softly.assertThat(aSelector.select(jcas, Token.class, t.getEnd(), Integer.MAX_VALUE))
+            .as("Selection begins at annotation end and ends after annotation (###[---])")
+            .isEmpty();
+
+        softly.assertThat(aSelector.select(jcas, Token.class, t.getEnd(), t.getEnd()))
+            .as("Zero-width selection at annotation end (###|)")
+            .isEmpty();
+
+        softly.assertThat(aSelector.select(jcas, Token.class, t.getEnd() + 1, Integer.MAX_VALUE))
+            .as("Selection begins and ends after annotation (### [---])")
+            .isEmpty();
+      
+        softly.assertThat(aSelector.select(jcas, Token.class, t.getEnd() + 1, t.getEnd() + 1))
+            .as("Zero-width selection after annotation end (### |)")
+            .isEmpty();
+      
+        jcas.reset();
+        Token zero = new Token(jcas, 10, 10);
+        zero.addToIndexes();
+  
+        softly.assertThat(aSelector.select(jcas, Token.class, 20, 30))
+            .as("Zero-width annotation before selection start (# [---])")
+            .isEmpty();
+  
+        softly.assertThat(aSelector.select(jcas, Token.class, 10, 20))
+            .as("Zero-width annotation at selection start (#---])")
+            .containsExactly(zero);
+        
+        softly.assertThat(aSelector.select(jcas, Token.class, 0, 10))
+            .as("Zero-width annotation at selection end ([---#)")
+            .isEmpty();
+  
+        softly.assertThat(aSelector.select(jcas, Token.class, 10, 10))
+            .as("Zero-width annotation matches zero-width selection start/end (#)")
+            .containsExactly(zero);
+        
+        softly.assertThat(aSelector.select(jcas, Token.class, 0, 5))
+            .as("Zero-width annotation after selection end ([---] #)")
+            .isEmpty();
+      }
+  }
+
+  @Test
+  public void thatSelectOverlappingWorksOnRandomData() throws Exception
+  {
+      final int ITERATIONS = 10;
+
+      CAS cas = CasFactory.createCas();
+      
+      for (int i = 0; i < ITERATIONS; i++) {
+          initRandomCas(cas, 3 * i);
+
+          JCas jcas = cas.getJCas();
+          Collection<Sentence> sentences = select(jcas, Sentence.class);
+
+          long timeNaive = 0;
+          long timeNaiveV3 = 0;
+          long timeOptimized = 0;
+          long timeOptimizedV3 = 0;
+
+          for (Sentence s : sentences) {
+              // The naive approach is assumed to be correct
+              long ti = System.currentTimeMillis();
+              List<Token> expected = selectOverlappingNaive(jcas, Token.class, s.getBegin(),
+                      s.getEnd());
+              timeNaive += System.currentTimeMillis() - ti;
+
+              // Record time for the naive V3 approach
+              ti = System.currentTimeMillis();
+              List<Token> actualNaiveV3 = selectOverlappingNaiveV3(jcas, Token.class, s.getBegin(),
+                      s.getEnd());
+              timeNaiveV3 += System.currentTimeMillis() - ti;
+
+              // Record time for the optimized approach
+              ti = System.currentTimeMillis();
+              List<Token> actual = selectOverlapping(jcas, Token.class, s.getBegin(), s.getEnd());
+              timeOptimized += System.currentTimeMillis() - ti;
+
+              // Record time for the optimized v3 approach
+              ti = System.currentTimeMillis();
+              List<Token> actualV3 = selectOverlappingV3(jcas, Token.class, s.getBegin(),
+                      s.getEnd());
+              timeOptimizedV3 += System.currentTimeMillis() - ti;
+
+              assertThat(actual)
+                  .as("Selection           : [" + s.getBegin() + ".." + s.getEnd() + "]")
+                  .containsExactlyElementsOf(expected);
+              assertThat(actualNaiveV3)
+                  .as("Selection (naive V3): [" + s.getBegin() + ".." + s.getEnd() + "]")
+                  .containsExactlyElementsOf(expected);
+              assertThat(actualV3)
+                  .as("Selection (V3)      : [" + s.getBegin() + ".." + s.getEnd() + "]")
+                  .containsExactlyElementsOf(expected);
+          }
+
+          System.out.printf(
+                  "%3d Optimized   : speed up x%3.2f [baseline:%4d current:%4d (diff:%4d)]%n",
+                  i, (double) timeNaive / (double) timeOptimized, timeNaive, timeOptimized,
+                  timeNaive - timeOptimized);
+          System.out.printf(
+                  "%3d Naive V3    : speed up x%3.2f [baseline:%4d current:%4d (diff:%4d)]%n",
+                  i, (double) timeNaive / (double) timeNaiveV3, timeNaive, timeNaiveV3,
+                  timeNaive - timeNaiveV3);
+          System.out.printf(
+                  "%3d Optimized V3: speed up x%3.2f [baseline:%4d current:%4d (diff:%4d)]%n",
+                  i, (double) timeNaive / (double) timeOptimizedV3, timeNaive, timeOptimizedV3,
+                  timeNaive - timeOptimizedV3);
+          System.out.println();
+      }
+  }
+  
+  private static boolean naiveOverlappingCondition(AnnotationFS ann, int aSelBegin, int aSelEnd)
+  {
+    int begin = ann.getBegin();
+    int end = ann.getEnd();
+    
+    // Selection exactly matches annotation (even if empty)
+    if (begin == aSelBegin && end == aSelEnd) {
+      return true;
+    }
+    
+    boolean zeroWidthSelection = aSelBegin == aSelEnd;
+    return !(
+      // NOT Zero-width selection at end of annotation
+      (zeroWidthSelection && aSelBegin == end) ||
+      // NOT Zero-width annotation at end of selection
+      (begin == end && begin == aSelEnd)) 
+    && (
+      // Zero-width selection at beginning of annotation
+      (zeroWidthSelection && aSelBegin == begin) ||
+      // Annotation left-overlapping on selection
+      (aSelBegin <= begin && begin < aSelEnd) ||
+      // Annotation right overlapping on selection
+      (aSelBegin < end && end <= aSelEnd) ||
+      // Annotation fully containing selection
+      (begin <= aSelBegin && aSelEnd <= end) ||
+      // Selection fully containing annotation
+      (aSelBegin <= begin && end <= aSelEnd));
+  }
+
+  public static <T extends Annotation> List<T> selectOverlappingNaive(JCas aCas, Class<T> aType,
+          int aSelBegin, int aSelEnd) {
+      return select(aCas, aType).stream()
+              .filter(ann -> naiveOverlappingCondition(ann, aSelBegin, aSelEnd))
+              .collect(Collectors.toList());
+  }
+  
+  public static <T extends Annotation> List<T> selectOverlappingNaiveV3(JCas aCas, Class<T> aType,
+          int aSelBegin, int aSelEnd) {
+      return aCas.select(aType)
+              // Commented out because due to UIMA-6269, some annotations may be missed
+              // .coveredBy(0,  aSelEnd)
+              // .includeAnnotationsWithEndBeyondBounds()
+              .filter(ann -> naiveOverlappingCondition(ann, aSelBegin, aSelEnd))
+              .collect(Collectors.toList());
+  }
+
+  public static <T extends Annotation> List<T> selectOverlappingV3(JCas aCas, Class<T> aType,
+          int aSelBegin, int aSelEnd) {
+
+    return aCas.getAnnotationIndex(aType).select()
+            .filter(ann -> overlaps(ann, aSelBegin, aSelEnd))
+            .collect(toList());
+    
+    // These are alternative implementations, but just as the version above, they are also slow 
+    // compared to the implementation we use in CasUtil.selectOverlapping() right now.
+    // Also, they suffer from UIMA-6269, some annotations may be missed
+//      return aCas.getAnnotationIndex(aType).select()
+//            .coveredBy(0, aSelEnd)
+//            .includeAnnotationsWithEndBeyondBounds()
+//            .filter(ann -> isOverlapping(ann, aSelBegin, aSelEnd))
+//            .collect(Collectors.toList());
+
+//      return aCas.select(aType)
+//              .coveredBy(0,  aSelEnd)
+//              .includeAnnotationsWithEndBeyondBounds()
+//              .filter(ann -> isOverlapping(ann, aSelBegin, aSelEnd))
+//              .collect(Collectors.toList());
+  }
   /**
    * Test what happens if there is actually nothing overlapping with the Token.
    */
@@ -315,7 +579,9 @@ public class JCasUtilTest extends ComponentTestBase {
     // print(a1);
     // System.out.println("--- Optimized");
     // print(a2);
-    assertEquals("Container: [" + t.getBegin() + ".." + t.getEnd() + "]", a1, a2);
+    assertThat(a2)
+        .as("Container: [" + t.getBegin() + ".." + t.getEnd() + "]")
+        .containsExactlyElementsOf((Iterable) a1);
   }
 
   @Test
@@ -884,4 +1150,10 @@ public class JCasUtilTest extends ComponentTestBase {
     assertEquals(b.getBegin(), tokenAt.getBegin());
     assertEquals(b.getEnd(), tokenAt.getEnd());
   }
+  
+  @FunctionalInterface
+  private static interface TypeByOffsetSelector {
+    <T extends Annotation> List<T> select(JCas aCas, Class<T> aType,
+            int aBegin, int aEnd);
+  }
 }