Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
167 commits
Select commit Hold shift + click to select a range
2d95887
Add opennlp-embeddings module with a safetensors reader
krickert Jul 7, 2026
dc3fa77
Add StaticEmbeddingModel: tokenize, gather, weight, mean-pool, normalize
krickert Jul 7, 2026
cffed1f
Add word similarity and analogy convenience API
krickert Jul 7, 2026
b13f849
Add a JMH benchmark for StaticEmbeddingModel, matching the perf-1850-…
krickert Jul 7, 2026
c9381b8
Review pass on opennlp-embeddings: analogy exclusion fixes, faster ne…
krickert Jul 7, 2026
a919ceb
Stream safetensors tensor data with positional reads
krickert Jul 9, 2026
795734b
Unit-test SafetensorsHeaderParser directly, reject trailing header ga…
krickert Jul 9, 2026
dcd15e9
Add StaticEmbeddingModel.load(modelDirectory) resolving switches from…
krickert Jul 9, 2026
e25ff87
Document opennlp-embeddings: Dev Manual chapter and module README
krickert Jul 9, 2026
c7610de
OPENNLP-1877: Address review: value-safe TensorInfo, checked IO, type…
krickert Jul 10, 2026
78414d9
OPENNLP-1877: Register opennlp-embeddings in the root dependencyManag…
krickert Jul 10, 2026
4cb03e8
OPENNLP-1877: Rename WordPieceVocabulary to WordpieceVocabulary
krickert Jul 11, 2026
e76555e
OPENNLP-1877: Add the TextEmbedder seam; both engines implement it
krickert Jul 11, 2026
d43f6e1
OPENNLP-1877: Give the embeddings module its own tokenization pipeline
krickert Jul 12, 2026
4aea42b
OPENNLP-1877: Load the tiny-vectors test model from the classpath stream
krickert Jul 12, 2026
b7ca1b6
OPENNLP-1877: Trim commentary and tighten javadoc per review conventions
krickert Jul 12, 2026
3f9bead
OPENNLP-1877: Rewrite the opennlp-embeddings README with diagrams and…
krickert Jul 12, 2026
99538d3
OPENNLP-1877: Document implementation-defined empty-input behavior on…
krickert Jul 12, 2026
110613c
OPENNLP-1877: Read F16 and BF16 safetensors tensors, not just F32
krickert Jul 12, 2026
78a3fd9
OPENNLP-1877: Support SentencePiece models through the SubwordTokeniz…
krickert Jul 13, 2026
d423eb2
OPENNLP-1877: Document helpers and validate record and parser boundaries
krickert Jul 13, 2026
9f7ea98
OPENNLP-1877: Let the embeddings JMH benchmark load real model direct…
krickert Jul 13, 2026
48e81e5
OPENNLP-1877: Add a distillation tutorial and real two-model benchmar…
krickert Jul 13, 2026
6c2a938
OPENNLP-1877: Load WordPiece tables that dropped the frame tokens
krickert Jul 13, 2026
2ae4912
OPENNLP-1877: Add the AssembleModel CLI to complete and verify distil…
krickert Jul 13, 2026
8b2c075
OPENNLP-1877: Ship the distillation script and the Python parity harness
krickert Jul 13, 2026
dfe5834
OPENNLP-1877: Trim residual commentary per review conventions
krickert Jul 13, 2026
0349b1f
OPENNLP-1877: Name format constants, document throws, and source or r…
krickert Jul 16, 2026
1fd2f68
OPENNLP-1877: Cite the static embedding usage example test in the manual
krickert Jul 20, 2026
9aa534c
OPENNLP-1877: Align dev helper placement and test literals with the r…
krickert Jul 21, 2026
d3cf3ff
OPENNLP-1877: Add a Java model distiller and address the review comments
krickert Jul 28, 2026
2501af1
OPENNLP-1877: Fix distiller correctness bugs and cover the untested s…
krickert Jul 28, 2026
1bc1928
OPENNLP-1877: Pin and verify teacher downloads
krickert Jul 28, 2026
7694191
OPENNLP-1877: Batch same-length inputs into one ONNX run in embedAll
krickert Aug 2, 2026
742148c
OPENNLP-1877: Mark public API experimental and reject bare minus headers
krickert Aug 6, 2026
595959d
OPENNLP-1877: Address review: complete experimental markers, hrefs, a…
krickert Aug 8, 2026
71b16b3
OPENNLP-1877: Throw InvalidFormatException for malformed model content
krickert Aug 9, 2026
7212e40
OPENNLP-1877: Demote test-only SafetensorsFile accessors to package-p…
krickert Aug 9, 2026
ddb1992
OPENNLP-1877: Document the DistillModel and AssembleModel tools in th…
krickert Aug 9, 2026
d78300e
OPENNLP-1877: Fail loud on a config.json declaring non-mean pooling
krickert Aug 10, 2026
b42043c
OPENNLP-1877: Clamp the neighbor scan's topK to the vocabulary size
krickert Aug 10, 2026
86963bc
OPENNLP-1877: Reject non-finite embedding matrix values at load time
krickert Aug 10, 2026
52ac814
OPENNLP-1877: Exercise Casing.CASED through the directory loader
krickert Aug 10, 2026
b0ae5e7
OPENNLP-1877: Mirror the manual's explicit-overload listing with a test
krickert Aug 10, 2026
851196f
OPENNLP-1877: Document semantic search in the manual with a mirrored …
krickert Aug 10, 2026
f5e6568
OPENNLP-1904: Default-locale case folding corrupts lemmatizer models …
krickert Aug 13, 2026
543067e
OPENNLP-1905: Default-locale case folding breaks Morfologik dictionar…
krickert Aug 13, 2026
0e1b0fa
OPENNLP-1877: Parse teacher references and hex digests with cursor scans
krickert Aug 16, 2026
5cc79e3
OPENNLP-1877: Distill whole terms as extra rows matched before subwor…
krickert Aug 16, 2026
ef26102
Bump org.apache.maven:apache-maven from 3.9.6 to 3.9.16 (#1224)
dependabot[bot] Aug 17, 2026
853028a
Bump onnxruntime.version from 1.28.0 to 1.29.0 (#1216)
dependabot[bot] Aug 17, 2026
b48862a
Bump junit.version from 6.1.2 to 6.1.3 (#1218)
dependabot[bot] Aug 17, 2026
fdffe5a
Bump io.github.classgraph:classgraph from 4.8.186 to 4.8.192 (#1219)
dependabot[bot] Aug 17, 2026
9d224f9
OPENNLP-1906: Sentence detector abbreviation veto is quadratic in doc…
krickert Aug 17, 2026
bb41293
OPENNLP-1914 - Bump org.apache.maven.wrapper:maven-wrapper from 3.3.2…
dependabot[bot] Aug 17, 2026
74464fb
Minor: Regenerated NOTICE File for 853028a9f46953c24eb0e1643445cc120a…
github-actions[bot] Aug 17, 2026
4df09a9
OPENNLP-1915: Fix line endings of mvnw.cmd (#1231)
rzo1 Aug 18, 2026
a42e50c
OPENNLP-1913: Include Maven Wrapper configuration in source release (…
rzo1 Aug 18, 2026
ea2ec53
Minor: Regenerated NOTICE File for a42e50c407592db8bd05c756b98b0a32f7…
github-actions[bot] Aug 18, 2026
f6f1bef
OPENNLP-1877: load self-contained Model2Vec Unigram tokenizers
krickert Aug 21, 2026
8881065
Bump io.github.classgraph:classgraph from 4.8.192 to 4.8.193 (#1241)
dependabot[bot] Aug 24, 2026
ef942ef
Minor: Regenerated NOTICE File for 88810656a5ea115ecb22ed2cd7f6fca5db…
github-actions[bot] Aug 25, 2026
daf86d2
OPENNLP-1902: Verify cached models against their SHA-512 checksum (#1…
sumanpal97 Aug 25, 2026
6fd56f5
OPENNLP-1916: Normalizer documentation and fail loud on chain (#1235)
krickert Aug 25, 2026
b46b957
OPENNLP-1903: Replace per-candidate Sequence copies with chain nodes …
krickert Aug 26, 2026
8f7befe
OPENNLP-1922: Fix ReDoS / stack exhaustion in RegexNameFinderFactory …
smarthi Aug 30, 2026
06eeaa4
Bump actions/setup-java from 5.7.0 to 6.0.0 (#1247)
dependabot[bot] Aug 30, 2026
7235395
Bump io.github.classgraph:classgraph from 4.8.193 to 4.8.194 (#1255)
dependabot[bot] Aug 31, 2026
d6b8641
OPENNLP-1888: Document annotation container with typed offset-anchore…
krickert Sep 1, 2026
06f43f0
OPENNLP-1923: Update logcaptor to 2.12.7 (#1253)
dependabot[bot] Sep 1, 2026
e04982a
Minor: Regenerated NOTICE File for 06f43f0f0433f746c9f26ac1b8e8e5d83f…
github-actions[bot] Sep 1, 2026
4c8cdf4
OPENNLP-1921: Add Paragraph Normalizer for Gutenberg-style and Markdo…
tusharsaini18899 Sep 1, 2026
228fd7e
OPENNLP-1885: Add opennlp-subword: pure-Java SentencePiece inference …
krickert Jul 10, 2026
8fe701c
OPENNLP-1885: Speed up the encode path 2.3x, parity-checked at every …
krickert Jul 10, 2026
28e6456
OPENNLP-1885: Move the subword contract into opennlp-api
krickert Jul 11, 2026
5b6be0f
OPENNLP-1885: Add WordpieceEncoder and fold the unreleased BertTokeni…
krickert Jul 12, 2026
878e897
OPENNLP-1885: Document the hand-rolled protobuf reader rationale and …
krickert Jul 12, 2026
dfc43f7
OPENNLP-1885: Trim commentary and tighten javadoc per review conventions
krickert Jul 12, 2026
2d57fb6
OPENNLP-1885: Tighten javadoc to contracts and document helpers and o…
krickert Jul 13, 2026
b8aaeb5
OPENNLP-1885: Declare serialVersionUID on SentencePieceTokenizer
krickert Jul 13, 2026
eab8f5d
OPENNLP-1885: Trim residual commentary per review conventions
krickert Jul 13, 2026
8c89cdd
OPENNLP-1885: Document subword tokenization in the manual
krickert Jul 14, 2026
294ccf0
OPENNLP-1885: Make the tokenizer graph serializable with computed UID…
krickert Jul 16, 2026
efacca1
OPENNLP-1885: Guard tokenizer deserialization with an allow-listing O…
krickert Jul 17, 2026
ef6edb6
OPENNLP-1885: Cite the SentencePiece usage example test in the manual
krickert Jul 20, 2026
a5bb0b4
OPENNLP-1885: Align null contracts and annotations with the review co…
krickert Jul 21, 2026
0912464
OPENNLP-1885: Address review: checked InvalidFormatException for malf…
krickert Jul 21, 2026
6f27789
OPENNLP-1885: Address review: validation message style, shared test f…
krickert Jul 28, 2026
eeb829b
OPENNLP-1885: Deprecate BertTokenizer as a shim over WordpieceEncoder…
krickert Jul 29, 2026
2a60b37
OPENNLP-1885: Document fixture regeneration and Utf8Text span mapping
krickert Aug 6, 2026
3d82635
OPENNLP-1885: Attribute the double-array literature in DoubleArrayTrie
krickert Aug 6, 2026
40bb365
OPENNLP-1885: Expand fixtures README into a validation tutorial and l…
krickert Aug 6, 2026
c24cd7f
OPENNLP-1885: Pin InvalidFormatException on a malformed precompiled c…
krickert Aug 8, 2026
d15b010
OPENNLP-1885: Fix loader exception type and wire opennlp-subword into…
krickert Aug 8, 2026
db498e0
OPENNLP-1885: Reference the fixtures README by its in-tree path
krickert Aug 9, 2026
447178f
OPENNLP-1885: Document where the subword API lives and what a piece c…
krickert Sep 1, 2026
154b789
Add opennlp-embeddings module with a safetensors reader
krickert Jul 7, 2026
333da58
Add StaticEmbeddingModel: tokenize, gather, weight, mean-pool, normalize
krickert Jul 7, 2026
c3d6589
Add word similarity and analogy convenience API
krickert Jul 7, 2026
a5b2508
Add a JMH benchmark for StaticEmbeddingModel, matching the perf-1850-…
krickert Jul 7, 2026
cf7bd72
Review pass on opennlp-embeddings: analogy exclusion fixes, faster ne…
krickert Jul 7, 2026
2df6e63
Stream safetensors tensor data with positional reads
krickert Jul 9, 2026
9e247a1
Unit-test SafetensorsHeaderParser directly, reject trailing header ga…
krickert Jul 9, 2026
bc741a2
Add StaticEmbeddingModel.load(modelDirectory) resolving switches from…
krickert Jul 9, 2026
0a98852
Document opennlp-embeddings: Dev Manual chapter and module README
krickert Jul 9, 2026
85b6882
OPENNLP-1877: Address review: value-safe TensorInfo, checked IO, type…
krickert Jul 10, 2026
7aef651
OPENNLP-1877: Register opennlp-embeddings in the root dependencyManag…
krickert Jul 10, 2026
b9043d7
OPENNLP-1877: Rename WordPieceVocabulary to WordpieceVocabulary
krickert Jul 11, 2026
ad625f9
OPENNLP-1877: Add the TextEmbedder seam; both engines implement it
krickert Jul 11, 2026
b9fb977
OPENNLP-1877: Give the embeddings module its own tokenization pipeline
krickert Jul 12, 2026
d5f0391
OPENNLP-1877: Load the tiny-vectors test model from the classpath stream
krickert Jul 12, 2026
01549dd
OPENNLP-1877: Trim commentary and tighten javadoc per review conventions
krickert Jul 12, 2026
610af68
OPENNLP-1877: Rewrite the opennlp-embeddings README with diagrams and…
krickert Jul 12, 2026
c874771
OPENNLP-1877: Document implementation-defined empty-input behavior on…
krickert Jul 12, 2026
566c0da
OPENNLP-1877: Read F16 and BF16 safetensors tensors, not just F32
krickert Jul 12, 2026
826c8b4
OPENNLP-1877: Support SentencePiece models through the SubwordTokeniz…
krickert Jul 13, 2026
213c0ea
OPENNLP-1877: Document helpers and validate record and parser boundaries
krickert Jul 13, 2026
1fb5d66
OPENNLP-1877: Let the embeddings JMH benchmark load real model direct…
krickert Jul 13, 2026
0d3306d
OPENNLP-1877: Add a distillation tutorial and real two-model benchmar…
krickert Jul 13, 2026
0941962
OPENNLP-1877: Load WordPiece tables that dropped the frame tokens
krickert Jul 13, 2026
d2d1f6a
OPENNLP-1877: Add the AssembleModel CLI to complete and verify distil…
krickert Jul 13, 2026
7463c47
OPENNLP-1877: Ship the distillation script and the Python parity harness
krickert Jul 13, 2026
064f981
OPENNLP-1877: Trim residual commentary per review conventions
krickert Jul 13, 2026
f33cc6d
OPENNLP-1877: Name format constants, document throws, and source or r…
krickert Jul 16, 2026
0451d0a
OPENNLP-1877: Cite the static embedding usage example test in the manual
krickert Jul 20, 2026
2b83deb
OPENNLP-1877: Align dev helper placement and test literals with the r…
krickert Jul 21, 2026
3bcac53
OPENNLP-1877: Add a Java model distiller and address the review comments
krickert Jul 28, 2026
231537f
OPENNLP-1877: Fix distiller correctness bugs and cover the untested s…
krickert Jul 28, 2026
3a1084f
OPENNLP-1877: Pin and verify teacher downloads
krickert Jul 28, 2026
45e1c47
OPENNLP-1877: Batch same-length inputs into one ONNX run in embedAll
krickert Aug 2, 2026
36d3f8f
OPENNLP-1877: Mark public API experimental and reject bare minus headers
krickert Aug 6, 2026
201d284
OPENNLP-1877: Address review: complete experimental markers, hrefs, a…
krickert Aug 8, 2026
75ba701
OPENNLP-1877: Throw InvalidFormatException for malformed model content
krickert Aug 9, 2026
3db9afc
OPENNLP-1877: Demote test-only SafetensorsFile accessors to package-p…
krickert Aug 9, 2026
4f15498
OPENNLP-1877: Document the DistillModel and AssembleModel tools in th…
krickert Aug 9, 2026
337caa7
OPENNLP-1877: Fail loud on a config.json declaring non-mean pooling
krickert Aug 10, 2026
2bb3f41
OPENNLP-1877: Clamp the neighbor scan's topK to the vocabulary size
krickert Aug 10, 2026
1f70611
OPENNLP-1877: Reject non-finite embedding matrix values at load time
krickert Aug 10, 2026
ac2b012
OPENNLP-1877: Exercise Casing.CASED through the directory loader
krickert Aug 10, 2026
9e72bc8
OPENNLP-1877: Mirror the manual's explicit-overload listing with a test
krickert Aug 10, 2026
a41ac4b
OPENNLP-1877: Document semantic search in the manual with a mirrored …
krickert Aug 10, 2026
000abb9
OPENNLP-1877: Parse teacher references and hex digests with cursor scans
krickert Aug 16, 2026
322da72
OPENNLP-1877: Distill whole terms as extra rows matched before subwor…
krickert Aug 16, 2026
6d52da2
OPENNLP-1877: load self-contained Model2Vec Unigram tokenizers
krickert Aug 21, 2026
73f9a25
OPENNLP-1897: Add document-scoped term vector layer (#1212)
krickert Sep 3, 2026
d2c216e
OPENNLP-1885: Add subword API and WordPiece encoder
krickert Sep 3, 2026
6591f8f
Merge remote-tracking branch 'upstream/main' into OPENNLP-1877-static…
krickert Sep 3, 2026
18b3426
Merge API-only OPENNLP-1885 into static embeddings
krickert Sep 3, 2026
1f929fa
OPENNLP-1893: Support Hunspell affix dictionaries for stemming (#1190)
krickert Sep 4, 2026
93136f6
OPENNLP-1894: Add dictionary-based tokenization for Japanese, Korean,…
krickert Sep 4, 2026
6491446
OPENNLP-1885: Add subword API and WordPiece encoder
krickert Sep 3, 2026
c7ebfce
OPENNLP-1885: Cover WordPiece parity and model ids
krickert Sep 4, 2026
141160e
OPENNLP-1885: Align WordPiece API and DL callers
krickert Sep 4, 2026
e0f669c
OPENNLP-1894: Harden CJK dictionary loading (#1265)
krickert Sep 4, 2026
144be05
OPENNLP-1909: General verified installer for user-supplied third-part…
krickert Sep 4, 2026
535c6d4
Cancel superseded pull request CI runs (#1267)
rzo1 Sep 5, 2026
ff83d94
OPENNLP-1877: Harden static embedding loading
krickert Sep 5, 2026
d130e1c
OPENNLP-1885: Merge current main and resource installer
krickert Sep 5, 2026
9d9dcb0
OPENNLP-1877: Reconcile published embeddings history
krickert Sep 5, 2026
f8228d9
OPENNLP-1877: Merge reviewed subword API and current main
krickert Sep 5, 2026
b81a2a7
OPENNLP-1925: Document RegEx exemption and harden ReDoS guard test (#…
krickert Sep 5, 2026
9f187e0
OPENNLP-1877: Add failing test for relative parent-segment teacher paths
krickert Sep 5, 2026
00082f1
OPENNLP-1877: Reject relative teacher paths with parent segments
krickert Sep 5, 2026
34ff57b
OPENNLP-1893: Correct Hunspell parsing and analysis (#1266)
krickert Sep 5, 2026
d85402c
Merge Apache main through Hunspell follow-up #1266 into OPENNLP-1885-…
krickert Sep 5, 2026
048c058
Merge OPENNLP-1885-sentencepiece into OPENNLP-1877-static-embeddings
krickert Sep 5, 2026
9b0c434
OPENNLP-1877: Validate teacher vector lengths and test saved models
krickert Sep 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/allowlist-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ on:
paths:
- ".github/**"

concurrency:
# A new commit on a pull request makes the run in flight obsolete, so cancel it
# instead of paying for a result nobody will read. Pushes to a branch group by ref
# and are never cancelled, so every commit on main keeps a full build.
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
contents: read

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/license.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up JDK 21
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
with:
java-version: '21'
distribution: 'temurin'
Expand Down
15 changes: 11 additions & 4 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ on:
branches:
- main

concurrency:
# A new commit on a pull request makes the run in flight obsolete, so cancel it
# instead of paying for a result nobody will read. Pushes to a branch group by ref
# and are never cancelled, so every commit on main keeps a full build.
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
build:
runs-on: ${{ matrix.os }}
Expand All @@ -49,7 +56,7 @@ jobs:
restore-keys: |
${{ runner.os }}-maven-
- name: Set up JDK ${{ matrix.java }}
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
with:
distribution: temurin
java-version: ${{ matrix.java }}
Expand Down Expand Up @@ -89,7 +96,7 @@ jobs:
sudo apt-get install -y bats

- name: Set up JDK 21
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
with:
distribution: temurin
java-version: 21
Expand Down Expand Up @@ -148,7 +155,7 @@ jobs:
brew install bats-core

- name: Set up JDK 21
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
with:
distribution: temurin
java-version: 21
Expand Down Expand Up @@ -199,7 +206,7 @@ jobs:
shell: pwsh

- name: Set up JDK 21
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
with:
distribution: temurin
java-version: 21
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-snapshots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
restore-keys: |
${{ runner.os }}-maven-
- name: Setup Java
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
with:
distribution: temurin
java-version: 21
Expand Down
20 changes: 2 additions & 18 deletions .mvn/wrapper/maven-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,19 +1,3 @@
# 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.
wrapperVersion=3.3.2
wrapperVersion=3.3.4
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip
6 changes: 3 additions & 3 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,10 @@ List of third-party dependencies grouped by their license type.

MIT License

* onnx-runtime (com.microsoft.onnxruntime:onnxruntime_gpu:1.28.0 - https://microsoft.github.io/onnxruntime/)
* onnx-runtime (com.microsoft.onnxruntime:onnxruntime:1.28.0 - https://microsoft.github.io/onnxruntime/)
* onnx-runtime (com.microsoft.onnxruntime:onnxruntime_gpu:1.29.0 - https://microsoft.github.io/onnxruntime/)
* onnx-runtime (com.microsoft.onnxruntime:onnxruntime:1.29.0 - https://microsoft.github.io/onnxruntime/)
* SLF4J API Module (org.slf4j:slf4j-api:2.0.18 - http://www.slf4j.org)

The MIT License (MIT)

* ClassGraph (io.github.classgraph:classgraph:4.8.186 - https://github.com/classgraph/classgraph)
* ClassGraph (io.github.classgraph:classgraph:4.8.194 - https://github.com/classgraph/classgraph)
11 changes: 8 additions & 3 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,14 @@ JVM arguments, or the `OPENNLP_MAX_ENTRIES` setting.
CDN (`https://dlcdn.apache.org/opennlp/`) and verifies each download against the
published SHA-512 checksum before use. A mismatch fails the load.

Note that verification happens at **download** time. A model already present in
the local download cache is currently not re-verified when it is loaded again, so
treat the cache directory (`~/.opennlp` by default) as trusted storage and protect
The published checksum is stored next to the model in the download cache, and a
model already present in that cache is re-verified against it on every load. This
check is performed locally and does not contact the CDN again. A cache entry
written by an OpenNLP version that predates this behaviour has no stored checksum;
for those the published checksum is fetched once and then stored, and if it cannot
be retrieved the model is loaded and a warning is logged.

Treat the cache directory (`~/.opennlp` by default) as trusted storage and protect
it accordingly.

The base URL can be overridden with the `OPENNLP_DOWNLOAD_BASE_URL` system
Expand Down
108 changes: 108 additions & 0 deletions dev/README-hunspell-dictionaries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<!--
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.
-->

# Hunspell dictionaries for the affix stemmer

The Hunspell stemmer (`opennlp.tools.stemmer.hunspell`) reads a user-supplied
`.dic` word list and its `.aff` affix file. Apache OpenNLP bundles no dictionary
data. The dictionary's readme states its license.

## Where dictionaries come from

The LibreOffice project maintains Hunspell dictionaries by language at
`github.com/LibreOffice/dictionaries`. Each dictionary has a separate license.
For example, SCOWL is the source for the `en_US` dictionary, with terms in
`README_en_US.txt`. Other sources can be used when the `.aff` and `.dic` files
follow the Hunspell format.

OpenNLP does not ship a URL catalog. Applications that manage downloads can keep a
properties file with an entry id followed by `.url`, `.sha512`, and optionally
`.filename` keys. Use a URL for a stable release or commit.

## Option A: application catalog

Catalog downloads stay inactive until you set `-Dopennlp.download.remote=true`.

```java
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import opennlp.tools.stemmer.hunspell.HunspellDictionaryDownload;
import opennlp.tools.util.DictionaryCatalog;

// JVM flag: -Dopennlp.download.remote=true
try (InputStream in = Files.newInputStream(Path.of("dictionary-catalog.properties"))) {
DictionaryCatalog catalog = DictionaryCatalog.load(in);
HunspellDictionaryDownload.downloadFromCatalog(
catalog, "en_US", Path.of("/tmp/hunspell-en_US"));
}
```

For `en_US`, the catalog ids are `hunspell.en_US.aff`, `hunspell.en_US.dic`, and
optionally `hunspell.en_US.readme`. A complete catalog example lives at
`opennlp-core/opennlp-runtime/src/test/resources/opennlp/tools/util/dictionary-catalog.properties`.
The download test uses local file URLs to exercise this flow without network access.

## Option B: your own files

Fetch `.aff` / `.dic` (and the license readme) with any tool, or with
`ResourceInstaller.install(uri, directory, sha512)`, then load them:

```java
import java.nio.file.Path;
import opennlp.tools.stemmer.Stemmer;
import opennlp.tools.stemmer.hunspell.HunspellDictionary;
import opennlp.tools.stemmer.hunspell.HunspellStemmerFactory;

HunspellDictionary dictionary = HunspellDictionary.load(
Path.of("/tmp/hunspell-en_US/en_US.aff"),
Path.of("/tmp/hunspell-en_US/en_US.dic"));
HunspellStemmerFactory factory = new HunspellStemmerFactory(dictionary);

Stemmer stemmer = factory.newStemmer();
CharSequence stem = stemmer.stem("workers");
```

The result depends on the loaded dictionary. The in-tree manual example uses a
small dictionary and checks that `workers` stems to `worker`.

The dictionary is immutable and safe to share between threads. The factory creates a
new stemmer for each call, so each thread can use its own instance. A dictionary that
declares a non-UTF-8 encoding through the `SET` directive in its `.aff` file is decoded
accordingly; no conversion is required.

## Testing against real dictionaries

The in-tree tests use project-authored fixtures only. An opt-in test class, `HunspellRealDictionaryTest`, also checks everyday morphology with the LibreOffice `en_US`, `de_DE_frami`, and `hu_HU` dictionaries. Point it at one directory containing all listed `<name>.aff` and `<name>.dic` files. A missing dictionary skips the associated test; a dictionary that cannot be loaded fails it.

```
./mvnw test -pl opennlp-core/opennlp-runtime -am \
-Dtest=HunspellRealDictionaryTest -Dsurefire.failIfNoSpecifiedTests=false \
-Dopennlp.hunspell.dict.dir=/tmp/hunspell-dicts
```

## What the engine supports

The engine applies `PFX` and `SFX` rules with strip strings and character-class conditions. It supports a prefix and suffix cross-product, a double suffix sequence connected by continuation classes, identity rules in continuation paths, file-wide `FLAG` modes, file-wide `AF` aliases, and the `SET` encoding declaration. Numeric flags range from 1 through 65000.

Compound decomposition supports `COMPOUNDFLAG`, `COMPOUNDBEGIN`, `COMPOUNDMIDDLE`, `COMPOUNDEND`, `COMPOUNDMIN`, `COMPOUNDWORDMAX`, `COMPOUNDPERMITFLAG`, `COMPOUNDFORBIDFLAG`, `CHECKCOMPOUNDDUP`, `CHECKCOMPOUNDCASE`, and `CHECKCOMPOUNDTRIPLE`. Compound boundaries and minimum lengths use Unicode code points. `NEEDAFFIX` (also named `PSEUDOROOT`), `ONLYINCOMPOUND`, `FORBIDDENWORD`, `CIRCUMFIX`, and `FULLSTRIP` control whether an analysis is accepted.

Other directives are skipped. Their conversion, suggestion, or advanced compound behavior is not applied by this affix stemmer. Comments and unused metadata may contain legacy-encoded bytes even when the file uses UTF-8. Parsed rules and dictionary text are decoded strictly. Default and `long` flag modes preserve raw one-byte flag values used by published UTF-8 dictionaries. Invalid rule counts, aliases, flags, and compound limits fail during loading. Each affix or dictionary stream is rejected when it exceeds `HunspellDictionary.MAX_STREAM_BYTES` (64 MiB).

Skipped directives include `ICONV`, `OCONV`, `COMPLEXPREFIXES`, `COMPOUNDRULE`,
`IGNORE`, and `KEEPCASE`. Loading a dictionary does not apply these rules;
results can differ from Hunspell for words that need them.
126 changes: 126 additions & 0 deletions dev/README-mecab-dictionaries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<!--
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.
-->

# CJK dictionaries for the lattice tokenizer

The lattice tokenizer (`opennlp.tools.tokenize.lattice`) segments Japanese and Korean over a MeCab-format dictionary, and the unigram segmenter handles Chinese over a plain word-frequency lexicon. Apache OpenNLP bundles no dictionary data: you download a dictionary from the project of your choice, and each dictionary contains its own license. Read the license file inside the archive before use.

## Known MeCab-format dictionary projects

| Catalog id | Dictionary | Language | Encoding |
|---|---|---|---|
| `mecab.ipadic` | IPADIC 2.7.0 | Japanese | EUC-JP |
| `mecab.ko-dic` | mecab-ko-dic 2.1.1 | Korean | UTF-8 |

Example download URLs and SHA-512 digests for those ids live in the test resource
`opennlp-core/opennlp-runtime/src/test/resources/opennlp/tools/util/dictionary-catalog.properties`. Both archives are
gzip-compressed tars; `MecabDictionaryInstaller` reads the ustar, pax, and GNU
formats through `ResourceInstaller`.

The installer extracts only the dictionary payload: the `*.csv` and `*.def` files a
`MecabDictionary` reads, plus the `dicrc` configuration file the distributions ship
alongside them. `ResourceInstaller` rejects paths outside the staging directory,
then `MecabDictionaryInstaller` flattens the selected files into the target. The
returned value is the number of dictionary files installed.

## Option A: opt-in catalog install

Applications supply the catalog. Catalog URLs are inactive until you set
`-Dopennlp.download.remote=true` or the equivalent system property in code.

```java
import java.nio.file.Path;
import opennlp.tools.tokenize.lattice.MecabDictionaryInstaller;
import opennlp.tools.util.DictionaryCatalog;

// JVM flag: -Dopennlp.download.remote=true
DictionaryCatalog catalog = DictionaryCatalog.load(catalogProperties);
int files = MecabDictionaryInstaller.installFromCatalog(
catalog, "mecab.ipadic", Path.of("ipadic"));
```

## Option B: your own URL and digest

```java
import java.net.URI;
import java.nio.file.Path;
import opennlp.tools.tokenize.lattice.MecabDictionaryInstaller;

String expectedSha512 = "..."; // the 128-hex SHA-512 of the archive
int files = MecabDictionaryInstaller.install(
URI.create("https://example.example/dict.tar.gz"),
Path.of("dict"),
expectedSha512);
```

A local `file:` URI may omit the digest:
`MecabDictionaryInstaller.install(localArchive.toUri(), targetDirectory)`.
HTTP and HTTPS sources require a digest. Other URI schemes are rejected.

## Size budgets for larger dictionaries

Fetching and unpacking go through `ResourceInstaller` and are bounded so a crafted
archive cannot fill the disk: by default one download is capped at 1 GiB, the
unpacked payload at 4 GiB, and the archive at 100000 entries. IPADIC and
mecab-ko-dic fit comfortably. For larger dictionaries, such as UniDic, raise the
limits at JVM startup:

```bash
-Dopennlp.download.max.bytes=4294967296 \
-Dopennlp.install.max.total.bytes=8589934592 \
-Dopennlp.install.max.entries=200000
```

Missing, invalid, and nonpositive property values use the default limits.

## Load and tokenize

`MecabDictionary.load(Path)` assumes UTF-8. IPADIC needs the two-argument overload:

```java
import java.nio.charset.Charset;
import java.nio.file.Path;
import opennlp.tools.tokenize.lattice.LatticeTokenizer;
import opennlp.tools.tokenize.lattice.MecabDictionary;

MecabDictionary dictionary =
MecabDictionary.load(Path.of("ipadic"), Charset.forName("EUC-JP"));
LatticeTokenizer tokenizer = new LatticeTokenizer(dictionary);
// "Tokyo-to ni iku" (go to the Tokyo metropolis), escaped to keep this file ASCII
String[] tokens = tokenizer.tokenize("\u6771\u4EAC\u90FD\u306B\u884C\u304F");
```

For a UTF-8 dictionary such as mecab-ko-dic, `MecabDictionary.load(Path.of("ko-dic"))`
is enough. Loaded dictionaries and tokenizers are immutable and safe to share between
threads, so load once and reuse.

## Chinese: the unigram segmenter needs only a frequency lexicon

`opennlp.tools.tokenize.lattice.UnigramSegmenter` does not use MeCab dictionaries. It
loads a plain text lexicon, one entry per line: the word, its count, and optionally a
tag, separated by whitespace. Any word-frequency list you have the rights to use works:

```java
import java.nio.file.Path;
import opennlp.tools.tokenize.lattice.UnigramSegmenter;

UnigramSegmenter segmenter = UnigramSegmenter.load(Path.of("words.txt"));
// "wo laidao Beijing Tian'anmen" (I arrive at Beijing Tiananmen), escaped as above
String[] tokens = segmenter.tokenize("\u6211\u6765\u5230\u5317\u4EAC\u5929\u5B89\u95E8");
```

The lexicon archive includes its license; OpenNLP bundles no data.
Loading