[ 
https://issues.apache.org/jira/browse/OPENNLP-1926?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Kristian Rickert updated OPENNLP-1926:
--------------------------------------
    Description: 
OpenNLP's processing path is RegEx-free by policy; a RegEx step that is the 
class's purpose (for example {{{}RegexNameFinder{}}}) is the documented 
exception (OPENNLP-1925). An audit of main found the production classes below 
touching {{{}java.util.regex{}}}, and the work turned up a second group: 
{{{}String.split{}}}, {{{}matches{}}}, {{{}replaceAll{}}}, and {{replaceFirst}} 
calls that compile a Pattern on each call without an import. This epic replaces 
each incidental usage with cursor or code-point loops and ends with a 
checkstyle guard so it stays that way. Classes whose job IS pattern matching 
are exempt and listed for completeness.
h3. Tickets, in merge order

Each branch is stacked on the one before it, so the pull requests merge in this 
order and OPENNLP-1935 merges last.
||Ticket||Scope||PR||
|OPENNLP-1928|part 1: trivial batch, plus the shared {{StringUtil}} helpers 
{{{}isAsciiWhitespace{}}}, {{{}splitOnAsciiWhitespace{}}}, 
{{{}containsAsciiUpperCase{}}}, {{containsAsciiDigit}}|apache/opennlp#1275|
|OPENNLP-1930|part 2: Arvores Deitadas markup parsing|apache/opennlp#1276|
|OPENNLP-1931|part 3: JSON vocabulary and id2label scrape in 
opennlp-dl|apache/opennlp#1277|
|OPENNLP-1932|part 4: wildcard matching in the model 
resolver|apache/opennlp#1278|
|OPENNLP-1933|part 5: per-call String regex splits and 
replacements|apache/opennlp#1279|
|OPENNLP-1929|bug: {{BasicContextGenerator}} splits on its separator as a 
regular expression|apache/opennlp#1280|
|OPENNLP-1934|part 6: tokenizer alphanumeric pattern evaluated as a character 
set|apache/opennlp#1281|
|OPENNLP-1935|part 7: checkstyle guard and the exempt list in 
{{checkstyle-suppressions.xml}}|apache/opennlp#1282|
h3. In scope: incidental usage to replace
||Class||Module||Regex usage||Ticket||
|{{{}DefaultLemmatizerContextGenerator{}}}, 
{{DefaultPOSContextGenerator}}|opennlp-runtime|{{[A-Z]}} / {{[0-9]}} presence 
checks|OPENNLP-1928|
|{{FeatureGeneratorUtil}}|opennlp-runtime|{{^[A-ZÄÖÜ]\.$}}|OPENNLP-1928|
|{{TokenPatternFeatureGenerator}}|opennlp-runtime|{{[^a-zA-Z]}} 
strip|OPENNLP-1928|
|{{{}BioCodec{}}}, {{NameFinderME}}|opennlp-runtime|{{(.{+})-\w{+}}} outcome 
split|OPENNLP-1928|
|{{BrownCluster}}|opennlp-runtime|split on tab|OPENNLP-1928|
|{{lang.en.TokenSampleStream}}|opennlp-runtime|{{split("}}
{{{}s+"){}}}, {{[A-Za-z0-9]}} check|OPENNLP-1928|
|{{ConlluStream}}|opennlp-formats|{{{}split("-"){}}}, 
{{text_([a-z]{2,3})}}|OPENNLP-1928|
|{{EmojiCharSequenceNormalizer}}|opennlp-runtime|surrogate-range 
replace|OPENNLP-1928|
|{{ParserTool}}|opennlp-cli|parenthesis spacing|OPENNLP-1928|
|{{DownloadUtil}}|opennlp-runtime|{{split("}}
{{{}s"){}}}, HTML link scrape|OPENNLP-1928|
|{{{}ADNameSampleStream{}}}, {{{}ADPOSSampleStream{}}}, 
{{{}ADSentenceSampleStream{}}}, {{ADSentenceStream}}|opennlp-formats|AD corpus 
markup, whitespace, metadata, and punctuation patterns|OPENNLP-1928 (trivial 
patterns), OPENNLP-1930 (node, leaf, tag, and metadata patterns)|
|{{{}AbstractDL{}}}, {{DocumentCategorizerConfig}}|opennlp-dl|ad-hoc JSON 
scrape|OPENNLP-1931|
|{{{}AbstractClassPathModelFinder{}}}, {{{}DirectoryModelFinder{}}}, 
{{SimpleClassPathModelFinder}}|opennlp-model-resolver|wildcard-to-regex glob 
matching, class path split|OPENNLP-1932|
|{{{}SimpleEventStreamBuilder{}}}, {{{}RealValueFileEventStream{}}}, 
{{RealBasicEventStream}}|opennlp-ml-commons, opennlp-ml-maxent|{{split("}}
{{s+")}}|OPENNLP-1933|
|{{LeipzigLanguageSampleStream}}|opennlp-formats|{{matches("[a-z]+")}}|OPENNLP-1933|
|{{{}MascNamedEntityParser{}}}, {{{}MascPennTagParser{}}}, 
{{MascWordParser}}|opennlp-formats|{{replaceFirst}} of id prefixes|OPENNLP-1933|
|{{MorfologikDictionaryBuilder}}|opennlp-morfologik|anchored {{replaceAll}} of 
the metadata suffix|OPENNLP-1933|
|{{FrequencyDictionaryLoader}}|opennlp-spellcheck|{{[\t ]+}} column 
split|OPENNLP-1933|
|{{BasicContextGenerator}}|opennlp-ml-maxent|{{split(separator)}} with a 
caller-supplied separator; documented as a character, evaluated as a 
pattern|OPENNLP-1929|
|{{{}TokenizerME{}}}, {{TokSpanEventStream}}|opennlp-runtime|per-token match of 
the alphanumeric pattern|OPENNLP-1934: built-in {{^[...]+$}} defaults run as a 
character set lookup, other patterns still use the engine|

Not in scope: single-character literal splits such as {{split(",")}} or 
{{split("\t")}} take the JDK fast path and compile no Pattern.
h3. Exempt: RegEx as the class's contract (no change)
||Class||Module||Why exempt||
|{{{}RegexNameFinder{}}}, {{RegexNameFinderFactory}}|opennlp-runtime|the class 
IS a regex step (OPENNLP-1925 note added)|
|{{AncoraSpanishHeadRules}}|opennlp-runtime|head rules are regex strings 
defined by the treebank spec|
|{{Parse}}|opennlp-api|Penn Treebank markup grammar|
|{{NameSample}}|opennlp-api|name-sample markup grammar|
|{{{}TokenizerFactory{}}}, {{lang.Factory}}|opennlp-runtime|the alphanumeric 
pattern is written to the model manifest as a regex string and read back with 
{{{}Pattern.compile{}}}, so the {{Pattern}} type is part of the model contract 
(settled in OPENNLP-1934; the hot path no longer uses the engine for the 
shipped defaults)|
|{{SpellCheckingCharSequenceNormalizer}}|opennlp-spellcheck|the URL heuristic 
is an alternation of schemes, an email shape, and a TLD list|

The exempt list is enforced: OPENNLP-1935 adds the checkstyle checks 
{{NoRegexImport}} and {{NoRegexStringCall}} for production sources, with these 
classes as the documented suppressions.
h3. Rules for replacement
 * Behavior must be identical; existing tests stay green, and any predicate 
rewrite gets tests for both the accept and reject side, including empty input, 
leading and trailing separators, non-ASCII spaces and digits, and 
supplementary-plane characters. Where practical the old pattern is kept in the 
test as the oracle.
 * Where a rewrite exposes a behavior difference, it is a bug: the failing test 
is committed before the fix.
 * Replacements use cursor and code-point loops only; whitespace goes through 
{{StringUtil.isWhitespace}} or {{StringUtil.isAsciiWhitespace}} (the {{\s}} 
set), never {{{}Character.isWhitespace{}}}; case mapping goes through 
{{{}StringUtil{}}}.
 * Shared scans live in {{StringUtil}} when more than one module needs them, in 
a package-private helper otherwise.
 * One commit per class or cohesive group so each fix can be cherry-picked onto 
its own ticket branch.

One note on the {{text_([a-z]{2,3})}} cell: the braces are escaped so JIRA does 
not read them as a macro, same as in the 1928 description. If the preview shows 
the backslashes, the
{noformat}
 {noformat}
fallback from earlier applies.

  was:
OpenNLP's processing path is RegEx-free by policy; a genuine RegEx step (for 
example {{RegexNameFinder}}) is the documented exception (OPENNLP-1925). An 
audit of main found the following production classes touching 
{{java.util.regex}}. This epic covers replacing every incidental usage with 
cursor or code-point loops. Classes whose job IS pattern matching are exempt 
and listed here only for completeness.

h3. In scope: incidental usage to replace

|| Class || Module || Regex usage || Effort ||
| {{DefaultLemmatizerContextGenerator}} | opennlp-runtime | {{[A-Z]}} / 
{{[0-9]}} presence checks | trivial: code-point loop |
| {{DefaultPOSContextGenerator}} | opennlp-runtime | {{[A-Z]}} / {{[0-9]}} 
presence checks | trivial: code-point loop |
| {{FeatureGeneratorUtil}} | opennlp-runtime | {{^[A-ZÄÖÜ]\.$}} | trivial: 
length-2 check |
| {{TokenPatternFeatureGenerator}} | opennlp-runtime | {{[^a-zA-Z]}} strip | 
trivial: code-point loop |
| {{BioCodec}} | opennlp-runtime | {{(.+)-\w+}} outcome split | trivial: 
lastIndexOf('-') |
| {{NameFinderME}} | opennlp-runtime | {{(.+)-\w+}} outcome split | trivial: 
lastIndexOf('-') |
| {{BrownCluster}} | opennlp-runtime | split on tab | trivial: cursor split |
| {{TokenSampleStream}} | opennlp-runtime | {{split("\\s+")}} | trivial: cursor 
split |
| {{ConlluStream}} | opennlp-formats | {{split("-")}}, {{text_([a-z]{2,3})}} | 
trivial: startsWith / substring |
| {{EmojiCharSequenceNormalizer}} | opennlp-runtime | surrogate-range replace | 
trivial: code-point loop |
| {{ParserTool}} | opennlp-cli | parenthesis spacing | moderate: single cursor 
scan |
| {{DownloadUtil}} | opennlp-runtime | {{split("\\s")}}, HTML link scrape | 
moderate: cursor + tag scan |
| {{AbstractDL}} | opennlp-dl | ad-hoc JSON field scrape | moderate: cursor 
parse |
| {{DocumentCategorizerConfig}} | opennlp-dl | ad-hoc JSON map scrape | 
moderate: cursor parse |
| {{ADNameSampleStream}}, {{ADPOSSampleStream}}, {{ADSentenceSampleStream}}, 
{{ADSentenceStream}} | opennlp-formats | AD corpus markup parsing | moderate: 
cursor parse |
| {{AbstractClassPathModelFinder}}, {{DirectoryModelFinder}}, 
{{SimpleClassPathModelFinder}} | opennlp-model-resolver | wildcard-to-regex 
glob matching | moderate: hand-rolled glob |

h3. Exempt: genuine RegEx steps (no change)

|| Class || Module || Why exempt ||
| {{RegexNameFinder}}, {{RegexNameFinderFactory}} | opennlp-runtime | the class 
IS a regex step (OPENNLP-1925 note added) |
| {{AncoraSpanishHeadRules}} | opennlp-runtime | head rules are regex strings 
defined by the treebank spec |
| {{Parse}} | opennlp-api | Penn Treebank markup grammar |
| {{NameSample}} | opennlp-api | name-sample markup grammar |

Still determining:
|| Class || Module || Investigating ||
| {{TokenizerFactory}}, {{TokenizerME}}, {{TokSpanEventStream}}, 
{{lang.Factory}}, {{lang.en.TokenSampleStream}} | opennlp-runtime | 
alphanumeric pattern comes from model training parameters, part of the API 
contract |


h3. Rules for replacement

* Behavior must be identical; existing tests stay green, and any predicate 
rewrite gets pinning tests for both the accept and reject side.
* Replacements use cursor and code-point loops only; case mapping goes through 
{{StringUtil}}.
* One commit per class or cohesive group so each fix can be cherry-picked onto 
its own ticket branch.


> Replace incidental RegEx usage in production code with cursor and code-point 
> loops
> ----------------------------------------------------------------------------------
>
>                 Key: OPENNLP-1926
>                 URL: https://issues.apache.org/jira/browse/OPENNLP-1926
>             Project: OpenNLP
>          Issue Type: Epic
>            Reporter: Kristian Rickert
>            Assignee: Kristian Rickert
>            Priority: Major
>
> OpenNLP's processing path is RegEx-free by policy; a RegEx step that is the 
> class's purpose (for example {{{}RegexNameFinder{}}}) is the documented 
> exception (OPENNLP-1925). An audit of main found the production classes below 
> touching {{{}java.util.regex{}}}, and the work turned up a second group: 
> {{{}String.split{}}}, {{{}matches{}}}, {{{}replaceAll{}}}, and 
> {{replaceFirst}} calls that compile a Pattern on each call without an import. 
> This epic replaces each incidental usage with cursor or code-point loops and 
> ends with a checkstyle guard so it stays that way. Classes whose job IS 
> pattern matching are exempt and listed for completeness.
> h3. Tickets, in merge order
> Each branch is stacked on the one before it, so the pull requests merge in 
> this order and OPENNLP-1935 merges last.
> ||Ticket||Scope||PR||
> |OPENNLP-1928|part 1: trivial batch, plus the shared {{StringUtil}} helpers 
> {{{}isAsciiWhitespace{}}}, {{{}splitOnAsciiWhitespace{}}}, 
> {{{}containsAsciiUpperCase{}}}, {{containsAsciiDigit}}|apache/opennlp#1275|
> |OPENNLP-1930|part 2: Arvores Deitadas markup parsing|apache/opennlp#1276|
> |OPENNLP-1931|part 3: JSON vocabulary and id2label scrape in 
> opennlp-dl|apache/opennlp#1277|
> |OPENNLP-1932|part 4: wildcard matching in the model 
> resolver|apache/opennlp#1278|
> |OPENNLP-1933|part 5: per-call String regex splits and 
> replacements|apache/opennlp#1279|
> |OPENNLP-1929|bug: {{BasicContextGenerator}} splits on its separator as a 
> regular expression|apache/opennlp#1280|
> |OPENNLP-1934|part 6: tokenizer alphanumeric pattern evaluated as a character 
> set|apache/opennlp#1281|
> |OPENNLP-1935|part 7: checkstyle guard and the exempt list in 
> {{checkstyle-suppressions.xml}}|apache/opennlp#1282|
> h3. In scope: incidental usage to replace
> ||Class||Module||Regex usage||Ticket||
> |{{{}DefaultLemmatizerContextGenerator{}}}, 
> {{DefaultPOSContextGenerator}}|opennlp-runtime|{{[A-Z]}} / {{[0-9]}} presence 
> checks|OPENNLP-1928|
> |{{FeatureGeneratorUtil}}|opennlp-runtime|{{^[A-ZÄÖÜ]\.$}}|OPENNLP-1928|
> |{{TokenPatternFeatureGenerator}}|opennlp-runtime|{{[^a-zA-Z]}} 
> strip|OPENNLP-1928|
> |{{{}BioCodec{}}}, {{NameFinderME}}|opennlp-runtime|{{(.{+})-\w{+}}} outcome 
> split|OPENNLP-1928|
> |{{BrownCluster}}|opennlp-runtime|split on tab|OPENNLP-1928|
> |{{lang.en.TokenSampleStream}}|opennlp-runtime|{{split("}}
> {{{}s+"){}}}, {{[A-Za-z0-9]}} check|OPENNLP-1928|
> |{{ConlluStream}}|opennlp-formats|{{{}split("-"){}}}, 
> {{text_([a-z]{2,3})}}|OPENNLP-1928|
> |{{EmojiCharSequenceNormalizer}}|opennlp-runtime|surrogate-range 
> replace|OPENNLP-1928|
> |{{ParserTool}}|opennlp-cli|parenthesis spacing|OPENNLP-1928|
> |{{DownloadUtil}}|opennlp-runtime|{{split("}}
> {{{}s"){}}}, HTML link scrape|OPENNLP-1928|
> |{{{}ADNameSampleStream{}}}, {{{}ADPOSSampleStream{}}}, 
> {{{}ADSentenceSampleStream{}}}, {{ADSentenceStream}}|opennlp-formats|AD 
> corpus markup, whitespace, metadata, and punctuation patterns|OPENNLP-1928 
> (trivial patterns), OPENNLP-1930 (node, leaf, tag, and metadata patterns)|
> |{{{}AbstractDL{}}}, {{DocumentCategorizerConfig}}|opennlp-dl|ad-hoc JSON 
> scrape|OPENNLP-1931|
> |{{{}AbstractClassPathModelFinder{}}}, {{{}DirectoryModelFinder{}}}, 
> {{SimpleClassPathModelFinder}}|opennlp-model-resolver|wildcard-to-regex glob 
> matching, class path split|OPENNLP-1932|
> |{{{}SimpleEventStreamBuilder{}}}, {{{}RealValueFileEventStream{}}}, 
> {{RealBasicEventStream}}|opennlp-ml-commons, opennlp-ml-maxent|{{split("}}
> {{s+")}}|OPENNLP-1933|
> |{{LeipzigLanguageSampleStream}}|opennlp-formats|{{matches("[a-z]+")}}|OPENNLP-1933|
> |{{{}MascNamedEntityParser{}}}, {{{}MascPennTagParser{}}}, 
> {{MascWordParser}}|opennlp-formats|{{replaceFirst}} of id 
> prefixes|OPENNLP-1933|
> |{{MorfologikDictionaryBuilder}}|opennlp-morfologik|anchored {{replaceAll}} 
> of the metadata suffix|OPENNLP-1933|
> |{{FrequencyDictionaryLoader}}|opennlp-spellcheck|{{[\t ]+}} column 
> split|OPENNLP-1933|
> |{{BasicContextGenerator}}|opennlp-ml-maxent|{{split(separator)}} with a 
> caller-supplied separator; documented as a character, evaluated as a 
> pattern|OPENNLP-1929|
> |{{{}TokenizerME{}}}, {{TokSpanEventStream}}|opennlp-runtime|per-token match 
> of the alphanumeric pattern|OPENNLP-1934: built-in {{^[...]+$}} defaults run 
> as a character set lookup, other patterns still use the engine|
> Not in scope: single-character literal splits such as {{split(",")}} or 
> {{split("\t")}} take the JDK fast path and compile no Pattern.
> h3. Exempt: RegEx as the class's contract (no change)
> ||Class||Module||Why exempt||
> |{{{}RegexNameFinder{}}}, {{RegexNameFinderFactory}}|opennlp-runtime|the 
> class IS a regex step (OPENNLP-1925 note added)|
> |{{AncoraSpanishHeadRules}}|opennlp-runtime|head rules are regex strings 
> defined by the treebank spec|
> |{{Parse}}|opennlp-api|Penn Treebank markup grammar|
> |{{NameSample}}|opennlp-api|name-sample markup grammar|
> |{{{}TokenizerFactory{}}}, {{lang.Factory}}|opennlp-runtime|the alphanumeric 
> pattern is written to the model manifest as a regex string and read back with 
> {{{}Pattern.compile{}}}, so the {{Pattern}} type is part of the model 
> contract (settled in OPENNLP-1934; the hot path no longer uses the engine for 
> the shipped defaults)|
> |{{SpellCheckingCharSequenceNormalizer}}|opennlp-spellcheck|the URL heuristic 
> is an alternation of schemes, an email shape, and a TLD list|
> The exempt list is enforced: OPENNLP-1935 adds the checkstyle checks 
> {{NoRegexImport}} and {{NoRegexStringCall}} for production sources, with 
> these classes as the documented suppressions.
> h3. Rules for replacement
>  * Behavior must be identical; existing tests stay green, and any predicate 
> rewrite gets tests for both the accept and reject side, including empty 
> input, leading and trailing separators, non-ASCII spaces and digits, and 
> supplementary-plane characters. Where practical the old pattern is kept in 
> the test as the oracle.
>  * Where a rewrite exposes a behavior difference, it is a bug: the failing 
> test is committed before the fix.
>  * Replacements use cursor and code-point loops only; whitespace goes through 
> {{StringUtil.isWhitespace}} or {{StringUtil.isAsciiWhitespace}} (the {{\s}} 
> set), never {{{}Character.isWhitespace{}}}; case mapping goes through 
> {{{}StringUtil{}}}.
>  * Shared scans live in {{StringUtil}} when more than one module needs them, 
> in a package-private helper otherwise.
>  * One commit per class or cohesive group so each fix can be cherry-picked 
> onto its own ticket branch.
> One note on the {{text_([a-z]{2,3})}} cell: the braces are escaped so JIRA 
> does not read them as a macro, same as in the 1928 description. If the 
> preview shows the backslashes, the
> {noformat}
>  {noformat}
> fallback from earlier applies.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to