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