Merge pull request #74 from apache/bugfix/UIMA-6414-Ruta-missing-match-for-optional-after-sidestep-out-of-composed

UIMA-6414: Ruta: missing match for optional after sidestep out of composed
diff --git a/ruta-core/src/main/java/org/apache/uima/ruta/rule/AbstractRuleElement.java b/ruta-core/src/main/java/org/apache/uima/ruta/rule/AbstractRuleElement.java
index 6e134e1..f367d61 100644
--- a/ruta-core/src/main/java/org/apache/uima/ruta/rule/AbstractRuleElement.java
+++ b/ruta-core/src/main/java/org/apache/uima/ruta/rule/AbstractRuleElement.java
@@ -113,12 +113,16 @@
       } else {

         RuleElement nextRuleElement = getContainer().getNextElement(newDirection, this);

         if (nextRuleElement != null) {

+          RuleElement sideStepOrigin = null;

+          if (getContainer() instanceof RuleElement) {

+            sideStepOrigin = (RuleElement) getContainer();

+          }

           result = nextRuleElement.continueMatch(newDirection, annotation, ruleMatch, ruleApply,

-                  sideStepContainerMatch, null, null, stream, crowd);

+                  sideStepContainerMatch, sideStepOrigin, entryPoint, stream, crowd);

         } else if (getContainer() instanceof ComposedRuleElement) {

           ComposedRuleElement composed = (ComposedRuleElement) getContainer();

           result = composed.fallbackContinue(newDirection, false, annotation, ruleMatch, ruleApply,

-                  sideStepContainerMatch, null, entryPoint, stream, crowd);

+                  sideStepContainerMatch, composed, entryPoint, stream, crowd);

         }

       }

     }

@@ -127,7 +131,9 @@
 

   protected void doneMatching(RuleMatch ruleMatch, RuleApply ruleApply, RutaStream stream,

           InferenceCrowd crowd) {

-    if (!ruleMatch.isApplied()) {

+    // do not execute actions if they already have been or if this is just a lookahead

+    // (ruleApply==null)

+    if (!ruleMatch.isApplied() && ruleApply != null) {

       ruleApply.add(ruleMatch, stream);

       if (ruleMatch.matchedCompletely()) {

         RutaRule rule = ruleMatch.getRule();

diff --git a/ruta-core/src/main/java/org/apache/uima/ruta/rule/ComposedRuleElement.java b/ruta-core/src/main/java/org/apache/uima/ruta/rule/ComposedRuleElement.java
index e25038b..ade9153 100644
--- a/ruta-core/src/main/java/org/apache/uima/ruta/rule/ComposedRuleElement.java
+++ b/ruta-core/src/main/java/org/apache/uima/ruta/rule/ComposedRuleElement.java
@@ -527,7 +527,8 @@
               containerMatch, sideStepOrigin, entryPoint, stream, crowd);

     }

 

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

+    if (sideStepOrigin != null && !failed && sideStepOrigin.getContainer() != null) {

+      // only continue sidestep if we did not yet reach the root

       return sideStepOrigin.continueSideStep(after, ruleMatch, ruleApply, containerMatch,

               entryPoint, stream, crowd);

     }

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/ConjunctiveRuleElementTest.java b/ruta-core/src/test/java/org/apache/uima/ruta/ConjunctiveRuleElementTest.java
index 02c7bff..0dd61f2 100644
--- a/ruta-core/src/test/java/org/apache/uima/ruta/ConjunctiveRuleElementTest.java
+++ b/ruta-core/src/test/java/org/apache/uima/ruta/ConjunctiveRuleElementTest.java
@@ -97,7 +97,7 @@
   }

 

   @Test

-  public void testWithStartAnchor() {

+  public void testWithStartAnchor() throws Exception {

 

     String document = "Peter did something.";

     String script = "";

@@ -120,18 +120,12 @@
     String fn3 = "tense";

     list.add(new TestFeature(fn3, "", "uima.cas.String"));

 

-    CAS cas = null;

-    try {

-      cas = RutaTestUtils.getCAS(document, typeMap, featureMap);

-      Ruta.apply(cas, script);

-    } catch (Exception e) {

-      e.printStackTrace();

-    }

+    CAS cas = RutaTestUtils.getCAS(document, typeMap, featureMap);

+    Ruta.apply(cas, script);

 

     RutaTestUtils.assertAnnotationsEquals(cas, 1, 1, "Peter did");

     RutaTestUtils.assertAnnotationsEquals(cas, 2, 1, "Peter did");

     RutaTestUtils.assertAnnotationsEquals(cas, 3, 1, "did something");

 

-    cas.release();

   }

 }

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..e7d4a6c 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,31 @@
 
     RutaTestUtils.assertAnnotationsEquals(cas, 3, 1, "A, B and C");
 
-    cas.release();
   }
+
+  @Test
+  public void testAcrossComposedInSequence() 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");
+  }
+
+  @Test
+  public void testLeaveComposedInSequence() throws Exception {
+    String text = "bla w CAP w bla";
+    String script = "(W @CAP W) {->T1} ANY{-PARTOF(NUM)};";
+
+    CAS cas = RutaTestUtils.getCAS(text);
+    Ruta.apply(cas, script);
+
+    RutaTestUtils.assertAnnotationsEquals(cas, 1, 1, "w CAP w");
+  }
+
 }
diff --git a/ruta-core/src/test/java/org/apache/uima/ruta/rule/SidestepInComposedTest.java b/ruta-core/src/test/java/org/apache/uima/ruta/rule/SidestepInComposedTest.java
index c990a44..c97b34c 100644
--- a/ruta-core/src/test/java/org/apache/uima/ruta/rule/SidestepInComposedTest.java
+++ b/ruta-core/src/test/java/org/apache/uima/ruta/rule/SidestepInComposedTest.java
@@ -66,4 +66,18 @@
 

   }

 

+  @Test

+  public void testOptionalBeforeComposed() throws Exception {

+    String document = "test 05/05 test\n";

+    document += "test 06/06 . test\n";

+    document += "test . 07/07 test\n";

+    String script = "_{-PARTOF(PM)} (NUM SPECIAL @NUM){-> T1} _{-PARTOF({PM})};\n";

+

+    CAS cas = RutaTestUtils.getCAS(document);

+    Ruta.apply(cas, script);

+

+    RutaTestUtils.assertAnnotationsEquals(cas, 1, 1, "05/05");

+

+  }

+

 }

diff --git a/ruta-core/src/test/java/org/apache/uima/ruta/rule/quantifier/MinMaxQuantifierTest.java b/ruta-core/src/test/java/org/apache/uima/ruta/rule/quantifier/MinMaxQuantifierTest.java
index facef59..84a8086 100644
--- a/ruta-core/src/test/java/org/apache/uima/ruta/rule/quantifier/MinMaxQuantifierTest.java
+++ b/ruta-core/src/test/java/org/apache/uima/ruta/rule/quantifier/MinMaxQuantifierTest.java
@@ -49,10 +49,10 @@
     CAS cas = RutaTestUtils.getCAS(document);
     Ruta.apply(cas, script);
 
-    if (RutaTestUtils.DEBUG_MODE) {
-      RutaTestUtils.storeTypeSystem();
-      RutaTestUtils.storeCas(cas, "testMinMaxOnComposedWithAnchor");
-    }
+//    if (RutaTestUtils.DEBUG_MODE) {
+//      RutaTestUtils.storeTypeSystem();
+//      RutaTestUtils.storeCas(cas, "testMinMaxOnComposedWithAnchor");
+//    }
 
     RutaTestUtils.assertAnnotationsEquals(cas, 1, 3, "1 2 3 4 5 6 7 8", "2 3 4 5 6 7 8 9",
             "3 4 5 6 7 8 9 10");