This is an automated email from the ASF dual-hosted git repository.

mawiesne pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opennlp.git


The following commit(s) were added to refs/heads/main by this push:
     new 6038bf503 OPENNLP-1892: Precompile Tag patterns to avoid repeated 
pattern compilation (#1187)
6038bf503 is described below

commit 6038bf503d35b6f90e6fa40a3c52994eb61a9d25
Author: subbudvk <[email protected]>
AuthorDate: Sun Jul 19 16:13:59 2026 +0530

    OPENNLP-1892: Precompile Tag patterns to avoid repeated pattern compilation 
(#1187)
    
    * Extracts and precompile tag patterns
    * Adds new tests related to pattern compilation
---
 .../parser/lang/es/AncoraSpanishHeadRules.java     | 53 +++++++++++-----
 .../parser/lang/es/AncoraSpanishHeadRulesTest.java | 72 ++++++++++++++++++++++
 2 files changed, 109 insertions(+), 16 deletions(-)

diff --git 
a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/parser/lang/es/AncoraSpanishHeadRules.java
 
b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/parser/lang/es/AncoraSpanishHeadRules.java
index 82b0d6d96..61e38595e 100644
--- 
a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/parser/lang/es/AncoraSpanishHeadRules.java
+++ 
b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/parser/lang/es/AncoraSpanishHeadRules.java
@@ -35,6 +35,7 @@ import java.util.Objects;
 import java.util.Set;
 import java.util.Stack;
 import java.util.StringTokenizer;
+import java.util.regex.Pattern;
 
 import opennlp.tools.parser.Constituent;
 import opennlp.tools.parser.GapLabeler;
@@ -84,6 +85,7 @@ public class AncoraSpanishHeadRules implements HeadRules, 
GapLabeler, Serializab
   private static class HeadRule {
     public final boolean leftToRight;
     public final String[] tags;
+    public final Pattern[] tagPatterns;
 
     public HeadRule(boolean l2r, String[] tags) {
       leftToRight = l2r;
@@ -93,6 +95,7 @@ public class AncoraSpanishHeadRules implements HeadRules, 
GapLabeler, Serializab
       }
 
       this.tags = tags;
+      this.tagPatterns = compile(tags);
     }
 
     @Override
@@ -116,6 +119,29 @@ public class AncoraSpanishHeadRules implements HeadRules, 
GapLabeler, Serializab
     }
   }
 
+  // Tag patterns are regexes (e.g. "AQA.*", "GRUP\\.A"), not literals, so 
they must be
+  // matched with Pattern/matches() semantics - they are precompiled once here 
(and once
+  // per HeadRule, see HeadRule.tagPatterns) instead of via String.matches(), 
which would
+  // otherwise recompile the same regex on every single constituent comparison.
+  private static final String[] TAGS1 =
+      {"AQA.*", "AQC.*", "GRUP\\.A", "S\\.A", "NC.*S.*", "NP.*", "NC.*P.*", 
"GRUP\\.NOM"};
+  private static final Pattern[] TAGS1_PATTERNS = compile(TAGS1);
+
+  private static final String[] TAGS2 = {"\\$", "GRUP\\.A", "SA"};
+  private static final Pattern[] TAGS2_PATTERNS = compile(TAGS2);
+
+  private static final String[] TAGS3 =
+      {"AQ0.*", "AQ[AC].*", "AO.*", "GRUP\\.A", "S\\.A", "RG", "RN", 
"GRUP\\.NOM"};
+  private static final Pattern[] TAGS3_PATTERNS = compile(TAGS3);
+
+  private static Pattern[] compile(String[] tags) {
+    Pattern[] patterns = new Pattern[tags.length];
+    for (int i = 0; i < tags.length; i++) {
+      patterns[i] = Pattern.compile(tags[i]);
+    }
+    return patterns;
+  }
+
   private Map<String, HeadRule> headRules;
   private final Set<String> punctSet;
 
@@ -150,11 +176,9 @@ public class AncoraSpanishHeadRules implements HeadRules, 
GapLabeler, Serializab
     }
     HeadRule hr;
     if (type.equals("SN") || type.equals("GRUP.NOM")) {
-      String[] tags1 = {"AQA.*","AQC.*","GRUP\\.A","S\\.A","NC.*S.*", 
"NP.*","NC.*P.*", "GRUP\\.NOM"};
-
       for (Parse constituent : constituents) {
-        for (int t = tags1.length - 1; t >= 0; t--) {
-          if (constituent.getType().matches(tags1[t])) {
+        for (int t = TAGS1_PATTERNS.length - 1; t >= 0; t--) {
+          if (TAGS1_PATTERNS[t].matcher(constituent.getType()).matches()) {
             return constituent;
           }
         }
@@ -164,18 +188,16 @@ public class AncoraSpanishHeadRules implements HeadRules, 
GapLabeler, Serializab
           return constituent;
         }
       }
-      String[] tags2 = {"\\$","GRUP\\.A","SA"};
       for (int ci = constituents.length - 1; ci >= 0; ci--) {
-        for (int ti = tags2.length - 1; ti >= 0; ti--) {
-          if (constituents[ci].getType().matches(tags2[ti])) {
+        for (int ti = TAGS2_PATTERNS.length - 1; ti >= 0; ti--) {
+          if 
(TAGS2_PATTERNS[ti].matcher(constituents[ci].getType()).matches()) {
             return constituents[ci];
           }
         }
       }
-      String[] tags3 = {"AQ0.*", 
"AQ[AC].*","AO.*","GRUP\\.A","S\\.A","RG","RN","GRUP\\.NOM"};
       for (int ci = constituents.length - 1; ci >= 0; ci--) {
-        for (int ti = tags3.length - 1; ti >= 0; ti--) {
-          if (constituents[ci].getType().matches(tags3[ti])) {
+        for (int ti = TAGS3_PATTERNS.length - 1; ti >= 0; ti--) {
+          if 
(TAGS3_PATTERNS[ti].matcher(constituents[ci].getType()).matches()) {
             return constituents[ci];
           }
         }
@@ -183,13 +205,12 @@ public class AncoraSpanishHeadRules implements HeadRules, 
GapLabeler, Serializab
       return constituents[constituents.length - 1].getHead();
     }
     else if ((hr = headRules.get(type)) != null) {
-      String[] tags = hr.tags;
+      Pattern[] tagPatterns = hr.tagPatterns;
       int cl = constituents.length;
-      int tl = tags.length;
       if (hr.leftToRight) {
-        for (String tag : tags) {
+        for (Pattern tagPattern : tagPatterns) {
           for (Parse constituent : constituents) {
-            if (constituent.getType().matches(tag)) {
+            if (tagPattern.matcher(constituent.getType()).matches()) {
               return constituent;
             }
           }
@@ -197,9 +218,9 @@ public class AncoraSpanishHeadRules implements HeadRules, 
GapLabeler, Serializab
         return constituents[0].getHead();
       }
       else {
-        for (String tag : tags) {
+        for (Pattern tagPattern : tagPatterns) {
           for (int ci = cl - 1; ci >= 0; ci--) {
-            if (constituents[ci].getType().matches(tag)) {
+            if (tagPattern.matcher(constituents[ci].getType()).matches()) {
               return constituents[ci];
             }
           }
diff --git 
a/opennlp-core/opennlp-runtime/src/test/java/opennlp/tools/parser/lang/es/AncoraSpanishHeadRulesTest.java
 
b/opennlp-core/opennlp-runtime/src/test/java/opennlp/tools/parser/lang/es/AncoraSpanishHeadRulesTest.java
new file mode 100644
index 000000000..bff63c50e
--- /dev/null
+++ 
b/opennlp-core/opennlp-runtime/src/test/java/opennlp/tools/parser/lang/es/AncoraSpanishHeadRulesTest.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package opennlp.tools.parser.lang.es;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import opennlp.tools.parser.Parse;
+import opennlp.tools.util.Span;
+
+public class AncoraSpanishHeadRulesTest {
+
+  /**
+   * getHead() for a rule loaded from a head-rules file must still honour 
regex tag
+   * patterns (e.g. "AQA.*") after switching from String.matches() to 
precompiled
+   * java.util.regex.Pattern - a literal tag like "AQA.*" must NOT be required 
to
+   * appear verbatim, only to match the pattern.
+   */
+  @Test
+  void testGetHeadMatchesRegexTagFromRuleFile() throws IOException {
+    // Rule for type "SA", right-to-left (dir=0), single tag pattern "AQA.*"
+    String rules = "3 SA 0 AQA.*\n";
+    AncoraSpanishHeadRules headRules = new AncoraSpanishHeadRules(new 
StringReader(rules));
+
+    Parse other = new Parse("text", new Span(0, 4), "OTHER", 1.0, 0);
+    // "AQA0" matches the regex "AQA.*" but is not equal to it - proves 
matching is
+    // still regex-based, not literal/equals-based, after the fix.
+    Parse matching = new Parse("text", new Span(5, 9), "AQA0", 1.0, 0);
+
+    Parse[] constituents = {other, matching};
+    Parse head = headRules.getHead(constituents, "SA");
+
+    Assertions.assertSame(matching, head);
+  }
+
+  /**
+   * getHead() for the hardcoded SN/GRUP.NOM branch must still honour its 
inline regex
+   * tag patterns (e.g. "GRUP\\.A") after they were hoisted into precompiled, 
static
+   * Pattern arrays.
+   */
+  @Test
+  void testGetHeadMatchesRegexTagInSnBranch() throws IOException {
+    AncoraSpanishHeadRules headRules = new AncoraSpanishHeadRules(new 
StringReader(""));
+
+    Parse other = new Parse("text", new Span(0, 4), "OTHER", 1.0, 0);
+    // "GRUP.A" matches the escaped-dot regex "GRUP\\.A" used in the SN 
branch's tags1.
+    Parse matching = new Parse("text", new Span(5, 11), "GRUP.A", 1.0, 0);
+
+    Parse[] constituents = {other, matching};
+    Parse head = headRules.getHead(constituents, "SN");
+
+    Assertions.assertSame(matching, head);
+  }
+}

Reply via email to