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/11/04 07:32:54 UTC

[uima-uimafit] branch feature/UIMA-6270-Add-selectOverlapping-to-JCasUtil created (now acd427a)

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

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


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

This branch includes the following new commits:

     new acd427a  [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 feature/UIMA-6270-Add-selectOverlapping-to-JCasUtil
in repository https://gitbox.apache.org/repos/asf/uima-uimafit.git

commit acd427a5a59c6b088e676f42b29c906da38767cd
Author: Richard Eckart de Castilho <re...@apache.org>
AuthorDate: Wed Nov 4 08:32:26 2020 +0100

    [UIMA-6270] Add selectOverlapping to (J)CasUtil
    
    - Drop AnnotationPredicates again from uimaFIT and instead use the one meanwhile provided in uimaj-core
    - Updated various unit tests to check for alignment of uimaFIT with the annotation predicates
---
 .../apache/uima/fit/util/AnnotationPredicates.java | 217 ------------
 .../uima/fit/util/AnnotationPredicatesTest.java    | 382 ---------------------
 .../java/org/apache/uima/fit/util/CasUtilTest.java | 136 +++++++-
 .../org/apache/uima/fit/util/JCasUtilTest.java     |  78 +----
 .../org/apache/uima/fit/util/SelectionAssert.java  | 292 ++++++++++++++++
 uimafit-parent/pom.xml                             |   2 +-
 6 files changed, 430 insertions(+), 677 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
deleted file mode 100644
index 75c7385..0000000
--- a/uimafit-core/src/main/java/org/apache/uima/fit/util/AnnotationPredicates.java
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * 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/test/java/org/apache/uima/fit/util/AnnotationPredicatesTest.java b/uimafit-core/src/test/java/org/apache/uima/fit/util/AnnotationPredicatesTest.java
deleted file mode 100644
index a5bdfb0..0000000
--- a/uimafit-core/src/test/java/org/apache/uima/fit/util/AnnotationPredicatesTest.java
+++ /dev/null
@@ -1,382 +0,0 @@
-/*
- * 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/CasUtilTest.java b/uimafit-core/src/test/java/org/apache/uima/fit/util/CasUtilTest.java
index 769502e..7ccf685 100644
--- a/uimafit-core/src/test/java/org/apache/uima/fit/util/CasUtilTest.java
+++ b/uimafit-core/src/test/java/org/apache/uima/fit/util/CasUtilTest.java
@@ -18,24 +18,47 @@
  */
 package org.apache.uima.fit.util;
 
+import static java.lang.Integer.MAX_VALUE;
 import static java.util.Arrays.asList;
+import static java.util.stream.Collectors.toList;
+import static org.apache.uima.cas.text.AnnotationPredicates.colocated;
+import static org.apache.uima.cas.text.AnnotationPredicates.coveredBy;
+import static org.apache.uima.cas.text.AnnotationPredicates.covering;
+import static org.apache.uima.cas.text.AnnotationPredicates.following;
+import static org.apache.uima.cas.text.AnnotationPredicates.preceding;
 import static org.apache.uima.fit.factory.TypeSystemDescriptionFactory.createTypeSystemDescription;
+import static org.apache.uima.fit.util.CasUtil.exists;
 import static org.apache.uima.fit.util.CasUtil.getAnnotationType;
 import static org.apache.uima.fit.util.CasUtil.getType;
 import static org.apache.uima.fit.util.CasUtil.iterator;
 import static org.apache.uima.fit.util.CasUtil.iteratorFS;
 import static org.apache.uima.fit.util.CasUtil.select;
+import static org.apache.uima.fit.util.CasUtil.selectAt;
 import static org.apache.uima.fit.util.CasUtil.selectByIndex;
+import static org.apache.uima.fit.util.CasUtil.selectCovered;
+import static org.apache.uima.fit.util.CasUtil.selectCovering;
 import static org.apache.uima.fit.util.CasUtil.selectFS;
+import static org.apache.uima.fit.util.CasUtil.selectFollowing;
+import static org.apache.uima.fit.util.CasUtil.selectPreceding;
 import static org.apache.uima.fit.util.CasUtil.toText;
-import static org.apache.uima.fit.util.CasUtil.exists;
+import static org.apache.uima.fit.util.SelectionAssert.NON_ZERO_WIDTH_TEST_CASES;
+import static org.apache.uima.fit.util.SelectionAssert.ZERO_WIDTH_TEST_CASES;
+import static org.apache.uima.fit.util.SelectionAssert.assertSelection;
+import static org.apache.uima.fit.util.SelectionAssert.assertSelectionIsEqualOnRandomData;
+import static org.apache.uima.fit.util.SelectionAssert.RelativePosition.COLOCATED;
+import static org.apache.uima.fit.util.SelectionAssert.RelativePosition.COVERED_BY;
+import static org.apache.uima.fit.util.SelectionAssert.RelativePosition.COVERING;
+import static org.apache.uima.fit.util.SelectionAssert.RelativePosition.FOLLOWING;
+import static org.apache.uima.fit.util.SelectionAssert.RelativePosition.PRECEDING;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
+import java.util.List;
 
 import org.apache.uima.UIMAException;
 import org.apache.uima.cas.ArrayFS;
@@ -45,6 +68,7 @@ import org.apache.uima.cas.Type;
 import org.apache.uima.cas.text.AnnotationFS;
 import org.apache.uima.fit.ComponentTestBase;
 import org.apache.uima.fit.type.Token;
+import org.apache.uima.fit.util.SelectionAssert.TestCase;
 import org.apache.uima.jcas.cas.TOP;
 import org.apache.uima.jcas.tcas.Annotation;
 import org.apache.uima.util.CasCreationUtils;
@@ -55,6 +79,9 @@ import org.junit.Test;
  * 
  */
 public class CasUtilTest extends ComponentTestBase {
+  private List<TestCase> defaultPredicatesTestCases = union(NON_ZERO_WIDTH_TEST_CASES,
+          ZERO_WIDTH_TEST_CASES);
+  
   @Test
   public void testGetType() {
     String text = "Rot wood cheeses dew?";
@@ -205,4 +232,111 @@ public class CasUtilTest extends ComponentTestBase {
 
     assertTrue(exists(cas, tokenType));
   }
+  
+  @Test
+  public void thatSelectFollowingBehaviorAlignsWithPrecedingPredicate() throws Exception {
+    // In order to find annotations that X is preceding, we select the following annotations
+    assertSelection(
+        PRECEDING,
+        (cas, type, x, y) -> selectFollowing(cas, type, x, MAX_VALUE).contains(y),
+        defaultPredicatesTestCases);
+  }
+  
+  @Test
+  public void thatSelectPrecedingBehaviorAlignsWithPrecedingPredicateOnRandomData() throws Exception
+  {
+    assertSelectionIsEqualOnRandomData(
+        (cas, type, context) -> cas.getAnnotationIndex(type).select()
+            .filter(candidate -> preceding(candidate, context))
+            .collect(toList()),
+        (cas, type, context) -> selectPreceding(cas, type, context, MAX_VALUE));
+  }
+
+  @Test
+  public void thatSelectPrecedingBehaviorAlignsWithFollowingPredicate() throws Exception {
+    // In order to find annotations that X is following, we select the preceding annotations
+    assertSelection(
+        FOLLOWING,
+        (cas, type, x, y) -> selectPreceding(cas, type, x, MAX_VALUE).contains(y),
+        defaultPredicatesTestCases);
+  }
+  
+  @Test
+  public void thatSelectFollowingBehaviorAlignsWithFollowingPredicateOnRandomData() throws Exception
+  {
+    assertSelectionIsEqualOnRandomData(
+        (cas, type, context) -> cas.getAnnotationIndex(type).select()
+            .filter(candidate -> following(candidate, context))
+            .collect(toList()),
+        (cas, type, context) -> selectFollowing(cas, type, context, MAX_VALUE));
+  }
+
+  @Test
+  public void thatSelectCoveringBehaviorAlignsWithCoveredByPredicate() throws Exception {
+    // X covered by Y means that Y is covering X, so we need to select the covering annotations
+    // below.
+    assertSelection(
+        COVERED_BY,
+        (cas, type, x, y) -> selectCovering(cas, type, x).contains(y),
+        defaultPredicatesTestCases);
+  }
+  
+  @Test
+  public void thatSelectCoveredBehaviorAlignsWithCoveredByPredicateOnRandomData() throws Exception
+  {
+    assertSelectionIsEqualOnRandomData(
+        (cas, type, context) -> cas.getAnnotationIndex(type).select()
+            .filter(candidate -> coveredBy(candidate, context))
+            .collect(toList()),
+        (cas, type, context) -> selectCovered(cas, type, context));
+  }
+
+  @Test
+  public void thatSelectCoveredBehaviorAlignsWithCoveringPredicate() throws Exception {
+    // X covering Y means that Y is covered by Y, so we need to select the covered by annotations
+    // below.
+    assertSelection(
+        COVERING,
+        (cas, type, x, y) -> selectCovered(cas, type, x).contains(y),
+        defaultPredicatesTestCases);
+  }
+
+  @Test
+  public void thatSelectFsBehaviorAlignsWithCoveringPredicateOnRandomData() throws Exception
+  {
+    assertSelectionIsEqualOnRandomData(
+        (cas, type, context) -> cas.getAnnotationIndex(type).select()
+            .filter(candidate -> covering(candidate, context))
+            .collect(toList()),
+        (cas, type, context) -> selectCovering(cas, type, context));
+  }
+  
+  @Test
+  public void thatSelectAtBehaviorAlignsWithColocatedPredicate() throws Exception {
+    // X covering Y means that Y is covered by Y, so we need to select the covered by annotations
+    // below.
+    assertSelection(
+        COLOCATED,
+        (cas, type, x, y) -> selectAt(cas, type, x.getBegin(), x.getEnd()).contains(y),
+        defaultPredicatesTestCases);
+  }  
+
+  @Test
+  public void thatSelectAtBehaviorAlignsWithColocatedPredicateOnRandomData() throws Exception
+  {
+    assertSelectionIsEqualOnRandomData(
+        (cas, type, context) -> cas.getAnnotationIndex(type).select()
+            .filter(candidate -> colocated(candidate, context))
+            .collect(toList()),
+        (cas, type, context) -> selectAt(cas, type, context.getBegin(), context.getEnd()));
+  }
+  
+  @SafeVarargs
+  public static <T> List<T> union(List<T>... aLists) {
+      List<T> all = new ArrayList<>();
+      for (List<T> list : aLists) {
+        all.addAll(list);
+      }
+      return all;
+  }
 }
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 c3817a0..5acf940 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
@@ -23,9 +23,7 @@ package org.apache.uima.fit.util;
 
 import static java.lang.Integer.MAX_VALUE;
 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;
@@ -87,6 +85,7 @@ import org.junit.Test;
  * Test cases for {@link JCasUtil}.
  */
 public class JCasUtilTest extends ComponentTestBase {
+  
   /**
    * Test Tokens (Stems + Lemmas) overlapping with each other.
    */
@@ -202,25 +201,12 @@ public class JCasUtilTest extends ComponentTestBase {
       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 (| ###)")
@@ -322,9 +308,7 @@ public class JCasUtilTest extends ComponentTestBase {
           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
@@ -333,46 +317,20 @@ public class JCasUtilTest extends ComponentTestBase {
                       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();
       }
   }
@@ -413,38 +371,6 @@ public class JCasUtilTest extends ComponentTestBase {
               .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.
    */
@@ -1193,7 +1119,7 @@ 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,
diff --git a/uimafit-core/src/test/java/org/apache/uima/fit/util/SelectionAssert.java b/uimafit-core/src/test/java/org/apache/uima/fit/util/SelectionAssert.java
new file mode 100644
index 0000000..2bf7b0e
--- /dev/null
+++ b/uimafit-core/src/test/java/org/apache/uima/fit/util/SelectionAssert.java
@@ -0,0 +1,292 @@
+/*
+ * 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.SelectionAssert.RelativePosition.BEGINNING_WITH;
+import static org.apache.uima.fit.util.SelectionAssert.RelativePosition.COLOCATED;
+import static org.apache.uima.fit.util.SelectionAssert.RelativePosition.COVERED_BY;
+import static org.apache.uima.fit.util.SelectionAssert.RelativePosition.COVERING;
+import static org.apache.uima.fit.util.SelectionAssert.RelativePosition.ENDING_WITH;
+import static org.apache.uima.fit.util.SelectionAssert.RelativePosition.FOLLOWING;
+import static org.apache.uima.fit.util.SelectionAssert.RelativePosition.OVERLAPPING;
+import static org.apache.uima.fit.util.SelectionAssert.RelativePosition.OVERLAPPING_AT_BEGIN;
+import static org.apache.uima.fit.util.SelectionAssert.RelativePosition.OVERLAPPING_AT_END;
+import static org.apache.uima.fit.util.SelectionAssert.RelativePosition.PRECEDING;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.function.Function;
+
+import org.apache.uima.UIMAFramework;
+import org.apache.uima.cas.CAS;
+import org.apache.uima.cas.Type;
+import org.apache.uima.cas.text.AnnotationFS;
+import org.apache.uima.jcas.tcas.Annotation;
+import org.apache.uima.resource.metadata.TypeSystemDescription;
+import org.apache.uima.util.CasCreationUtils;
+import org.assertj.core.api.AutoCloseableSoftAssertions;
+
+public class SelectionAssert {
+  public static enum RelativePosition {
+    COLOCATED,
+    OVERLAPPING,
+    OVERLAPPING_AT_BEGIN,
+    OVERLAPPING_AT_END,
+    COVERING,
+    COVERED_BY,
+    PRECEDING,
+    FOLLOWING,
+    BEGINNING_WITH,
+    ENDING_WITH
+  }
+  
+  // Used as fixed references for the annotation relation cases.
+  private static final int BEGIN = 10;
+  private static final int END = 20;
+  private static final int Z_POS = 10;
+
+  public static final List<TestCase> NON_ZERO_WIDTH_TEST_CASES = asList(
+      new TestCase("1) Y begins and ends after X (### [---])", 
+          p -> p.apply(BEGIN, END, END + 1, MAX_VALUE),
+          asList(PRECEDING)),
+      new TestCase("2) Y begins at X's end and ends after X (###[---])", 
+          p -> p.apply(BEGIN, END, END, MAX_VALUE),
+          asList(PRECEDING)),
+      new TestCase("3) Y begins within and ends after X (##[#--])", 
+          p -> p.apply(BEGIN, END, END - 1 , MAX_VALUE),
+          asList(OVERLAPPING, OVERLAPPING_AT_BEGIN)),
+      new TestCase("4) Y begins and ends at X's boundries ([###])", 
+          p -> p.apply(BEGIN, END, BEGIN, END),
+          asList(OVERLAPPING, COLOCATED, COVERED_BY, COVERING, BEGINNING_WITH, ENDING_WITH)),
+      new TestCase("5) Y begins and ends within X (#[#]#)", 
+          p -> p.apply(BEGIN, END, BEGIN + 1, END - 1),
+          asList(OVERLAPPING, COVERING)),
+      new TestCase("6) Y begins at and ends before X's boundries ([##]#)", 
+          p -> p.apply(BEGIN, END, BEGIN, END - 1),
+          asList(OVERLAPPING, COVERING, BEGINNING_WITH)),
+      new TestCase("7) Y begins after and ends at X's boundries (#[##])", 
+          p -> p.apply(BEGIN, END, BEGIN + 1, END),
+          asList(OVERLAPPING, COVERING, ENDING_WITH)),
+      new TestCase("8) Y begins before and ends after X's boundries ([-###-])", 
+          p -> p.apply(BEGIN, END, BEGIN - 1, END + 1),
+          asList(OVERLAPPING, COVERED_BY)),
+      new TestCase("9) X starts where Y begins and ends within Y ([##-])", 
+          p -> p.apply(BEGIN, END, BEGIN, END + 1),
+          asList(OVERLAPPING, COVERED_BY, BEGINNING_WITH)),
+      new TestCase("10) X starts within Y and ends where Y ends ([-##])", 
+          p -> p.apply(BEGIN, END, BEGIN - 1, END),
+          asList(OVERLAPPING, COVERED_BY, ENDING_WITH)),
+      new TestCase("11) Y begins before and ends within X ([--#]##)", 
+          p -> p.apply(BEGIN, END, 0, BEGIN + 1),
+          asList(OVERLAPPING, OVERLAPPING_AT_END)),
+      new TestCase("12) Y begins before and ends where X begins ([---]###)", 
+          p -> p.apply(BEGIN, END, 0, BEGIN),
+          asList(FOLLOWING)),
+      new TestCase("13) Y begins and ends before X begins ([---] ###)", 
+          p -> p.apply(BEGIN, END, 0, BEGIN - 1),
+          asList(FOLLOWING)));
+
+  public static final List<TestCase> ZERO_WIDTH_TEST_CASES = asList(
+      new TestCase("Z1) Zero-width X before Y start (# [---])", 
+          p -> p.apply(Z_POS, Z_POS, Z_POS + 10, Z_POS + 20),
+          asList(PRECEDING)),
+      new TestCase("Z2) Zero-width Y after X's end (### |)", 
+          p -> p.apply(BEGIN, END, END + 1, END + 1),
+          asList(PRECEDING)),
+      new TestCase("Z3) Zero-width X at Y's start (#---])", 
+          p -> p.apply(Z_POS, Z_POS, Z_POS, Z_POS + 10),
+          asList(OVERLAPPING, COVERED_BY, BEGINNING_WITH)),
+      new TestCase("Z4) Zero-width X at Y's end ([---#)", 
+          p -> p.apply(Z_POS, Z_POS, Z_POS-10, Z_POS),
+          asList(OVERLAPPING, COVERED_BY, ENDING_WITH)),
+      new TestCase("Z5) Zero-width Y where X begins (|###)", 
+          p -> p.apply(BEGIN, END, BEGIN, BEGIN),
+          asList(OVERLAPPING, COVERING, BEGINNING_WITH)),
+      new TestCase("Z6) Zero-width Y within X (#|#)", 
+          p -> p.apply(BEGIN, END, BEGIN + 1, BEGIN + 1),
+          asList(OVERLAPPING, COVERING)),
+      new TestCase("Z7) Zero-width Y at X's end (###|)", 
+          p -> p.apply(BEGIN, END, END, END),
+          asList(OVERLAPPING, COVERING, ENDING_WITH)),
+      new TestCase("Z8) Zero-width X with Y (-|-)", 
+          p -> p.apply(Z_POS, Z_POS, Z_POS - 5, Z_POS + 5),
+          asList(OVERLAPPING, COVERED_BY)),
+      new TestCase("Z9) Zero-width X after Y's end ([---] #)", 
+          p -> p.apply(Z_POS, Z_POS, Z_POS - 10, Z_POS - 5),
+          asList(FOLLOWING)),
+      new TestCase("Z10) Zero-width Y before X begins (| ###)", 
+          p -> p.apply(BEGIN, END, BEGIN - 1, BEGIN - 1),
+          asList(FOLLOWING)),
+      new TestCase("Z11) Zero-width X matches zero-width Y start/end (#)", 
+          p -> p.apply(Z_POS, Z_POS, Z_POS, Z_POS),
+          asList(OVERLAPPING, COVERED_BY, COVERING, COLOCATED, BEGINNING_WITH, ENDING_WITH)));
+  
+  public static void assertSelection(RelativePosition aCondition, RelativeAnnotationPredicate aPredicate, 
+      List<TestCase> aTestCases)
+      throws Exception {
+    CAS cas = CasCreationUtils.createCas();
+    Type type = cas.getAnnotationType();
+
+    try (AutoCloseableSoftAssertions softly = new AutoCloseableSoftAssertions()) {
+      for (TestCase testCase : aTestCases) {
+        cas.reset();
+
+        // Create annotations
+        Annotation x = (Annotation) cas.createAnnotation(type, 0, 0);
+        Annotation y = (Annotation) cas.createAnnotation(type, 0, 0);
+
+        // Position the annotations according to the test data
+        testCase.getTest().apply((beginA, endA, beginB, endB) -> {
+          x.setBegin(beginA);
+          x.setEnd(endA);
+          y.setBegin(beginB);
+          y.setEnd(endB);
+          cas.addFsToIndexes(x);
+          cas.addFsToIndexes(y);
+          return true;
+        });
+
+        softly.assertThat(aPredicate.apply(cas, type, x, y)).as(testCase.getDescription())
+            .isEqualTo(testCase.getValidPositions().contains(aCondition));
+      }
+    }
+  }
+
+  public static void assertSelectionIsEqualOnRandomData(TypeByContextSelector aExpected, TypeByContextSelector aActual)
+      throws Exception {
+    final int ITERATIONS = 30;
+    final int TYPES = 5;
+
+    TypeSystemDescription tsd = UIMAFramework.getResourceSpecifierFactory().createTypeSystemDescription();
+    
+    Map<String, Type> types = new LinkedHashMap<>();
+    for (int i = 0; i < TYPES; i++) {
+      String typeName = "test.Type" + (i + 1);
+      tsd.addType(typeName, "", CAS.TYPE_NAME_ANNOTATION);
+      types.put(typeName, null);
+    }
+    
+    CAS randomCas = CasCreationUtils.createCas(tsd, null, null, null);
+
+    for (String typeName : types.keySet()) {
+      types.put(typeName, randomCas.getTypeSystem().getType(typeName));
+    }
+    
+    System.out.print("Iteration: ");
+    try {
+      Iterator<Type> ti = types.values().iterator();
+      Type type1 = ti.next();
+      Type type2 = ti.next();
+      
+      for (int i = 0; i < ITERATIONS; i++) {
+        if (i % 10 == 0) {
+          System.out.print(i);
+        }
+        else {
+          System.out.print(".");
+        }
+  
+        initRandomCas(randomCas, 3 * i, 0, types.values().toArray(new Type[types.size()]));
+  
+        for (Annotation context : randomCas.<Annotation>select(type1)) {
+          List<AnnotationFS> expected = aExpected.select(randomCas, type2, context);
+          List<AnnotationFS> actual = aActual.select(randomCas, type2, context);
+  
+          assertThat(actual)
+              .as("Selected [%s] with context [%s]@[%d..%d]", type2.getShortName(), 
+                  type1.getShortName(), context.getBegin(), context.getEnd())
+              .containsExactlyElementsOf(expected);
+        }
+      }
+      System.out.print(ITERATIONS);
+    }
+    finally {
+      System.out.println();
+    }
+  }
+
+  private static void initRandomCas(CAS aCas, int aSize, int aMinimumWidth, Type... aTypes) {
+    Random rnd = new Random();
+
+    List<Type> types = new ArrayList<>(asList(aTypes));
+
+    // Shuffle the types
+    for (int n = 0; n < 10; n++) {
+      Type t = types.remove(rnd.nextInt(types.size()));
+      types.add(t);
+    }
+
+    // Randomly generate annotations
+    for (int n = 0; n < aSize; n++) {
+      for (Type t : types) {
+        int begin = rnd.nextInt(100);
+        int end = begin + rnd.nextInt(30) + aMinimumWidth;
+        aCas.addFsToIndexes(aCas.createAnnotation(t, begin, end));
+      }
+    }
+  }
+
+  @FunctionalInterface
+  public static interface RelativeAnnotationPredicate {
+    boolean apply(CAS cas, Type type, Annotation x, Annotation y);
+  }
+
+  @FunctionalInterface
+  public static interface TypeByContextSelector {
+    List<AnnotationFS> select(CAS aCas, Type aType, Annotation aContext);
+  }
+  
+  @FunctionalInterface
+  public static interface RelativePositionPredicate {
+    boolean apply(int beginA, int endA, int beginB, int endB);
+  }
+
+  public static class TestCase {
+    private final String description;
+
+    private final Function<RelativePositionPredicate, Boolean> predicate;
+    
+    private final List<RelativePosition> validPositions;
+
+    public TestCase(String aDescription, Function<RelativePositionPredicate, Boolean> aPredicate, List<RelativePosition> aValidPositions) {
+      description = aDescription;
+      predicate = aPredicate;
+      validPositions = aValidPositions;
+    }
+
+    public String getDescription() {
+      return description;
+    }
+
+    public Function<RelativePositionPredicate, Boolean> getTest() {
+      return predicate;
+    }
+    
+    public List<RelativePosition> getValidPositions() {
+      return validPositions;
+    }
+  }
+}
diff --git a/uimafit-parent/pom.xml b/uimafit-parent/pom.xml
index 4f6ba2e..6cba377 100644
--- a/uimafit-parent/pom.xml
+++ b/uimafit-parent/pom.xml
@@ -33,7 +33,7 @@
   <inceptionYear>2012</inceptionYear>
   <properties>
     <spring.version>4.3.26.RELEASE</spring.version>
-    <uima.version>3.1.1</uima.version>
+    <uima.version>3.1.2-SNAPSHOT</uima.version>
     <slf4j.version>1.7.26</slf4j.version>
     <maven.compiler.source>1.8</maven.compiler.source>
     <maven.compiler.target>1.8</maven.compiler.target>