You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@opennlp.apache.org by kr...@apache.org on 2026/08/22 01:01:08 UTC
(opennlp) 14/15: relation: Pin the shared required-layer contract for annotate
This is an automated email from the ASF dual-hosted git repository.
krickert pushed a commit to branch OPENNLP-1920-relation-extraction
in repository https://gitbox.apache.org/repos/asf/opennlp.git
commit 2cf63f4c4b48a20ba587c92dc58af7d51a2b3d4b
Author: Kristian Rickert <kr...@gmail.com>
AuthorDate: Sat Aug 8 19:05:41 2026 -0400
relation: Pin the shared required-layer contract for annotate
Every annotator in the container rejects an absent required layer with a
message naming it and accepts present but empty layers as valid input.
The absent-entity-layer and empty-layers tests fail until the relation
annotator is aligned with that contract.
---
.../tools/relation/RelationAnnotatorTest.java | 59 +++++++++++++++++++---
1 file changed, 53 insertions(+), 6 deletions(-)
diff --git a/opennlp-core/opennlp-runtime/src/test/java/opennlp/tools/relation/RelationAnnotatorTest.java b/opennlp-core/opennlp-runtime/src/test/java/opennlp/tools/relation/RelationAnnotatorTest.java
index ca334ee2a..259d375fc 100644
--- a/opennlp-core/opennlp-runtime/src/test/java/opennlp/tools/relation/RelationAnnotatorTest.java
+++ b/opennlp-core/opennlp-runtime/src/test/java/opennlp/tools/relation/RelationAnnotatorTest.java
@@ -34,7 +34,7 @@ import opennlp.tools.util.Span;
/**
* Verifies {@link RelationAnnotator} against a document with directly constructed token,
* entity, and dependency layers, covering matching, direction handling, duplicate and
- * invalid configurations, and documents without entities.
+ * invalid configurations, and the required-layer contract for absent and empty layers.
*/
public class RelationAnnotatorTest {
@@ -277,11 +277,12 @@ public class RelationAnnotatorTest {
}
/**
- * Verifies the behavior on a document without an entity layer: an absent layer reads
- * as an empty list, so the annotator succeeds and adds an empty relations layer.
+ * Builds the parsed document "Ada slept." with token and dependency layers only, so
+ * tests can attach an entity layer or leave it absent.
+ *
+ * @return A document without an entity layer. Never {@code null}.
*/
- @Test
- void testNoEntitiesProducesEmptyRelationsLayer() {
+ private static Document sleepingDocument() {
final String text = "Ada slept.";
final List<Annotation<String>> tokens = List.of(
new Annotation<>(new Span(0, 3), "Ada"),
@@ -295,9 +296,55 @@ public class RelationAnnotatorTest {
for (final DependencyArc arc : arcs) {
dependencies.add(new Annotation<>(tokens.get(arc.dependent()).span(), arc));
}
- final Document document = Document.of(text)
+ return Document.of(text)
.with(Layers.TOKENS, tokens)
.with(DependencyAnnotator.DEPENDENCIES, dependencies);
+ }
+
+ /**
+ * Verifies that a document lacking the entity layer is rejected the way every
+ * annotator in the container rejects an absent required layer: {@code requires()}
+ * names {@link Layers#ENTITIES}, so annotate throws the shared message naming the
+ * missing layer instead of reading absence as an empty layer.
+ */
+ @Test
+ void testAbsentEntityLayerIsRejectedWithTheLayerName() {
+ final RelationAnnotator annotator = new RelationAnnotator(List.of(
+ new RelationPattern("t", "<nsubj", null)));
+ final IllegalArgumentException e = Assertions.assertThrows(
+ IllegalArgumentException.class, () -> annotator.annotate(sleepingDocument()));
+ Assertions.assertEquals("document lacks the required layer " + Layers.ENTITIES,
+ e.getMessage());
+ }
+
+ /**
+ * Verifies that a present but empty entity layer is valid input: no entities means no
+ * pairs, so the annotator succeeds and adds an empty relations layer.
+ */
+ @Test
+ void testEmptyEntityLayerProducesEmptyRelationsLayer() {
+ final RelationAnnotator annotator = new RelationAnnotator(List.of(
+ new RelationPattern("t", "<nsubj", null)));
+
+ final Document annotated = annotator.annotate(
+ sleepingDocument().with(Layers.ENTITIES, List.of()));
+
+ Assertions.assertTrue(annotated.layers().contains(RelationAnnotator.RELATIONS));
+ Assertions.assertTrue(annotated.get(RelationAnnotator.RELATIONS).isEmpty());
+ }
+
+ /**
+ * Verifies the graceful degradation the annotator contract promises for documents
+ * without content: every required layer is present but empty, so the annotator
+ * succeeds and adds a present but empty relations layer instead of rejecting the
+ * document.
+ */
+ @Test
+ void testEmptyRequiredLayersDegradeToAnEmptyRelationsLayer() {
+ final Document document = Document.of("")
+ .with(Layers.TOKENS, List.of())
+ .with(Layers.ENTITIES, List.of())
+ .with(DependencyAnnotator.DEPENDENCIES, List.of());
final RelationAnnotator annotator = new RelationAnnotator(List.of(
new RelationPattern("t", "<nsubj", null)));