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)));