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:03 UTC

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

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);
+  }
 }