Merge branch 'maintenance/3.1.x' into bugfix/UIMA-6404-Ruta-anchor-with-quantifier-ignores-matches
diff --git a/ruta-core/.gitignore b/ruta-core/.gitignore
new file mode 100644
index 0000000..862d276
--- /dev/null
+++ b/ruta-core/.gitignore
@@ -0,0 +1,2 @@
+input/
+TypeSystem.xml
diff --git a/ruta-core/src/main/java/org/apache/uima/ruta/RutaStream.java b/ruta-core/src/main/java/org/apache/uima/ruta/RutaStream.java
index 3778925..a6e28b1 100644
--- a/ruta-core/src/main/java/org/apache/uima/ruta/RutaStream.java
+++ b/ruta-core/src/main/java/org/apache/uima/ruta/RutaStream.java
@@ -1151,7 +1151,11 @@
 

     if (cas.getTypeSystem().subsumes(type, windowAnnotation.getType())) {

       if (!sensitiveToVisibility || isVisible(windowAnnotation)) {

-        result.add(windowAnnotation);

+        // the window defined by a BLOCK could actually have already been removed, thus we do not

+        // want to return it

+        if (cas.getAnnotationIndex(windowAnnotation.getType()).contains(windowAnnotation)) {

+          result.add(windowAnnotation);

+        }

       }

     }

 

diff --git a/ruta-core/src/main/java/org/apache/uima/ruta/engine/RutaTestUtils.java b/ruta-core/src/main/java/org/apache/uima/ruta/engine/RutaTestUtils.java
index 5769578..d5230a4 100644
--- a/ruta-core/src/main/java/org/apache/uima/ruta/engine/RutaTestUtils.java
+++ b/ruta-core/src/main/java/org/apache/uima/ruta/engine/RutaTestUtils.java
@@ -19,14 +19,18 @@
 

 package org.apache.uima.ruta.engine;

 

+import static org.apache.uima.fit.factory.TypeSystemDescriptionFactory.createTypeSystemDescription;

+

 import java.io.File;

 import java.io.FileOutputStream;

 import java.io.IOException;

 import java.io.OutputStream;

+import java.lang.management.ManagementFactory;

 import java.net.URISyntaxException;

 import java.net.URL;

 import java.util.ArrayList;

 import java.util.Collection;

+import java.util.Collections;

 import java.util.HashMap;

 import java.util.Iterator;

 import java.util.LinkedHashMap;

@@ -34,6 +38,7 @@
 import java.util.Map;

 import java.util.Map.Entry;

 import java.util.Set;

+import java.util.regex.Pattern;

 

 import org.apache.uima.UIMAFramework;

 import org.apache.uima.analysis_engine.AnalysisEngine;

@@ -60,6 +65,19 @@
 

 public class RutaTestUtils {

 

+  public static final boolean DEBUG_MODE = isDebugging();

+

+  private static boolean isDebugging() {

+

+    Pattern debugPattern = Pattern.compile("-Xdebug|jdwp");

+    for (String arg : ManagementFactory.getRuntimeMXBean().getInputArguments()) {

+      if (debugPattern.matcher(arg).find()) {

+        return true;

+      }

+    }

+    return false;

+  }

+

   public static class TestFeature {

     public String name;

 

@@ -151,24 +169,8 @@
     AnalysisEngineDescription aed = (AnalysisEngineDescription) specifier;

 

     TypeSystemDescription basicTypeSystem = aed.getAnalysisEngineMetaData().getTypeSystem();

-    for (int i = 1; i <= amount; i++) {

-      basicTypeSystem.addType(TYPE + i, "Type for Testing", "uima.tcas.Annotation");

-    }

-

-    if (complexTypes != null) {

-      Set<Entry<String, String>> entrySet = complexTypes.entrySet();

-      for (Entry<String, String> entry : entrySet) {

-        String name = entry.getKey();

-        TypeDescription addType = basicTypeSystem.addType(name, "Type for Testing",

-                entry.getValue());

-        if (features != null) {

-          List<TestFeature> list = features.get(name);

-          for (TestFeature f : list) {

-            addType.addFeature(f.name, f.description, f.range);

-          }

-        }

-      }

-    }

+    addTestTypes(basicTypeSystem);

+    addAdditionalTypes(complexTypes, features, basicTypeSystem);

 

     Collection<TypeSystemDescription> tsds = new ArrayList<TypeSystemDescription>();

     tsds.add(basicTypeSystem);

@@ -248,23 +250,8 @@
     ResourceSpecifier specifier = UIMAFramework.getXMLParser().parseResourceSpecifier(in);

     AnalysisEngineDescription aed = (AnalysisEngineDescription) specifier;

     TypeSystemDescription basicTypeSystem = aed.getAnalysisEngineMetaData().getTypeSystem();

-    for (int i = 1; i <= 50; i++) {

-      basicTypeSystem.addType("org.apache.uima.T" + i, "Type for Testing", "uima.tcas.Annotation");

-    }

-    if (complexTypes != null) {

-      Set<Entry<String, String>> entrySet = complexTypes.entrySet();

-      for (Entry<String, String> entry : entrySet) {

-        String name = entry.getKey();

-        TypeDescription addType = basicTypeSystem.addType(name, "Type for Testing",

-                entry.getValue());

-        if (features != null) {

-          List<TestFeature> list = features.get(name);

-          for (TestFeature f : list) {

-            addType.addFeature(f.name, f.description, f.range);

-          }

-        }

-      }

-    }

+    addTestTypes(basicTypeSystem);

+    addAdditionalTypes(complexTypes, features, basicTypeSystem);

     Collection<TypeSystemDescription> tsds = new ArrayList<TypeSystemDescription>();

     tsds.add(basicTypeSystem);

     TypeSystemDescription mergeTypeSystems = CasCreationUtils.mergeTypeSystems(tsds);

@@ -282,6 +269,31 @@
     return cas;

   }

 

+  public static void addTestTypes(TypeSystemDescription typeSystemDescription) {

+    for (int i = 1; i <= 50; i++) {

+      typeSystemDescription.addType("org.apache.uima.T" + i, "Type for Testing",

+              "uima.tcas.Annotation");

+    }

+  }

+

+  private static void addAdditionalTypes(Map<String, String> complexTypes,

+          Map<String, List<TestFeature>> features, TypeSystemDescription typeSystemDescription) {

+    if (complexTypes != null) {

+      Set<Entry<String, String>> entrySet = complexTypes.entrySet();

+      for (Entry<String, String> entry : entrySet) {

+        String name = entry.getKey();

+        TypeDescription addType = typeSystemDescription.addType(name, "Type for Testing",

+                entry.getValue());

+        if (features != null) {

+          List<TestFeature> list = features.get(name);

+          for (TestFeature f : list) {

+            addType.addFeature(f.name, f.description, f.range);

+          }

+        }

+      }

+    }

+  }

+

   public static void printAnnotations(CAS cas, int typeId) {

     Type t = getTestType(cas, typeId);

     AnnotationIndex<AnnotationFS> ai = cas.getAnnotationIndex(t);

@@ -366,4 +378,34 @@
     }

   }

 

+  public static void storeTypeSystem() {

+    storeTypeSystem(Collections.emptyMap(), Collections.emptyMap());

+  }

+

+  public static void storeTypeSystem(Map<String, String> complexTypes,

+          Map<String, List<TestFeature>> features) {

+

+    File tsFile = new File("TypeSystem.xml");

+

+    try {

+

+      TypeSystemDescription typeSystemDescription = createTypeSystemDescription();

+      addTestTypes(typeSystemDescription);

+      addAdditionalTypes(complexTypes, features, typeSystemDescription);

+      try (OutputStream os = new FileOutputStream(tsFile)) {

+        typeSystemDescription.toXML(os);

+      }

+    } catch (Exception e) {

+      throw new IllegalStateException(e);

+    }

+  }

+

+  public static Map<String, Object> getDebugParams() {

+    Map<String, Object> params = new LinkedHashMap<>();

+    params.put(RutaEngine.PARAM_DEBUG, true);

+    params.put(RutaEngine.PARAM_DEBUG_WITH_MATCHES, true);

+    params.put(RutaEngine.PARAM_CREATED_BY, true);

+    return params;

+  }

+

 }

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 f85c809..6b99ab1 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
@@ -126,8 +126,8 @@
     return result;

   }

 

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

-          InferenceCrowd crowd) {

+  protected void doneMatching(RuleMatch ruleMatch, RuleApply ruleApply, RuleElement entryPoint,

+          RutaStream stream, InferenceCrowd crowd) {

     if (!ruleMatch.isApplied()) {

       ruleApply.add(ruleMatch, stream);

       RutaRule rule = ruleMatch.getRule();

@@ -135,7 +135,8 @@
       if (ruleMatch.matchedCompletely()) {

         rule.getEnvironment().acceptTempVariableValues(localVariables);

         rule.getRoot().applyRuleElements(ruleMatch, stream, crowd);

-      } else {

+      } else if (entryPoint == null) {

+        // there was no dynamic lookahead, remove temp vars

         rule.getEnvironment().clearTempVariables(localVariables);

       }

       ruleMatch.setApplied(true);

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..a38318c 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
@@ -534,7 +534,7 @@
 

     // take care that failed matches wont be applied

     ruleMatch.setMatched(ruleMatch.matched && !failed);

-    doneMatching(ruleMatch, ruleApply, stream, crowd);

+    doneMatching(ruleMatch, ruleApply, entryPoint, stream, crowd);

     return asList(ruleMatch);

   }

 

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 f616ab7..d4abcc6 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
@@ -110,7 +110,7 @@
               result.addAll(fallbackContinue);

             } else if (getContainer() instanceof RuleElementIsolator) {

               // TODO move and refactor this:

-              doneMatching(extendedMatch, ruleApply, stream, crowd);

+              doneMatching(extendedMatch, ruleApply, entryPoint, stream, crowd);

             }

           }

         }

@@ -322,7 +322,7 @@
           result.addAll(fallbackContinue);

         } else {

           // should never happen!

-          doneMatching(ruleMatch, ruleApply, stream, crowd);

+          doneMatching(ruleMatch, ruleApply, entryPoint, stream, crowd);

         }

       }

     }

@@ -335,8 +335,7 @@
           InferenceCrowd crowd) {

     RuleElementMatch result = new RuleElementMatch(this, containerMatch);

     result.setRuleAnchor(ruleAnchor);

-    List<EvaluatedCondition> evaluatedConditions = new ArrayList<>(

-            conditions.size());

+    List<EvaluatedCondition> evaluatedConditions = new ArrayList<>(conditions.size());

     // boolean base = matcher.match(annotation, stream, getParent());

     boolean base = true;

     MatchContext context = new MatchContext(annotation, this, ruleMatch, after);

diff --git a/ruta-core/src/test/java/org/apache/uima/ruta/action/UnmarkTest.java b/ruta-core/src/test/java/org/apache/uima/ruta/action/UnmarkTest.java
index 64b7152..3eb4289 100644
--- a/ruta-core/src/test/java/org/apache/uima/ruta/action/UnmarkTest.java
+++ b/ruta-core/src/test/java/org/apache/uima/ruta/action/UnmarkTest.java
@@ -20,6 +20,7 @@
 package org.apache.uima.ruta.action;

 

 import java.util.ArrayList;

+import java.util.LinkedHashMap;

 import java.util.List;

 import java.util.Map;

 import java.util.TreeMap;

@@ -43,45 +44,70 @@
 

     cas.release();

   }

-  

-  

+

   @Test

   public void testAnnotationExpression() throws Exception {

-    Map<String, String> typeMap = new TreeMap<String, String>();

+    Map<String, String> typeMap = new LinkedHashMap<String, String>();

     typeMap.put("Complex", "uima.tcas.Annotation");

     Map<String, List<TestFeature>> featureMap = new TreeMap<String, List<TestFeature>>();

-    List<TestFeature> list = new ArrayList<RutaTestUtils.TestFeature>();

+    List<TestFeature> list = new ArrayList<>();

     featureMap.put("Complex", list);

     list.add(new TestFeature("inner", "", "uima.tcas.Annotation"));

-    

+

     CAS cas = RutaTestUtils.getCAS("This is a test.", typeMap, featureMap);

     String script = "";

     script += "CW{->T1};t:T1 SW SW{-> UNMARK(t)};";

     script += "CW{->T2};\n t:T2 # PERIOD{-> Complex, Complex.inner=t};\n Complex{-> UNMARK(Complex.inner)};\n";

     Ruta.apply(cas, script);

-    

+

     RutaTestUtils.assertAnnotationsEquals(cas, 1, 0);

     RutaTestUtils.assertAnnotationsEquals(cas, 2, 0);

-    

+

   }

-  

+

   @Test

-  public void testAnnotationListExpression()  throws Exception {

-    Map<String, String> typeMap = new TreeMap<String, String>();

+  public void testAnnotationListExpression() throws Exception {

+    Map<String, String> typeMap = new LinkedHashMap<String, String>();

     typeMap.put("Complex", "uima.tcas.Annotation");

     Map<String, List<TestFeature>> featureMap = new TreeMap<String, List<TestFeature>>();

-    List<TestFeature> list = new ArrayList<RutaTestUtils.TestFeature>();

+    List<TestFeature> list = new ArrayList<>();

     featureMap.put("Complex", list);

     list.add(new TestFeature("inner", "", "uima.cas.FSArray"));

-    

+

     CAS cas = RutaTestUtils.getCAS("This is a test.", typeMap, featureMap);

     String script = "";

     script += "W{->T1}; Document{-> Complex, Complex.inner = T1};";

     script += "Complex{-> UNMARK(Complex.inner)};\n";

     Ruta.apply(cas, script);

-    

+

     RutaTestUtils.assertAnnotationsEquals(cas, 1, 0);

     RutaTestUtils.assertAnnotationsEquals(cas, 2, 0);

   }

-  

+

+  @Test

+  public void testUnmarkWithFeatureMatchInBlock() throws Exception {

+

+    Map<String, String> typeMap = new LinkedHashMap<String, String>();

+    typeMap.put("Struct", "uima.tcas.Annotation");

+    Map<String, List<TestFeature>> featureMap = new TreeMap<String, List<TestFeature>>();

+    List<TestFeature> list = new ArrayList<>();

+    featureMap.put("Struct", list);

+    list.add(new TestFeature("s", "", CAS.TYPE_NAME_STRING));

+

+    CAS cas = RutaTestUtils.getCAS("This is a test.", typeMap, featureMap);

+    String script = "\"a\"{->s:Struct,Struct.s=\"foo\"};";

+    script += "BLOCK(SoftRemove) Struct.s==\"foo\"{} {\r\n"

+            + "    t:Struct.s==\"foo\"{-> UNMARK(t)};\r\n" //

+            + "    t:Struct.s==\"foo\"{-> T1}; \r\n" + "}";

+

+    Ruta.apply(cas, script, RutaTestUtils.getDebugParams());

+

+    if (RutaTestUtils.DEBUG_MODE) {

+      RutaTestUtils.storeTypeSystem(typeMap, featureMap);

+      RutaTestUtils.storeCas(cas, "testUnmarkWithFeatureMatchInBlock");

+    }

+

+    RutaTestUtils.assertAnnotationsEquals(cas, 1, 0);

+  }

+

 }

diff --git a/ruta-core/src/test/java/org/apache/uima/ruta/rule/WildCard2Test.java b/ruta-core/src/test/java/org/apache/uima/ruta/rule/WildCard2Test.java
index 38dabdf..03bd641 100644
--- a/ruta-core/src/test/java/org/apache/uima/ruta/rule/WildCard2Test.java
+++ b/ruta-core/src/test/java/org/apache/uima/ruta/rule/WildCard2Test.java
@@ -275,13 +275,12 @@
   public void testLabelForFailedLookahead() throws Exception {

     String document = "A x B x C x D";

     String script = "(w1:CW{REGEXP(\"A\")} # w2:CW{REGEXP(\"C\")})->{w1{->T1};};";

-	

-	CAS cas = RutaTestUtils.getCAS(document, null, null, false);

-    Ruta.apply(cas, script);

-	

-	RutaTestUtils.assertAnnotationsEquals(cas, 1, 1, "A");

-  }

 

+    CAS cas = RutaTestUtils.getCAS(document, null, null, false);

+    Ruta.apply(cas, script);

+

+    RutaTestUtils.assertAnnotationsEquals(cas, 1, 1, "A");

+  }

 

   @Test

   public void testLastElementAlsoAnnotatedWithLookahead() throws Exception {

@@ -297,4 +296,33 @@
     RutaTestUtils.assertAnnotationsEquals(cas, 4, 2, "c", "c");

   }

 

+  @Test

+  public void testLookaheadWithFeatureMatch() throws Exception {

+    String document = "a 2 b 3 c 4 d";

+    String script = "";

+    script += "\"2\"{->s:Struct,s.s=\"x\"};\n";

+    script += "\"3\"{->s:Struct};\n";

+    script += "\"4\"{->s:Struct,s.s=\"y\"};\n";

+    script += "s1:Struct.s==\"x\" # s2:Struct.s==\"y\"{->s2.s=s1.s, T1};\n";

+    script += "s:Struct.s==\"x\"{->T2};\n";

+

+    Map<String, String> complexType = new HashMap<>();

+    complexType.put("Struct", CAS.TYPE_NAME_ANNOTATION);

+    Map<String, List<TestFeature>> featureMap = new HashMap<>();

+    List<TestFeature> list = new ArrayList<>();

+    list.add(new TestFeature("s", "", CAS.TYPE_NAME_STRING));

+    featureMap.put("Struct", list);

+

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

+    Ruta.apply(cas, script);

+

+    if (RutaTestUtils.DEBUG_MODE) {

+      RutaTestUtils.storeTypeSystem(complexType, featureMap);

+      RutaTestUtils.storeCas(cas, "testLookaheadWithFeatureMatch");

+    }

+

+    RutaTestUtils.assertAnnotationsEquals(cas, 1, 1, "4");

+    RutaTestUtils.assertAnnotationsEquals(cas, 2, 2, "2", "4");

+  }

+

 }