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:55 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 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>