You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@opennlp.apache.org by kr...@apache.org on 2026/07/16 10:47:19 UTC
(opennlp) 04/04: OPENNLP-1886: Share the char-array stemming shell, cite the algorithms with verified links, drop the dangling deprecation
This is an automated email from the ASF dual-hosted git repository.
krickert pushed a commit to branch light-stemmers
in repository https://gitbox.apache.org/repos/asf/opennlp.git
commit 2d00a837d54b2a26322605ff4ee0964a53fb68df
Author: Kristian Rickert <kr...@gmail.com>
AuthorDate: Thu Jul 16 06:21:14 2026 -0400
OPENNLP-1886: Share the char-array stemming shell, cite the algorithms with verified links, drop the dangling deprecation
---
.../stemmer/light/AbstractCharArrayStemmer.java | 52 ++++++++++++++++
.../tools/stemmer/light/EnglishMinimalStemmer.java | 27 +++------
.../tools/stemmer/light/FinnishLightStemmer.java | 45 ++++----------
.../tools/stemmer/light/FrenchLightStemmer.java | 32 ++++------
.../tools/stemmer/light/FrenchMinimalStemmer.java | 30 ++++------
.../tools/stemmer/light/GermanLightStemmer.java | 29 +++------
.../tools/stemmer/light/GermanMinimalStemmer.java | 29 +++------
.../tools/stemmer/light/HungarianLightStemmer.java | 45 ++++----------
.../tools/stemmer/light/ItalianLightStemmer.java | 29 +++------
.../tools/stemmer/light/NorwegianLightStemmer.java | 42 +++++--------
.../stemmer/light/NorwegianMinimalStemmer.java | 41 +++++--------
.../stemmer/light/PortugueseLightStemmer.java | 30 ++++------
.../tools/stemmer/light/RussianLightStemmer.java | 30 ++++------
.../tools/stemmer/light/SpanishLightStemmer.java | 29 +++------
.../tools/stemmer/light/SpanishMinimalStemmer.java | 32 ++++------
.../opennlp/tools/stemmer/light/StemmerUtil.java | 69 +++++-----------------
.../tools/stemmer/light/SwedishLightStemmer.java | 30 ++++------
.../tools/stemmer/light/SwedishMinimalStemmer.java | 33 ++++-------
.../stemmer/light/LightStemmerParityTest.java | 6 +-
19 files changed, 249 insertions(+), 411 deletions(-)
diff --git a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/AbstractCharArrayStemmer.java b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/AbstractCharArrayStemmer.java
new file mode 100644
index 000000000..16f994c19
--- /dev/null
+++ b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/AbstractCharArrayStemmer.java
@@ -0,0 +1,52 @@
+/*
+ * 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.stemmer.light;
+
+import opennlp.tools.stemmer.Stemmer;
+
+/**
+ * Base class of the light and minimal stemmers. It implements the public
+ * {@link Stemmer#stem(CharSequence)} contract once: the word is validated, copied into a
+ * {@code char[]} buffer, stemmed in place by the concrete algorithm via {@link #stem(char[], int)},
+ * and returned as a {@link String} of the resulting length.
+ */
+abstract class AbstractCharArrayStemmer implements Stemmer {
+
+ /** {@inheritDoc} */
+ @Override
+ public CharSequence stem(CharSequence word) {
+ if (word == null) {
+ throw new IllegalArgumentException("word must not be null");
+ }
+ final int length = word.length();
+ final char[] buffer = new char[length];
+ for (int i = 0; i < length; i++) {
+ buffer[i] = word.charAt(i);
+ }
+ final int stemmedLength = stem(buffer, length);
+ return new String(buffer, 0, stemmedLength);
+ }
+
+ /**
+ * Stems the buffer prefix in place.
+ *
+ * @param s The buffer holding the word; the algorithm may overwrite characters in place.
+ * @param len The filled length of the buffer.
+ * @return The length of the stem within {@code s}.
+ */
+ abstract int stem(char[] s, int len);
+}
diff --git a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/EnglishMinimalStemmer.java b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/EnglishMinimalStemmer.java
index d1f9bb6f8..500599865 100644
--- a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/EnglishMinimalStemmer.java
+++ b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/EnglishMinimalStemmer.java
@@ -20,10 +20,13 @@ package opennlp.tools.stemmer.light;
import opennlp.tools.commons.ThreadSafe;
import opennlp.tools.stemmer.Stemmer;
import opennlp.tools.stemmer.StemmerFactory;
+
/**
* Minimal plural stemmer for English.
*
- * <p>This stemmer implements the "S-Stemmer" from <i>How Effective Is Suffixing?</i> Donna Harman.
+ * <p>This stemmer implements the "S-Stemmer" from
+ * <a href="https://doi.org/10.1002/(SICI)1097-4571(199101)42:1%3C7::AID-ASI2%3E3.0.CO;2-P">
+ * <i>How Effective Is Suffixing?</i></a> by Donna Harman (JASIS 42(1), 1991).
*
* <p>Adapted from the identically named algorithm in Apache Lucene's analysis-common module.
* Instances are stateless and safe for concurrent use by multiple threads; each instance is also
@@ -31,30 +34,18 @@ import opennlp.tools.stemmer.StemmerFactory;
* case-folding normalization step; the stemmer does not fold case itself.</p>
*/
@ThreadSafe
-public final class EnglishMinimalStemmer implements Stemmer, StemmerFactory {
- /** {@inheritDoc} */
- @Override
- public CharSequence stem(CharSequence word) {
- if (word == null) {
- throw new IllegalArgumentException("The word must not be null.");
- }
- final int length = word.length();
- final char[] buffer = new char[length];
- for (int i = 0; i < length; i++) {
- buffer[i] = word.charAt(i);
- }
- final int stemmedLength = stem(buffer, length);
- return new String(buffer, 0, stemmedLength);
- }
-
+public final class EnglishMinimalStemmer extends AbstractCharArrayStemmer
+ implements StemmerFactory {
/** {@inheritDoc} */
@Override
public Stemmer newStemmer() {
return this;
}
+ /** {@inheritDoc} */
+ @Override
@SuppressWarnings("fallthrough")
- private int stem(char[] s, int len) {
+ int stem(char[] s, int len) {
if (len < 3 || s[len - 1] != 's') return len;
switch (s[len - 2]) {
diff --git a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/FinnishLightStemmer.java b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/FinnishLightStemmer.java
index b1035ace6..d06e6ff1f 100644
--- a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/FinnishLightStemmer.java
+++ b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/FinnishLightStemmer.java
@@ -57,11 +57,16 @@ import opennlp.tools.stemmer.StemmerFactory;
import static opennlp.tools.stemmer.light.StemmerUtil.delete;
import static opennlp.tools.stemmer.light.StemmerUtil.endsWith;
+import static opennlp.tools.stemmer.light.StemmerUtil.isVowel;
+
/**
* Light Stemmer for Finnish.
*
- * <p>This stemmer implements the algorithm described in: <i>Report on CLEF-2003 Monolingual
- * Tracks</i> Jacques Savoy
+ * <p>This stemmer implements the algorithm described in:
+ * <a href="https://ceur-ws.org/Vol-1169/CLEF2003wn-adhoc-Savoy2003b.pdf"><i>Report on CLEF-2003
+ * Monolingual Tracks</i></a> by Jacques Savoy (CLEF 2003 working notes). The inline suffix
+ * literals mirror the suffix tables of the cited paper and are kept inline for fidelity to the
+ * published algorithm.
*
* <p>Adapted from the identically named algorithm in Apache Lucene's analysis-common module.
* Instances are stateless and safe for concurrent use by multiple threads; each instance is also
@@ -69,29 +74,17 @@ import static opennlp.tools.stemmer.light.StemmerUtil.endsWith;
* case-folding normalization step; the stemmer does not fold case itself.</p>
*/
@ThreadSafe
-public final class FinnishLightStemmer implements Stemmer, StemmerFactory {
- /** {@inheritDoc} */
- @Override
- public CharSequence stem(CharSequence word) {
- if (word == null) {
- throw new IllegalArgumentException("The word must not be null.");
- }
- final int length = word.length();
- final char[] buffer = new char[length];
- for (int i = 0; i < length; i++) {
- buffer[i] = word.charAt(i);
- }
- final int stemmedLength = stem(buffer, length);
- return new String(buffer, 0, stemmedLength);
- }
-
+public final class FinnishLightStemmer extends AbstractCharArrayStemmer
+ implements StemmerFactory {
/** {@inheritDoc} */
@Override
public Stemmer newStemmer() {
return this;
}
- private int stem(char[] s, int len) {
+ /** {@inheritDoc} */
+ @Override
+ int stem(char[] s, int len) {
if (len < 4) return len;
for (int i = 0; i < len; i++)
@@ -247,18 +240,4 @@ public final class FinnishLightStemmer implements Stemmer, StemmerFactory {
return len;
}
-
- private boolean isVowel(char ch) {
- switch (ch) {
- case 'a':
- case 'e':
- case 'i':
- case 'o':
- case 'u':
- case 'y':
- return true;
- default:
- return false;
- }
- }
}
diff --git a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/FrenchLightStemmer.java b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/FrenchLightStemmer.java
index 38f11138a..2efea82e7 100644
--- a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/FrenchLightStemmer.java
+++ b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/FrenchLightStemmer.java
@@ -57,11 +57,15 @@ import opennlp.tools.stemmer.StemmerFactory;
import static opennlp.tools.stemmer.light.StemmerUtil.delete;
import static opennlp.tools.stemmer.light.StemmerUtil.endsWith;
+
/**
* Light Stemmer for French.
*
- * <p>This stemmer implements the "UniNE" algorithm in: <i>Light Stemming Approaches for the French,
- * Portuguese, German and Hungarian Languages</i> Jacques Savoy
+ * <p>This stemmer implements the "UniNE" algorithm in:
+ * <a href="https://doi.org/10.1145/1141277.1141523"><i>Light Stemming Approaches for the French,
+ * Portuguese, German and Hungarian Languages</i></a> by Jacques Savoy (ACM SAC 2006). The inline
+ * suffix literals mirror the suffix tables of the cited paper and are kept inline for fidelity
+ * to the published algorithm.
*
* <p>Adapted from the identically named algorithm in Apache Lucene's analysis-common module.
* Instances are stateless and safe for concurrent use by multiple threads; each instance is also
@@ -69,29 +73,17 @@ import static opennlp.tools.stemmer.light.StemmerUtil.endsWith;
* case-folding normalization step; the stemmer does not fold case itself.</p>
*/
@ThreadSafe
-public final class FrenchLightStemmer implements Stemmer, StemmerFactory {
- /** {@inheritDoc} */
- @Override
- public CharSequence stem(CharSequence word) {
- if (word == null) {
- throw new IllegalArgumentException("The word must not be null.");
- }
- final int length = word.length();
- final char[] buffer = new char[length];
- for (int i = 0; i < length; i++) {
- buffer[i] = word.charAt(i);
- }
- final int stemmedLength = stem(buffer, length);
- return new String(buffer, 0, stemmedLength);
- }
-
+public final class FrenchLightStemmer extends AbstractCharArrayStemmer
+ implements StemmerFactory {
/** {@inheritDoc} */
@Override
public Stemmer newStemmer() {
return this;
}
- private int stem(char[] s, int len) {
+ /** {@inheritDoc} */
+ @Override
+ int stem(char[] s, int len) {
if (len > 5 && s[len - 1] == 'x') {
if (s[len - 3] == 'a' && s[len - 2] == 'u' && s[len - 4] != 'e') s[len - 2] = 'l';
len--;
@@ -270,6 +262,7 @@ public final class FrenchLightStemmer implements Stemmer, StemmerFactory {
char ch = s[0];
for (int i = 1; i < len; i++) {
+ // Character.isLetter is intentional: it is the letter test of the ported algorithm.
if (s[i] == ch && Character.isLetter(ch)) len = delete(s, i--, len);
else ch = s[i];
}
@@ -281,6 +274,7 @@ public final class FrenchLightStemmer implements Stemmer, StemmerFactory {
if (s[len - 1] == 'r') len--;
if (s[len - 1] == 'e') len--;
if (s[len - 1] == 'e') len--;
+ // Character.isLetter is intentional: it is the letter test of the ported algorithm.
if (s[len - 1] == s[len - 2] && Character.isLetter(s[len - 1])) len--;
}
return len;
diff --git a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/FrenchMinimalStemmer.java b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/FrenchMinimalStemmer.java
index 88ddde778..14a593101 100644
--- a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/FrenchMinimalStemmer.java
+++ b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/FrenchMinimalStemmer.java
@@ -54,11 +54,14 @@ package opennlp.tools.stemmer.light;
import opennlp.tools.commons.ThreadSafe;
import opennlp.tools.stemmer.Stemmer;
import opennlp.tools.stemmer.StemmerFactory;
+
/**
* Light Stemmer for French.
*
- * <p>This stemmer implements the following algorithm: <i>A Stemming procedure and stopword list for
- * general French corpora.</i> Jacques Savoy.
+ * <p>This stemmer implements the following algorithm:
+ * <a href="https://doi.org/10.1002/(SICI)1097-4571(1999)50:10%3C944::AID-ASI9%3E3.0.CO;2-Q">
+ * <i>A Stemming Procedure and Stopword List for General French Corpora</i></a> by Jacques Savoy
+ * (JASIS 50(10), 1999).
*
* <p>Adapted from the identically named algorithm in Apache Lucene's analysis-common module.
* Instances are stateless and safe for concurrent use by multiple threads; each instance is also
@@ -66,29 +69,17 @@ import opennlp.tools.stemmer.StemmerFactory;
* case-folding normalization step; the stemmer does not fold case itself.</p>
*/
@ThreadSafe
-public final class FrenchMinimalStemmer implements Stemmer, StemmerFactory {
- /** {@inheritDoc} */
- @Override
- public CharSequence stem(CharSequence word) {
- if (word == null) {
- throw new IllegalArgumentException("The word must not be null.");
- }
- final int length = word.length();
- final char[] buffer = new char[length];
- for (int i = 0; i < length; i++) {
- buffer[i] = word.charAt(i);
- }
- final int stemmedLength = stem(buffer, length);
- return new String(buffer, 0, stemmedLength);
- }
-
+public final class FrenchMinimalStemmer extends AbstractCharArrayStemmer
+ implements StemmerFactory {
/** {@inheritDoc} */
@Override
public Stemmer newStemmer() {
return this;
}
- private int stem(char[] s, int len) {
+ /** {@inheritDoc} */
+ @Override
+ int stem(char[] s, int len) {
if (len < 6) return len;
if (s[len - 1] == 'x') {
@@ -100,6 +91,7 @@ public final class FrenchMinimalStemmer implements Stemmer, StemmerFactory {
if (s[len - 1] == 'r') len--;
if (s[len - 1] == 'e') len--;
if (s[len - 1] == 'é') len--;
+ // Character.isLetter is intentional: it is the letter test of the ported algorithm.
if (s[len - 1] == s[len - 2] && Character.isLetter(s[len - 1])) len--;
return len;
}
diff --git a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/GermanLightStemmer.java b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/GermanLightStemmer.java
index e1daca454..f0b81f04c 100644
--- a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/GermanLightStemmer.java
+++ b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/GermanLightStemmer.java
@@ -54,11 +54,13 @@ package opennlp.tools.stemmer.light;
import opennlp.tools.commons.ThreadSafe;
import opennlp.tools.stemmer.Stemmer;
import opennlp.tools.stemmer.StemmerFactory;
+
/**
* Light Stemmer for German.
*
- * <p>This stemmer implements the "UniNE" algorithm in: <i>Light Stemming Approaches for the French,
- * Portuguese, German and Hungarian Languages</i> Jacques Savoy
+ * <p>This stemmer implements the "UniNE" algorithm in:
+ * <a href="https://doi.org/10.1145/1141277.1141523"><i>Light Stemming Approaches for the French,
+ * Portuguese, German and Hungarian Languages</i></a> by Jacques Savoy (ACM SAC 2006).
*
* <p>Adapted from the identically named algorithm in Apache Lucene's analysis-common module.
* Instances are stateless and safe for concurrent use by multiple threads; each instance is also
@@ -66,30 +68,17 @@ import opennlp.tools.stemmer.StemmerFactory;
* case-folding normalization step; the stemmer does not fold case itself.</p>
*/
@ThreadSafe
-public final class GermanLightStemmer implements Stemmer, StemmerFactory {
- /** {@inheritDoc} */
- @Override
- public CharSequence stem(CharSequence word) {
- if (word == null) {
- throw new IllegalArgumentException("The word must not be null.");
- }
- final int length = word.length();
- final char[] buffer = new char[length];
- for (int i = 0; i < length; i++) {
- buffer[i] = word.charAt(i);
- }
- final int stemmedLength = stem(buffer, length);
- return new String(buffer, 0, stemmedLength);
- }
-
+public final class GermanLightStemmer extends AbstractCharArrayStemmer
+ implements StemmerFactory {
/** {@inheritDoc} */
@Override
public Stemmer newStemmer() {
return this;
}
-
- private int stem(char[] s, int len) {
+ /** {@inheritDoc} */
+ @Override
+ int stem(char[] s, int len) {
for (int i = 0; i < len; i++)
switch (s[i]) {
case 'ä':
diff --git a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/GermanMinimalStemmer.java b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/GermanMinimalStemmer.java
index fe711a2ed..d6c2db31f 100644
--- a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/GermanMinimalStemmer.java
+++ b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/GermanMinimalStemmer.java
@@ -54,11 +54,13 @@ package opennlp.tools.stemmer.light;
import opennlp.tools.commons.ThreadSafe;
import opennlp.tools.stemmer.Stemmer;
import opennlp.tools.stemmer.StemmerFactory;
+
/**
* Minimal Stemmer for German.
*
- * <p>This stemmer implements the following algorithm: <i>Morphologie et recherche d'information</i>
- * Jacques Savoy.
+ * <p>This stemmer implements the following algorithm:
+ * <a href="http://members.unine.ch/jacques.savoy/clef/morpho.pdf"><i>Morphologie et recherche
+ * d'information</i></a> by Jacques Savoy (technical report).
*
* <p>Adapted from the identically named algorithm in Apache Lucene's analysis-common module.
* Instances are stateless and safe for concurrent use by multiple threads; each instance is also
@@ -66,30 +68,17 @@ import opennlp.tools.stemmer.StemmerFactory;
* case-folding normalization step; the stemmer does not fold case itself.</p>
*/
@ThreadSafe
-public final class GermanMinimalStemmer implements Stemmer, StemmerFactory {
- /** {@inheritDoc} */
- @Override
- public CharSequence stem(CharSequence word) {
- if (word == null) {
- throw new IllegalArgumentException("The word must not be null.");
- }
- final int length = word.length();
- final char[] buffer = new char[length];
- for (int i = 0; i < length; i++) {
- buffer[i] = word.charAt(i);
- }
- final int stemmedLength = stem(buffer, length);
- return new String(buffer, 0, stemmedLength);
- }
-
+public final class GermanMinimalStemmer extends AbstractCharArrayStemmer
+ implements StemmerFactory {
/** {@inheritDoc} */
@Override
public Stemmer newStemmer() {
return this;
}
-
- private int stem(char[] s, int len) {
+ /** {@inheritDoc} */
+ @Override
+ int stem(char[] s, int len) {
if (len < 5) return len;
for (int i = 0; i < len; i++)
diff --git a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/HungarianLightStemmer.java b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/HungarianLightStemmer.java
index cb8fed7c6..ad0b9a962 100644
--- a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/HungarianLightStemmer.java
+++ b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/HungarianLightStemmer.java
@@ -56,11 +56,16 @@ import opennlp.tools.stemmer.Stemmer;
import opennlp.tools.stemmer.StemmerFactory;
import static opennlp.tools.stemmer.light.StemmerUtil.endsWith;
+import static opennlp.tools.stemmer.light.StemmerUtil.isVowel;
+
/**
* Light Stemmer for Hungarian.
*
- * <p>This stemmer implements the "UniNE" algorithm in: <i>Light Stemming Approaches for the French,
- * Portuguese, German and Hungarian Languages</i> Jacques Savoy
+ * <p>This stemmer implements the "UniNE" algorithm in:
+ * <a href="https://doi.org/10.1145/1141277.1141523"><i>Light Stemming Approaches for the French,
+ * Portuguese, German and Hungarian Languages</i></a> by Jacques Savoy (ACM SAC 2006). The inline
+ * suffix literals mirror the suffix tables of the cited paper and are kept inline for fidelity
+ * to the published algorithm.
*
* <p>Adapted from the identically named algorithm in Apache Lucene's analysis-common module.
* Instances are stateless and safe for concurrent use by multiple threads; each instance is also
@@ -68,29 +73,17 @@ import static opennlp.tools.stemmer.light.StemmerUtil.endsWith;
* case-folding normalization step; the stemmer does not fold case itself.</p>
*/
@ThreadSafe
-public final class HungarianLightStemmer implements Stemmer, StemmerFactory {
- /** {@inheritDoc} */
- @Override
- public CharSequence stem(CharSequence word) {
- if (word == null) {
- throw new IllegalArgumentException("The word must not be null.");
- }
- final int length = word.length();
- final char[] buffer = new char[length];
- for (int i = 0; i < length; i++) {
- buffer[i] = word.charAt(i);
- }
- final int stemmedLength = stem(buffer, length);
- return new String(buffer, 0, stemmedLength);
- }
-
+public final class HungarianLightStemmer extends AbstractCharArrayStemmer
+ implements StemmerFactory {
/** {@inheritDoc} */
@Override
public Stemmer newStemmer() {
return this;
}
- private int stem(char[] s, int len) {
+ /** {@inheritDoc} */
+ @Override
+ int stem(char[] s, int len) {
for (int i = 0; i < len; i++)
switch (s[i]) {
case 'á':
@@ -256,18 +249,4 @@ public final class HungarianLightStemmer implements Stemmer, StemmerFactory {
}
return len;
}
-
- private boolean isVowel(char ch) {
- switch (ch) {
- case 'a':
- case 'e':
- case 'i':
- case 'o':
- case 'u':
- case 'y':
- return true;
- default:
- return false;
- }
- }
}
diff --git a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/ItalianLightStemmer.java b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/ItalianLightStemmer.java
index 2664775df..23bee8ed2 100644
--- a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/ItalianLightStemmer.java
+++ b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/ItalianLightStemmer.java
@@ -54,11 +54,13 @@ package opennlp.tools.stemmer.light;
import opennlp.tools.commons.ThreadSafe;
import opennlp.tools.stemmer.Stemmer;
import opennlp.tools.stemmer.StemmerFactory;
+
/**
* Light Stemmer for Italian.
*
- * <p>This stemmer implements the algorithm described in: <i>Report on CLEF-2001 Experiments</i>
- * Jacques Savoy
+ * <p>This stemmer implements the algorithm described in:
+ * <a href="https://doi.org/10.1007/3-540-45691-0_3"><i>Report on CLEF-2001 Experiments</i></a>
+ * by Jacques Savoy (CLEF 2001, LNCS 2406).
*
* <p>Adapted from the identically named algorithm in Apache Lucene's analysis-common module.
* Instances are stateless and safe for concurrent use by multiple threads; each instance is also
@@ -66,30 +68,17 @@ import opennlp.tools.stemmer.StemmerFactory;
* case-folding normalization step; the stemmer does not fold case itself.</p>
*/
@ThreadSafe
-public final class ItalianLightStemmer implements Stemmer, StemmerFactory {
- /** {@inheritDoc} */
- @Override
- public CharSequence stem(CharSequence word) {
- if (word == null) {
- throw new IllegalArgumentException("The word must not be null.");
- }
- final int length = word.length();
- final char[] buffer = new char[length];
- for (int i = 0; i < length; i++) {
- buffer[i] = word.charAt(i);
- }
- final int stemmedLength = stem(buffer, length);
- return new String(buffer, 0, stemmedLength);
- }
-
+public final class ItalianLightStemmer extends AbstractCharArrayStemmer
+ implements StemmerFactory {
/** {@inheritDoc} */
@Override
public Stemmer newStemmer() {
return this;
}
-
- private int stem(char[] s, int len) {
+ /** {@inheritDoc} */
+ @Override
+ int stem(char[] s, int len) {
if (len < 6) return len;
for (int i = 0; i < len; i++)
diff --git a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/NorwegianLightStemmer.java b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/NorwegianLightStemmer.java
index 4caeb4815..51b49a483 100644
--- a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/NorwegianLightStemmer.java
+++ b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/NorwegianLightStemmer.java
@@ -56,40 +56,22 @@ import opennlp.tools.stemmer.Stemmer;
import opennlp.tools.stemmer.StemmerFactory;
import static opennlp.tools.stemmer.light.StemmerUtil.endsWith;
+
/**
* Light Stemmer for Norwegian.
*
- * <p>Parts of this stemmer is adapted from SwedishLightStemFilter, except that while the Swedish
- * one has a pre-defined rule set and a corresponding corpus to validate against whereas the
+ * <p>Parts of this stemmer are adapted from the {@link SwedishLightStemmer}; while the Swedish
+ * algorithm has a pre-defined rule set and a corresponding corpus to validate against, the
* Norwegian one is hand crafted.
*
* <p>Adapted from the identically named algorithm in Apache Lucene's analysis-common module.
- * Instances are stateless and safe for concurrent use by multiple threads; each instance is also
+ * Instances are immutable and safe for concurrent use by multiple threads; each instance is also
* its own {@link StemmerFactory}. Input is expected to be lowercase, as produced by a
* case-folding normalization step; the stemmer does not fold case itself.</p>
*/
@ThreadSafe
-public final class NorwegianLightStemmer implements Stemmer, StemmerFactory {
- /** {@inheritDoc} */
- @Override
- public CharSequence stem(CharSequence word) {
- if (word == null) {
- throw new IllegalArgumentException("The word must not be null.");
- }
- final int length = word.length();
- final char[] buffer = new char[length];
- for (int i = 0; i < length; i++) {
- buffer[i] = word.charAt(i);
- }
- final int stemmedLength = stem(buffer, length);
- return new String(buffer, 0, stemmedLength);
- }
-
- /** {@inheritDoc} */
- @Override
- public Stemmer newStemmer() {
- return this;
- }
+public final class NorwegianLightStemmer extends AbstractCharArrayStemmer
+ implements StemmerFactory {
private final boolean useBokmaal;
private final boolean useNynorsk;
@@ -118,8 +100,16 @@ public final class NorwegianLightStemmer implements Stemmer, StemmerFactory {
useNynorsk = nynorsk;
}
- private int stem(char[] s, int len) {
- // Remove posessive -s (bilens -> bilen) and continue checking
+ /** {@inheritDoc} */
+ @Override
+ public Stemmer newStemmer() {
+ return this;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ int stem(char[] s, int len) {
+ // Remove possessive -s (bilens -> bilen) and continue checking
if (len > 4 && s[len - 1] == 's') len--;
// Remove common endings, single-pass
diff --git a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/NorwegianMinimalStemmer.java b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/NorwegianMinimalStemmer.java
index e079491fd..766099df7 100644
--- a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/NorwegianMinimalStemmer.java
+++ b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/NorwegianMinimalStemmer.java
@@ -58,37 +58,18 @@ import opennlp.tools.stemmer.StemmerFactory;
import static opennlp.tools.stemmer.light.StemmerUtil.endsWith;
/**
- * Minimal Stemmer for Norwegian Bokmål (no-nb) and Nynorsk (no-nn)
+ * Minimal Stemmer for Norwegian Bokmål ({@code nb}) and Nynorsk ({@code nn}).
*
- * <p>Stems known plural forms for Norwegian nouns only, together with genitiv -s
+ * <p>Stems known plural forms for Norwegian nouns only, together with the genitive {@code -s}.
*
* <p>Adapted from the identically named algorithm in Apache Lucene's analysis-common module.
- * Instances are stateless and safe for concurrent use by multiple threads; each instance is also
+ * Instances are immutable and safe for concurrent use by multiple threads; each instance is also
* its own {@link StemmerFactory}. Input is expected to be lowercase, as produced by a
* case-folding normalization step; the stemmer does not fold case itself.</p>
*/
@ThreadSafe
-public final class NorwegianMinimalStemmer implements Stemmer, StemmerFactory {
- /** {@inheritDoc} */
- @Override
- public CharSequence stem(CharSequence word) {
- if (word == null) {
- throw new IllegalArgumentException("The word must not be null.");
- }
- final int length = word.length();
- final char[] buffer = new char[length];
- for (int i = 0; i < length; i++) {
- buffer[i] = word.charAt(i);
- }
- final int stemmedLength = stem(buffer, length);
- return new String(buffer, 0, stemmedLength);
- }
-
- /** {@inheritDoc} */
- @Override
- public Stemmer newStemmer() {
- return this;
- }
+public final class NorwegianMinimalStemmer extends AbstractCharArrayStemmer
+ implements StemmerFactory {
private final boolean useBokmaal;
private final boolean useNynorsk;
@@ -117,8 +98,16 @@ public final class NorwegianMinimalStemmer implements Stemmer, StemmerFactory {
useNynorsk = nynorsk;
}
- private int stem(char[] s, int len) {
- // Remove genitiv s
+ /** {@inheritDoc} */
+ @Override
+ public Stemmer newStemmer() {
+ return this;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ int stem(char[] s, int len) {
+ // Remove genitive s
if (len > 4 && s[len - 1] == 's') len--;
if (len > 5
diff --git a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/PortugueseLightStemmer.java b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/PortugueseLightStemmer.java
index bf85bbb26..ba155ce75 100644
--- a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/PortugueseLightStemmer.java
+++ b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/PortugueseLightStemmer.java
@@ -56,11 +56,15 @@ import opennlp.tools.stemmer.Stemmer;
import opennlp.tools.stemmer.StemmerFactory;
import static opennlp.tools.stemmer.light.StemmerUtil.endsWith;
+
/**
* Light Stemmer for Portuguese
*
- * <p>This stemmer implements the "UniNE" algorithm in: <i>Light Stemming Approaches for the French,
- * Portuguese, German and Hungarian Languages</i> Jacques Savoy
+ * <p>This stemmer implements the "UniNE" algorithm in:
+ * <a href="https://doi.org/10.1145/1141277.1141523"><i>Light Stemming Approaches for the French,
+ * Portuguese, German and Hungarian Languages</i></a> by Jacques Savoy (ACM SAC 2006). The inline
+ * suffix literals mirror the suffix tables of the cited paper and are kept inline for fidelity
+ * to the published algorithm.
*
* <p>Adapted from the identically named algorithm in Apache Lucene's analysis-common module.
* Instances are stateless and safe for concurrent use by multiple threads; each instance is also
@@ -68,29 +72,17 @@ import static opennlp.tools.stemmer.light.StemmerUtil.endsWith;
* case-folding normalization step; the stemmer does not fold case itself.</p>
*/
@ThreadSafe
-public final class PortugueseLightStemmer implements Stemmer, StemmerFactory {
- /** {@inheritDoc} */
- @Override
- public CharSequence stem(CharSequence word) {
- if (word == null) {
- throw new IllegalArgumentException("The word must not be null.");
- }
- final int length = word.length();
- final char[] buffer = new char[length];
- for (int i = 0; i < length; i++) {
- buffer[i] = word.charAt(i);
- }
- final int stemmedLength = stem(buffer, length);
- return new String(buffer, 0, stemmedLength);
- }
-
+public final class PortugueseLightStemmer extends AbstractCharArrayStemmer
+ implements StemmerFactory {
/** {@inheritDoc} */
@Override
public Stemmer newStemmer() {
return this;
}
- private int stem(char[] s, int len) {
+ /** {@inheritDoc} */
+ @Override
+ int stem(char[] s, int len) {
if (len < 4) return len;
len = removeSuffix(s, len);
diff --git a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/RussianLightStemmer.java b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/RussianLightStemmer.java
index 2089b00c2..2e9e2a55d 100644
--- a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/RussianLightStemmer.java
+++ b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/RussianLightStemmer.java
@@ -56,11 +56,15 @@ import opennlp.tools.stemmer.Stemmer;
import opennlp.tools.stemmer.StemmerFactory;
import static opennlp.tools.stemmer.light.StemmerUtil.endsWith;
+
/**
* Light Stemmer for Russian.
*
- * <p>This stemmer implements the following algorithm: <i>Indexing and Searching Strategies for the
- * Russian Language.</i> Ljiljana Dolamic and Jacques Savoy.
+ * <p>This stemmer implements the following algorithm:
+ * <a href="https://doi.org/10.1002/asi.21191"><i>Indexing and Searching Strategies for the
+ * Russian Language</i></a> by Ljiljana Dolamic and Jacques Savoy (JASIST 60(12), 2009). The
+ * inline suffix literals mirror the suffix tables of the cited paper and are kept inline for
+ * fidelity to the published algorithm.
*
* <p>Adapted from the identically named algorithm in Apache Lucene's analysis-common module.
* Instances are stateless and safe for concurrent use by multiple threads; each instance is also
@@ -68,29 +72,17 @@ import static opennlp.tools.stemmer.light.StemmerUtil.endsWith;
* case-folding normalization step; the stemmer does not fold case itself.</p>
*/
@ThreadSafe
-public final class RussianLightStemmer implements Stemmer, StemmerFactory {
- /** {@inheritDoc} */
- @Override
- public CharSequence stem(CharSequence word) {
- if (word == null) {
- throw new IllegalArgumentException("The word must not be null.");
- }
- final int length = word.length();
- final char[] buffer = new char[length];
- for (int i = 0; i < length; i++) {
- buffer[i] = word.charAt(i);
- }
- final int stemmedLength = stem(buffer, length);
- return new String(buffer, 0, stemmedLength);
- }
-
+public final class RussianLightStemmer extends AbstractCharArrayStemmer
+ implements StemmerFactory {
/** {@inheritDoc} */
@Override
public Stemmer newStemmer() {
return this;
}
- private int stem(char[] s, int len) {
+ /** {@inheritDoc} */
+ @Override
+ int stem(char[] s, int len) {
len = removeCase(s, len);
return normalize(s, len);
}
diff --git a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/SpanishLightStemmer.java b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/SpanishLightStemmer.java
index 0645593da..bcde8199c 100644
--- a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/SpanishLightStemmer.java
+++ b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/SpanishLightStemmer.java
@@ -54,11 +54,13 @@ package opennlp.tools.stemmer.light;
import opennlp.tools.commons.ThreadSafe;
import opennlp.tools.stemmer.Stemmer;
import opennlp.tools.stemmer.StemmerFactory;
+
/**
* Light Stemmer for Spanish
*
- * <p>This stemmer implements the algorithm described in: <i>Report on CLEF-2001 Experiments</i>
- * Jacques Savoy
+ * <p>This stemmer implements the algorithm described in:
+ * <a href="https://doi.org/10.1007/3-540-45691-0_3"><i>Report on CLEF-2001 Experiments</i></a>
+ * by Jacques Savoy (CLEF 2001, LNCS 2406).
*
* <p>Adapted from the identically named algorithm in Apache Lucene's analysis-common module.
* Instances are stateless and safe for concurrent use by multiple threads; each instance is also
@@ -66,30 +68,17 @@ import opennlp.tools.stemmer.StemmerFactory;
* case-folding normalization step; the stemmer does not fold case itself.</p>
*/
@ThreadSafe
-public final class SpanishLightStemmer implements Stemmer, StemmerFactory {
- /** {@inheritDoc} */
- @Override
- public CharSequence stem(CharSequence word) {
- if (word == null) {
- throw new IllegalArgumentException("The word must not be null.");
- }
- final int length = word.length();
- final char[] buffer = new char[length];
- for (int i = 0; i < length; i++) {
- buffer[i] = word.charAt(i);
- }
- final int stemmedLength = stem(buffer, length);
- return new String(buffer, 0, stemmedLength);
- }
-
+public final class SpanishLightStemmer extends AbstractCharArrayStemmer
+ implements StemmerFactory {
/** {@inheritDoc} */
@Override
public Stemmer newStemmer() {
return this;
}
-
- private int stem(char[] s, int len) {
+ /** {@inheritDoc} */
+ @Override
+ int stem(char[] s, int len) {
if (len < 5) return len;
for (int i = 0; i < len; i++)
diff --git a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/SpanishMinimalStemmer.java b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/SpanishMinimalStemmer.java
index b14ad4996..0a4c94a5e 100644
--- a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/SpanishMinimalStemmer.java
+++ b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/SpanishMinimalStemmer.java
@@ -20,39 +20,29 @@ package opennlp.tools.stemmer.light;
import opennlp.tools.commons.ThreadSafe;
import opennlp.tools.stemmer.Stemmer;
import opennlp.tools.stemmer.StemmerFactory;
+
/**
* Minimal plural stemmer for Spanish.
*
- * <p>This stemmer implements the "plurals" stemmer for spanish language.
+ * <p>This stemmer implements the "plurals" stemmer for the Spanish language.
*
- * @deprecated Use {@link SpanishPluralStemmer} instead.
+ * <p>Adapted from the identically named algorithm in Apache Lucene's analysis-common module.
+ * Instances are stateless and safe for concurrent use by multiple threads; each instance is also
+ * its own {@link StemmerFactory}. Input is expected to be lowercase, as produced by a
+ * case-folding normalization step; the stemmer does not fold case itself.</p>
*/
-@Deprecated
@ThreadSafe
-public final class SpanishMinimalStemmer implements Stemmer, StemmerFactory {
- /** {@inheritDoc} */
- @Override
- public CharSequence stem(CharSequence word) {
- if (word == null) {
- throw new IllegalArgumentException("The word must not be null.");
- }
- final int length = word.length();
- final char[] buffer = new char[length];
- for (int i = 0; i < length; i++) {
- buffer[i] = word.charAt(i);
- }
- final int stemmedLength = stem(buffer, length);
- return new String(buffer, 0, stemmedLength);
- }
-
+public final class SpanishMinimalStemmer extends AbstractCharArrayStemmer
+ implements StemmerFactory {
/** {@inheritDoc} */
@Override
public Stemmer newStemmer() {
return this;
}
-
- private int stem(char[] s, int len) {
+ /** {@inheritDoc} */
+ @Override
+ int stem(char[] s, int len) {
if (len < 4 || s[len - 1] != 's') return len;
for (int i = 0; i < len; i++)
diff --git a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/StemmerUtil.java b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/StemmerUtil.java
index 4c0b3f804..8745edc27 100644
--- a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/StemmerUtil.java
+++ b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/StemmerUtil.java
@@ -26,27 +26,6 @@ final class StemmerUtil {
private StemmerUtil() {
}
- /**
- * Checks whether the buffer starts with a prefix.
- *
- * @param s The input buffer.
- * @param len The filled length of the buffer.
- * @param prefix The prefix to test.
- * @return {@code true} if the buffer starts with {@code prefix}.
- */
- static boolean startsWith(char[] s, int len, String prefix) {
- final int prefixLen = prefix.length();
- if (prefixLen > len) {
- return false;
- }
- for (int i = 0; i < prefixLen; i++) {
- if (s[i] != prefix.charAt(i)) {
- return false;
- }
- }
- return true;
- }
-
/**
* Checks whether the buffer ends with a suffix.
*
@@ -68,27 +47,6 @@ final class StemmerUtil {
return true;
}
- /**
- * Checks whether the buffer ends with a suffix.
- *
- * @param s The input buffer.
- * @param len The filled length of the buffer.
- * @param suffix The suffix to test.
- * @return {@code true} if the buffer ends with {@code suffix}.
- */
- static boolean endsWith(char[] s, int len, char[] suffix) {
- final int suffixLen = suffix.length;
- if (suffixLen > len) {
- return false;
- }
- for (int i = suffixLen - 1; i >= 0; i--) {
- if (s[len - (suffixLen - i)] != suffix[i]) {
- return false;
- }
- }
- return true;
- }
-
/**
* Deletes one character in place.
*
@@ -105,19 +63,24 @@ final class StemmerUtil {
}
/**
- * Deletes {@code nChars} characters in place.
+ * Checks whether a character is one of the unaccented vowel letters {@code a}, {@code e},
+ * {@code i}, {@code o}, {@code u} or {@code y}. This is the vowel test shared by the Finnish
+ * and Hungarian light stemming algorithms.
*
- * @param s The input buffer.
- * @param pos The position of the first character to delete.
- * @param len The filled length of the buffer.
- * @param nChars The number of characters to delete; {@code pos + nChars} must not exceed
- * {@code len}.
- * @return The filled length after the deletion.
+ * @param ch The character to test.
+ * @return {@code true} if {@code ch} is one of the six vowel letters.
*/
- static int deleteN(char[] s, int pos, int len, int nChars) {
- if (pos + nChars < len) {
- System.arraycopy(s, pos + nChars, s, pos, len - pos - nChars);
+ static boolean isVowel(char ch) {
+ switch (ch) {
+ case 'a':
+ case 'e':
+ case 'i':
+ case 'o':
+ case 'u':
+ case 'y':
+ return true;
+ default:
+ return false;
}
- return len - nChars;
}
}
diff --git a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/SwedishLightStemmer.java b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/SwedishLightStemmer.java
index 8f835c1a2..eda81fe45 100644
--- a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/SwedishLightStemmer.java
+++ b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/SwedishLightStemmer.java
@@ -56,11 +56,15 @@ import opennlp.tools.stemmer.Stemmer;
import opennlp.tools.stemmer.StemmerFactory;
import static opennlp.tools.stemmer.light.StemmerUtil.endsWith;
+
/**
* Light Stemmer for Swedish.
*
- * <p>This stemmer implements the algorithm described in: <i>Report on CLEF-2003 Monolingual
- * Tracks</i> Jacques Savoy
+ * <p>This stemmer implements the algorithm described in:
+ * <a href="https://ceur-ws.org/Vol-1169/CLEF2003wn-adhoc-Savoy2003b.pdf"><i>Report on CLEF-2003
+ * Monolingual Tracks</i></a> by Jacques Savoy (CLEF 2003 working notes). The inline suffix
+ * literals mirror the suffix tables of the cited paper and are kept inline for fidelity to the
+ * published algorithm.
*
* <p>Adapted from the identically named algorithm in Apache Lucene's analysis-common module.
* Instances are stateless and safe for concurrent use by multiple threads; each instance is also
@@ -68,29 +72,17 @@ import static opennlp.tools.stemmer.light.StemmerUtil.endsWith;
* case-folding normalization step; the stemmer does not fold case itself.</p>
*/
@ThreadSafe
-public final class SwedishLightStemmer implements Stemmer, StemmerFactory {
- /** {@inheritDoc} */
- @Override
- public CharSequence stem(CharSequence word) {
- if (word == null) {
- throw new IllegalArgumentException("The word must not be null.");
- }
- final int length = word.length();
- final char[] buffer = new char[length];
- for (int i = 0; i < length; i++) {
- buffer[i] = word.charAt(i);
- }
- final int stemmedLength = stem(buffer, length);
- return new String(buffer, 0, stemmedLength);
- }
-
+public final class SwedishLightStemmer extends AbstractCharArrayStemmer
+ implements StemmerFactory {
/** {@inheritDoc} */
@Override
public Stemmer newStemmer() {
return this;
}
- private int stem(char[] s, int len) {
+ /** {@inheritDoc} */
+ @Override
+ int stem(char[] s, int len) {
if (len > 4 && s[len - 1] == 's') len--;
if (len > 7 && (endsWith(s, len, "elser") || endsWith(s, len, "heten"))) return len - 5;
diff --git a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/SwedishMinimalStemmer.java b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/SwedishMinimalStemmer.java
index 1eb2f6454..f2b07ceee 100644
--- a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/SwedishMinimalStemmer.java
+++ b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/stemmer/light/SwedishMinimalStemmer.java
@@ -55,13 +55,12 @@ import opennlp.tools.stemmer.Stemmer;
import opennlp.tools.stemmer.StemmerFactory;
import static opennlp.tools.stemmer.light.StemmerUtil.endsWith;
+
/**
- * Minimal Stemmer for Swedish. The algorithm is an adapted version of the SwedishLightStemmer, but
- * only stripping the most common plural suffixes for nouns: -ar/arne/arna/aren, -at, -er/erna, -et,
- * -or/orna, -en. We do not strip -an or -ans suffixes, since that would require a large dictionary
- * of exceptions.
- *
- * @since 9.0.0
+ * Minimal Stemmer for Swedish. The algorithm is an adapted version of the
+ * {@link SwedishLightStemmer}, but only stripping the most common plural suffixes for nouns:
+ * -ar/arne/arna/aren, -at, -er/erna, -et, -or/orna, -en. We do not strip -an or -ans suffixes,
+ * since that would require a large dictionary of exceptions.
*
* <p>Adapted from the identically named algorithm in Apache Lucene's analysis-common module.
* Instances are stateless and safe for concurrent use by multiple threads; each instance is also
@@ -69,29 +68,17 @@ import static opennlp.tools.stemmer.light.StemmerUtil.endsWith;
* case-folding normalization step; the stemmer does not fold case itself.</p>
*/
@ThreadSafe
-public final class SwedishMinimalStemmer implements Stemmer, StemmerFactory {
- /** {@inheritDoc} */
- @Override
- public CharSequence stem(CharSequence word) {
- if (word == null) {
- throw new IllegalArgumentException("The word must not be null.");
- }
- final int length = word.length();
- final char[] buffer = new char[length];
- for (int i = 0; i < length; i++) {
- buffer[i] = word.charAt(i);
- }
- final int stemmedLength = stem(buffer, length);
- return new String(buffer, 0, stemmedLength);
- }
-
+public final class SwedishMinimalStemmer extends AbstractCharArrayStemmer
+ implements StemmerFactory {
/** {@inheritDoc} */
@Override
public Stemmer newStemmer() {
return this;
}
- private int stem(char[] s, int len) {
+ /** {@inheritDoc} */
+ @Override
+ int stem(char[] s, int len) {
if (len > 4 && s[len - 1] == 's') len--;
if (len > 6
diff --git a/opennlp-core/opennlp-runtime/src/test/java/opennlp/tools/stemmer/light/LightStemmerParityTest.java b/opennlp-core/opennlp-runtime/src/test/java/opennlp/tools/stemmer/light/LightStemmerParityTest.java
index 5003b8fea..24452efd8 100644
--- a/opennlp-core/opennlp-runtime/src/test/java/opennlp/tools/stemmer/light/LightStemmerParityTest.java
+++ b/opennlp-core/opennlp-runtime/src/test/java/opennlp/tools/stemmer/light/LightStemmerParityTest.java
@@ -29,6 +29,7 @@ import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import opennlp.tools.stemmer.Stemmer;
+import opennlp.tools.stemmer.StemmerFactory;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -90,9 +91,8 @@ class LightStemmerParityTest {
@ParameterizedTest(name = "{0}")
@MethodSource("fixtures")
void testStemmerIsItsOwnFactory(String fixture, Stemmer stemmer) {
- final opennlp.tools.stemmer.StemmerFactory factory =
- (opennlp.tools.stemmer.StemmerFactory) stemmer;
+ final StemmerFactory factory = (StemmerFactory) stemmer;
assertEquals(stemmer, factory.newStemmer(),
- "a stateless stemmer returns itself from newStemmer()");
+ "the stemmer returns itself from newStemmer()");
}
}