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