UIMA-6414: Ruta: missing match for optional after sidestep out of composed

- only follow side step before composed fallback, if the origin is within the current container
diff --git a/ruta-core/src/main/java/org/apache/uima/ruta/rule/RutaRuleElement.java b/ruta-core/src/main/java/org/apache/uima/ruta/rule/RutaRuleElement.java
index a438cca..d90a999 100644
--- a/ruta-core/src/main/java/org/apache/uima/ruta/rule/RutaRuleElement.java
+++ b/ruta-core/src/main/java/org/apache/uima/ruta/rule/RutaRuleElement.java
@@ -201,7 +201,9 @@
     if (nextRuleElement != null) {

       result = nextRuleElement.continueMatch(after, eachAnchor, extendedMatch, ruleApply,

               extendedContainerMatch, sideStepOrigin, entryPoint, stream, crowd);

-    } else if (sideStepOrigin != null && !failed) {

+    } else if (sideStepOrigin != null && !failed && containedIn(sideStepOrigin, getContainer())) {

+      // continue directly with the sidestep if it is contained in this container

+      // if not, we might miss matches in the same direction

       result = sideStepOrigin.continueSideStep(after, extendedMatch, ruleApply,

               extendedContainerMatch, entryPoint, stream, crowd);

     } else if (getContainer() instanceof ComposedRuleElement) {

@@ -212,6 +214,26 @@
     return result;

   }

 

+  private boolean containedIn(RuleElement sideStepOrigin, RuleElementContainer container) {

+    // TODO: should we support this in interface?

+    if (container == null || sideStepOrigin == null) {

+      return false;

+    }

+    List<RuleElement> ruleElements = container.getRuleElements();

+    if (ruleElements.contains(sideStepOrigin)) {

+      return true;

+    } else {

+      for (RuleElement ruleElement : ruleElements) {

+        if (ruleElement instanceof RuleElementContainer) {

+          if (containedIn(sideStepOrigin, (RuleElementContainer) ruleElement)) {

+            return true;

+          }

+        }

+      }

+    }

+    return false;

+  }

+

   @Override

   public List<RuleMatch> continueMatch(boolean after, AnnotationFS annotation, RuleMatch ruleMatch,

           RuleApply ruleApply, ComposedRuleElementMatch containerMatch, RuleElement sideStepOrigin,

diff --git a/ruta-core/src/test/java/org/apache/uima/ruta/rule/ManualAnchoringTest.java b/ruta-core/src/test/java/org/apache/uima/ruta/rule/ManualAnchoringTest.java
index 64b7561..385478e 100644
--- a/ruta-core/src/test/java/org/apache/uima/ruta/rule/ManualAnchoringTest.java
+++ b/ruta-core/src/test/java/org/apache/uima/ruta/rule/ManualAnchoringTest.java
@@ -38,6 +38,19 @@
 
     RutaTestUtils.assertAnnotationsEquals(cas, 3, 1, "A, B and C");
 
-    cas.release();
+  }
+
+  @Test
+  public void testComposedInSequence() throws Exception {
+    String text = "bla CAP 1-2 bla";
+
+    String script = "FOREACH(cap) CAP{}{";
+    script += "ANY{-PARTOF(SPECIAL)} @cap (NUM SPECIAL NUM){-> T1} ANY{-PARTOF(SPECIAL)};";
+    script += "}";
+
+    CAS cas = RutaTestUtils.getCAS(text);
+    Ruta.apply(cas, script);
+
+    RutaTestUtils.assertAnnotationsEquals(cas, 1, 1, "1-2");
   }
 }