Merge branch 'main' into feature/UIMA-6311-Add-generated-resources-output-folder-as-resource-folder

* main:
  [UIMA-6312] Better PEAR parameter support
  [UIMA-6314] Align preceding/following with predicate in UIMA core
  [UIMA-6314] Align preceding/following with predicate in UIMA core
  [UIMA-6312] Better PEAR parameter support
diff --git a/uimafit-core/src/main/java/org/apache/uima/fit/factory/ConfigurationParameterFactory.java b/uimafit-core/src/main/java/org/apache/uima/fit/factory/ConfigurationParameterFactory.java
index 067de15..af9a86f 100644
--- a/uimafit-core/src/main/java/org/apache/uima/fit/factory/ConfigurationParameterFactory.java
+++ b/uimafit-core/src/main/java/org/apache/uima/fit/factory/ConfigurationParameterFactory.java
@@ -42,6 +42,7 @@
 import org.apache.uima.resource.metadata.NameValuePair;
 import org.apache.uima.resource.metadata.ResourceMetaData;
 import org.apache.uima.resource.metadata.impl.ConfigurationParameter_impl;
+import org.apache.uima.resource.metadata.impl.NameValuePair_impl;
 import org.springframework.beans.SimpleTypeConverter;
 import org.springframework.beans.TypeMismatchException;
 
@@ -548,8 +549,23 @@
         settings.put(p.getName(), p.getValue());
       }
     } else if (spec instanceof PearSpecifier) {
-      for (Parameter p : ((PearSpecifier) spec).getParameters()) {
-        settings.put(p.getName(), p.getValue());
+      PearSpecifier pearSpec = ((PearSpecifier) spec);
+      // Legacy parameters that only support string values.
+      Parameter[] parameters = pearSpec.getParameters();
+
+      if (parameters != null) {
+        for (Parameter parameter : parameters) {
+          settings.put(parameter.getName(), parameter.getValue());
+        }
+      }      
+
+      // Parameters supporting arbitrary objects as values
+      NameValuePair[] pearParameters = pearSpec.getPearParameters();
+
+      if (pearParameters != null) {
+        for (NameValuePair pearParameter : pearParameters) {
+          settings.put(pearParameter.getName(), pearParameter.getValue());
+        }
       }
     } else if (spec instanceof ResourceCreationSpecifier) {
       for (NameValuePair p : ((ResourceCreationSpecifier) spec).getMetaData()
@@ -609,23 +625,36 @@
     } else if (aSpec instanceof PearSpecifier) {
       PearSpecifier spec = (PearSpecifier) aSpec;
 
-      // If the parameter is already there, update it
       boolean found = false;
-      for (Parameter p : spec.getParameters()) {
+      
+      // Check modern parameters and if the parameter is present there, update it
+      NameValuePair[] parameters = spec.getPearParameters();
+      for (NameValuePair p : parameters) {
         if (p.getName().equals(name)) {
-          p.setValue((String) value);
+          p.setValue(value);
           found = true;
         }
       }
+      
+      // Check legacy parameters and if the parameter is present there, update it
+      Parameter[] legacyParameters = spec.getParameters();
+      if (legacyParameters != null) {
+        for (Parameter p : legacyParameters) {
+          if (p.getName().equals(name)) {
+            p.setValue((String) value);
+            found = true;
+          }
+        }
+      }
 
       // If the parameter is not there, add it
       if (!found) {
-        Parameter[] params = new Parameter[spec.getParameters().length + 1];
-        System.arraycopy(spec.getParameters(), 0, params, 0, spec.getParameters().length);
-        params[params.length - 1] = new Parameter_impl();
+        NameValuePair[] params = new NameValuePair[parameters.length + 1];
+        System.arraycopy(parameters, 0, params, 0, parameters.length);
+        params[params.length - 1] = new NameValuePair_impl();
         params[params.length - 1].setName(name);
-        params[params.length - 1].setValue((String) value);
-        spec.setParameters(params);
+        params[params.length - 1].setValue(value);
+        spec.setPearParameters(params);
       }
     } else if (aSpec instanceof ResourceCreationSpecifier) {
       ResourceMetaData md = ((ResourceCreationSpecifier) aSpec).getMetaData();
diff --git a/uimafit-core/src/main/java/org/apache/uima/fit/util/CasUtil.java b/uimafit-core/src/main/java/org/apache/uima/fit/util/CasUtil.java
index 50b0dfc..4194b11 100644
--- a/uimafit-core/src/main/java/org/apache/uima/fit/util/CasUtil.java
+++ b/uimafit-core/src/main/java/org/apache/uima/fit/util/CasUtil.java
@@ -203,7 +203,7 @@
   public static List<AnnotationFS> select(ArrayFS array, Type type) {
     final CAS cas = array.getCAS();
     requireAnnotationType(cas, type);
-    return (List) FSCollectionFactory.create(array, type);
+    return FSCollectionFactory.create(array, type);
   }
 
   /**
@@ -260,7 +260,7 @@
   @SuppressWarnings({ "unchecked", "rawtypes" })
   public static Collection<AnnotationFS> select(final CAS cas, final Type type) {
     requireAnnotationType(cas, type);
-    return (Collection) cas.getAnnotationIndex(type).select().asList();
+    return cas.getAnnotationIndex(type).select().asList();
   }
   
   /**
@@ -1208,11 +1208,18 @@
     }
     
     int anchorBegin = anchor.getBegin();
-    int anchorEnd = anchor.getEnd();
 
-    // No need to do additional seeks here (as done in selectCovered) because the current method
-    // does not have to worry about type priorities - it never returns annotations that have
-    // the same offset as the reference annotation.
+    // Zero-width annotations are in the index *after* the wider annotations starting at the same
+    // location, but we would consider a zero-width annotation at the beginning of a larger
+    // reference annotation to be preceding the larger one. So we need to seek right
+    // for any relevant zero-with annotations.
+    while (itr.isValid() && itr.get().getBegin() == anchorBegin) {
+      itr.moveToNext();
+      if (!itr.isValid()) {
+        itr.moveToLast();
+        break;
+      }
+    }
     
     // make sure we're past the beginning of the reference annotation
     while (itr.isValid() && itr.get().getEnd() > anchorBegin) {
@@ -1226,11 +1233,11 @@
       int curEnd = cur.getEnd();
 
       if (
-              curEnd <= anchorBegin && 
-              (cur.getBegin() != curEnd || anchorBegin != curEnd) &&
-              (anchorBegin != anchorEnd || curEnd != anchorBegin)
+              (curEnd <= anchorBegin
+              || (cur.getBegin() == curEnd && curEnd == anchorBegin))
+              && cur != anchor
       ) {
-        precedingAnnotations.add(itr.get());
+        precedingAnnotations.add(cur);
         i++;
       }
     }
@@ -1247,21 +1254,24 @@
    *          a CAS.
    * @param type
    *          a UIMA type.
-   * @param annotation
+   * @param anchor
    *          anchor annotation
    * @param count
    *          number of annotations to collect
    * @return List of aType annotations following anchor annotation
    * @see <a href="package-summary.html#SortOrder">Order of selected feature structures</a>
    */
-  public static List<AnnotationFS> selectFollowing(CAS cas, Type type, AnnotationFS annotation,
+  public static List<AnnotationFS> selectFollowing(CAS cas, Type type, AnnotationFS anchor,
           int count) {
     requireAnnotationType(cas, type);
 
     // Seek annotation in index
     // withSnapshotIterators() not needed here since we copy the FSes to a list anyway    
     FSIterator<AnnotationFS> itr = cas.getAnnotationIndex(type).iterator();
-    itr.moveTo(annotation);
+    itr.moveTo(anchor);
+    
+    int anchorBegin = anchor.getBegin();
+    int anchorEnd = anchor.getEnd();
 
     // When seeking forward, there is no need to check if the insertion point is beyond the
     // index. If it was, there would be nothing beyond it that could be found and returned.
@@ -1273,24 +1283,41 @@
     // does not have to worry about type priorities - it never returns annotations that have
     // the same offset as the reference annotation.
 
+    if (anchorBegin == anchorEnd) {
+      // zero-width annotations appear *after* larger annotations with the same start position in
+      // the index but the larger annotations are considered to be *following* the zero-width, so we
+      // have to look to the left for larger annotations...
+      if (itr.isValid()) {
+        itr.moveToPrevious();
+        while (itr.isValid() && itr.getNvc().getBegin() == anchorBegin) {
+          itr.moveToPrevious();
+        }
+        
+        if (!itr.isValid()) {
+          itr.moveToFirst();
+        }
+        else {
+          itr.moveToNext();
+        }
+      }
+      else {
+        itr.moveToFirst();
+      }
+    }
+    
     // make sure we're past the end of the reference annotation
-    while (itr.isValid() && itr.get().getBegin() < annotation.getEnd()) {
+    while (itr.isValid() && itr.get().getBegin() < anchorEnd) {
       itr.moveToNext();
     }
 
     // add annotations from the iterator into the result list
-    int refEnd = annotation.getEnd();
     List<AnnotationFS> followingAnnotations = new ArrayList<AnnotationFS>();
-    for (int i = 0; i < count && itr.isValid(); i++, itr.moveToNext()) {
-      AnnotationFS fs = itr.get();
-      int begin = fs.getBegin();
-      int end = fs.getEnd();
-      if (begin == end && refEnd == begin) {
-        // Skip zero-width annotation at the end of the reference annotation. These are considered
-        // to be "coveredBy" instead of following
-        continue;
+    for (int i = 0; i < count && itr.isValid(); itr.moveToNext()) {
+      AnnotationFS cur = itr.get();
+      if (cur != anchor && cur.getBegin() >= anchorEnd) {
+        followingAnnotations.add(cur);
+        i ++;
       }
-      followingAnnotations.add(itr.get());
     }
     
     return followingAnnotations;
diff --git a/uimafit-core/src/test/java/org/apache/uima/fit/factory/ConfigurationParameterFactoryTest.java b/uimafit-core/src/test/java/org/apache/uima/fit/factory/ConfigurationParameterFactoryTest.java
index e80bd68..f68fbd4 100644
--- a/uimafit-core/src/test/java/org/apache/uima/fit/factory/ConfigurationParameterFactoryTest.java
+++ b/uimafit-core/src/test/java/org/apache/uima/fit/factory/ConfigurationParameterFactoryTest.java
@@ -18,6 +18,8 @@
  */
 package org.apache.uima.fit.factory;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -28,9 +30,13 @@
 import java.io.File;
 import java.lang.reflect.Field;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import org.apache.uima.fit.descriptor.ConfigurationParameter;
+import org.apache.uima.resource.impl.Parameter_impl;
+import org.apache.uima.resource.impl.PearSpecifier_impl;
+import org.apache.uima.resource.metadata.impl.NameValuePair_impl;
 import org.junit.Test;
 
 public class ConfigurationParameterFactoryTest {
@@ -210,4 +216,52 @@
     assertArrayEquals(expected, actual);
   }
 
+  @SuppressWarnings("deprecation")
+  @Test
+  public void thatModernAndLegacyPearParametersArePickedUp() {
+    PearSpecifier_impl spec = new PearSpecifier_impl();
+    spec.setParameters( //
+            new Parameter_impl("legacyKey", "legacyValue"), //
+            new Parameter_impl("key", "false"));
+    spec.setPearParameters( //
+            new NameValuePair_impl("modernKey", 1), //
+            new NameValuePair_impl("key", true));
+    
+    Map<String, Object> params = ConfigurationParameterFactory.getParameterSettings(spec);
+    
+    assertThat(params).containsOnly( //
+            entry("legacyKey", "legacyValue"), //
+            entry("modernKey", 1), 
+            entry("key", true));
+  }
+
+  @Test
+  public void thatModernParametersAreUpdated() {
+    PearSpecifier_impl spec = new PearSpecifier_impl();
+    spec.setPearParameters(new NameValuePair_impl("key", 1));
+
+    ConfigurationParameterFactory.setParameter(spec, "key", 2);
+    
+    assertThat(spec.getPearParameters()).containsOnly(new NameValuePair_impl("key", 2));
+  }
+
+  @Test
+  public void thatModernParametersAreAdded() {
+    PearSpecifier_impl spec = new PearSpecifier_impl();
+
+    ConfigurationParameterFactory.setParameter(spec, "key", "value");
+    
+    assertThat(spec.getPearParameters()).containsOnly(new NameValuePair_impl("key", "value"));
+  }
+
+  @SuppressWarnings("deprecation")
+  @Test
+  public void thatLegacyParametersAreUpdated() {
+    PearSpecifier_impl spec = new PearSpecifier_impl();
+    spec.setParameters(new Parameter_impl("legacyKey", "legacyValue"));
+
+    ConfigurationParameterFactory.setParameter(spec, "legacyKey", "newLegacyValue");
+    
+    assertThat(spec.getParameters()).containsOnly(new Parameter_impl("legacyKey", "newLegacyValue"));
+  }
 }
diff --git a/uimafit-core/src/test/java/org/apache/uima/fit/util/AnnotationPredicateTestData.java b/uimafit-core/src/test/java/org/apache/uima/fit/util/AnnotationPredicateTestData.java
new file mode 100644
index 0000000..12dd85e
--- /dev/null
+++ b/uimafit-core/src/test/java/org/apache/uima/fit/util/AnnotationPredicateTestData.java
@@ -0,0 +1,137 @@
+/*
+ * 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.AnnotationPredicateTestData.RelativePosition.BEGINNING_WITH;
+import static org.apache.uima.fit.util.AnnotationPredicateTestData.RelativePosition.COLOCATED;
+import static org.apache.uima.fit.util.AnnotationPredicateTestData.RelativePosition.COVERED_BY;
+import static org.apache.uima.fit.util.AnnotationPredicateTestData.RelativePosition.COVERING;
+import static org.apache.uima.fit.util.AnnotationPredicateTestData.RelativePosition.ENDING_WITH;
+import static org.apache.uima.fit.util.AnnotationPredicateTestData.RelativePosition.FOLLOWING;
+import static org.apache.uima.fit.util.AnnotationPredicateTestData.RelativePosition.OVERLAPPING;
+import static org.apache.uima.fit.util.AnnotationPredicateTestData.RelativePosition.OVERLAPPING_AT_BEGIN;
+import static org.apache.uima.fit.util.AnnotationPredicateTestData.RelativePosition.OVERLAPPING_AT_END;
+import static org.apache.uima.fit.util.AnnotationPredicateTestData.RelativePosition.PRECEDING;
+
+import java.util.List;
+
+import org.apache.uima.fit.util.SelectionAssert.TestCase;
+
+public class AnnotationPredicateTestData {
+  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 TestCase T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13;
+  
+  public static final List<TestCase> NON_ZERO_WIDTH_TEST_CASES = asList(
+      T1 = new TestCase("1) Y begins and ends after X (### [---])", 
+          p -> p.apply(BEGIN, END, END + 1, MAX_VALUE),
+          asList(PRECEDING)),
+      T2 = new TestCase("2) Y begins at X's end and ends after X (###[---])", 
+          p -> p.apply(BEGIN, END, END, MAX_VALUE),
+          asList(PRECEDING)),
+      T3 = new TestCase("3) Y begins within and ends after X (##[#--])", 
+          p -> p.apply(BEGIN, END, END - 1 , MAX_VALUE),
+          asList(OVERLAPPING, OVERLAPPING_AT_BEGIN)),
+      T4 = 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)),
+      T5 = new TestCase("5) Y begins and ends within X (#[#]#)", 
+          p -> p.apply(BEGIN, END, BEGIN + 1, END - 1),
+          asList(OVERLAPPING, COVERING)),
+      T6 = 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, OVERLAPPING_AT_END)),
+      T7 = 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, OVERLAPPING_AT_BEGIN)),
+      T8 = 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)),
+      T9 = 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)),
+      T10 = 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)),
+      T11 = new TestCase("11) Y begins before and ends within X ([--#]##)", 
+          p -> p.apply(BEGIN, END, 0, BEGIN + 1),
+          asList(OVERLAPPING, OVERLAPPING_AT_END)),
+      T12 = new TestCase("12) Y begins before and ends where X begins ([---]###)", 
+          p -> p.apply(BEGIN, END, 0, BEGIN),
+          asList(FOLLOWING)),
+      T13 = new TestCase("13) Y begins and ends before X begins ([---] ###)", 
+          p -> p.apply(BEGIN, END, 0, BEGIN - 1),
+          asList(FOLLOWING)));
+
+  public static final TestCase TZ1, TZ2, TZ3, TZ4, TZ5, TZ6, TZ7, TZ8, TZ9, TZ10, TZ11;
+
+  public static final List<TestCase> ZERO_WIDTH_TEST_CASES = asList(
+      TZ1 = new TestCase("Z1) Zero-width X before Y start (# [---])", 
+          p -> p.apply(Z_POS, Z_POS, Z_POS + 10, Z_POS + 20),
+          asList(PRECEDING)),
+      TZ2 = new TestCase("Z2) Zero-width Y after X's end (### |)", 
+          p -> p.apply(BEGIN, END, END + 1, END + 1),
+          asList(PRECEDING)),
+      TZ3 = new TestCase("Z3) Zero-width X at Y's start (#---])", 
+          p -> p.apply(Z_POS, Z_POS, Z_POS, Z_POS + 10),
+          asList(PRECEDING, OVERLAPPING, COVERED_BY, BEGINNING_WITH)),
+      TZ4 = new TestCase("Z4) Zero-width X at Y's end ([---#)", 
+          p -> p.apply(Z_POS, Z_POS, Z_POS-10, Z_POS),
+          asList(FOLLOWING, OVERLAPPING, COVERED_BY, ENDING_WITH)),
+      TZ5 = new TestCase("Z5) Zero-width Y where X begins (|###)", 
+          p -> p.apply(BEGIN, END, BEGIN, BEGIN),
+          asList(FOLLOWING, OVERLAPPING, COVERING, BEGINNING_WITH)),
+      TZ6 = new TestCase("Z6) Zero-width Y within X (#|#)", 
+          p -> p.apply(BEGIN, END, BEGIN + 1, BEGIN + 1),
+          asList(OVERLAPPING, COVERING)),
+      TZ7 = new TestCase("Z7) Zero-width Y at X's end (###|)", 
+          p -> p.apply(BEGIN, END, END, END),
+          asList(PRECEDING, OVERLAPPING, COVERING, ENDING_WITH)),
+      TZ8 = 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)),
+      TZ9 = 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)),
+      TZ10 = new TestCase("Z10) Zero-width Y before X begins (| ###)", 
+          p -> p.apply(BEGIN, END, BEGIN - 1, BEGIN - 1),
+          asList(FOLLOWING)),
+      TZ11 = new TestCase("Z11) Zero-width X matches zero-width Y start/end (#)", 
+          p -> p.apply(Z_POS, Z_POS, Z_POS, Z_POS),
+          asList(FOLLOWING, PRECEDING, OVERLAPPING, COVERED_BY, COVERING, COLOCATED, BEGINNING_WITH, 
+              ENDING_WITH)));
+}
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 7ccf685..711de6d 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
@@ -27,6 +27,13 @@
 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.AnnotationPredicateTestData.NON_ZERO_WIDTH_TEST_CASES;
+import static org.apache.uima.fit.util.AnnotationPredicateTestData.ZERO_WIDTH_TEST_CASES;
+import static org.apache.uima.fit.util.AnnotationPredicateTestData.RelativePosition.COLOCATED;
+import static org.apache.uima.fit.util.AnnotationPredicateTestData.RelativePosition.COVERED_BY;
+import static org.apache.uima.fit.util.AnnotationPredicateTestData.RelativePosition.COVERING;
+import static org.apache.uima.fit.util.AnnotationPredicateTestData.RelativePosition.FOLLOWING;
+import static org.apache.uima.fit.util.AnnotationPredicateTestData.RelativePosition.PRECEDING;
 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;
@@ -41,15 +48,8 @@
 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.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;
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 5acf940..464ad56 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
@@ -858,7 +858,7 @@
   }
 
   @Test
-  public void thatSelectFollowingDoesNotFindZeroWidthAnnotationAtEnd()
+  public void thatSelectFollowingDoesFindZeroWidthAnnotationAtEnd()
   {
     Annotation a1 = new Annotation(jCas, 10, 20);
     Annotation a2 = new Annotation(jCas, 20, 20);
@@ -868,11 +868,11 @@
     List<Annotation> selection = selectFollowing(Annotation.class, a1, MAX_VALUE);
     
     assertThat(selection)
-            .isEmpty();
+            .containsExactly(a2);
   }
 
   @Test
-  public void thatSelectPrecedingDoesNotFindZeroWidthAnnotationAtStart()
+  public void thatSelectPrecedingDoesFindZeroWidthAnnotationAtStart()
   {
     Annotation a1 = new Annotation(jCas, 10, 20);
     Annotation a2 = new Annotation(jCas, 10, 10);
@@ -882,11 +882,11 @@
     List<Annotation> selection = selectPreceding(Annotation.class, a1, MAX_VALUE);
     
     assertThat(selection)
-            .isEmpty();
+            .containsExactly(a2);
   }
 
   @Test
-  public void thatSelectPrecedingOnZeroWidthDoesNotFindAnnotationEndingAtSameLocation()
+  public void thatSelectPrecedingOnZeroWidthDoesFindAnnotationEndingAtSameLocation()
   {
     Annotation a1 = new Annotation(jCas, 10, 20);
     Annotation a2 = new Annotation(jCas, 20, 20);
@@ -896,7 +896,7 @@
     List<Annotation> selection = selectPreceding(Annotation.class, a2, MAX_VALUE);
     
     assertThat(selection)
-            .isEmpty();
+            .containsExactly(a1);
   }
 
   @Test
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
index 2bf7b0e..f3b6697 100644
--- 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
@@ -18,18 +18,7 @@
  */
 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;
@@ -44,106 +33,13 @@
 import org.apache.uima.cas.CAS;
 import org.apache.uima.cas.Type;
 import org.apache.uima.cas.text.AnnotationFS;
+import org.apache.uima.fit.util.AnnotationPredicateTestData.RelativePosition;
 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 {