From 40816bd0a83706a5f9e51ca2f3a828446775bc54 Mon Sep 17 00:00:00 2001 From: mdrihan78 Date: Sun, 28 Jun 2026 17:09:48 +0600 Subject: [PATCH 1/6] God method in Sift4.distance() --- .../stringsimilarity/experimental/Sift4.java | 53 +++++++++++++------ 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/src/main/java/info/debatty/java/stringsimilarity/experimental/Sift4.java b/src/main/java/info/debatty/java/stringsimilarity/experimental/Sift4.java index 29a8946..85f60f7 100644 --- a/src/main/java/info/debatty/java/stringsimilarity/experimental/Sift4.java +++ b/src/main/java/info/debatty/java/stringsimilarity/experimental/Sift4.java @@ -24,6 +24,8 @@ package info.debatty.java.stringsimilarity.experimental; import info.debatty.java.stringsimilarity.interfaces.StringDistance; + +import java.util.Iterator; import java.util.LinkedList; /** @@ -62,34 +64,55 @@ public final void setMaxOffset(final int max_offset) { * @param s2 * @return */ + + // AFTER — Offset moved to private static nested class + private static final class Offset { + final int c1; + final int c2; + boolean trans; // mutable — documented intentionally + Offset(int c1, int c2, boolean trans) { + this.c1 = c1; this.c2 = c2; this.trans = trans; + } + } + + /** Validates that neither string is null, consistent with all other algorithms. */ + private static void validateInputs(String s1, String s2) { + if (s1 == null) throw new NullPointerException("s1 must not be null"); + if (s2 == null) throw new NullPointerException("s2 must not be null"); + } + + /** Resolves whether a new match at (c1, c2) is a transposition. */ + private boolean resolveTransposition( + int c1, int c2, LinkedList offsetArr, int[] trans) { + boolean isTrans = false; + Iterator it = offsetArr.iterator(); + while (it.hasNext()) { + Offset ofs = it.next(); + if (c1 <= ofs.c1 || c2 <= ofs.c2) { + isTrans = Math.abs(c2 - c1) >= Math.abs(ofs.c2 - ofs.c1); + if (isTrans) { trans[0]++; } + else if (!ofs.trans) { ofs.trans = true; trans[0]++; } + break; + } else if (c1 > ofs.c2 && c2 > ofs.c1) { it.remove(); } + } + return isTrans; + } + public final double distance(final String s1, final String s2) { /** * Used to store relation between same character in different positions * c1 and c2 in the input strings. */ - class Offset { - private final int c1; - private final int c2; - private boolean trans; - Offset(final int c1, final int c2, final boolean trans) { - this.c1 = c1; - this.c2 = c2; - this.trans = trans; - } - } + if (s1.isEmpty()) { - if (s1 == null || s1.isEmpty()) { - if (s2 == null) { - return 0; - } return s2.length(); } - if (s2 == null || s2.isEmpty()) { + if (s2.isEmpty()) { return s1.length(); } From 4e6322abea04fefa57a579821323a1ee7080185f Mon Sep 17 00:00:00 2001 From: mdrihan78 Date: Sun, 28 Jun 2026 17:28:09 +0600 Subject: [PATCH 2/6] Magic Number Array indexing in jaroWinkler.java --- .../java/stringsimilarity/JaroWinkler.java | 21 +++++++++++++------ .../stringsimilarity/experimental/Sift4.java | 3 --- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/main/java/info/debatty/java/stringsimilarity/JaroWinkler.java b/src/main/java/info/debatty/java/stringsimilarity/JaroWinkler.java index 267cbb4..8d748b5 100644 --- a/src/main/java/info/debatty/java/stringsimilarity/JaroWinkler.java +++ b/src/main/java/info/debatty/java/stringsimilarity/JaroWinkler.java @@ -22,8 +22,13 @@ public class JaroWinkler implements NormalizedStringSimilarity, NormalizedStringDistance { private static final double DEFAULT_THRESHOLD = 0.7; - private static final int THREE = 3; - private static final double JW_COEF = 0.1; + private static final int IDX_MATCHES = 0; + private static final int IDX_TRANSPOSITIONS = 1; + private static final int IDX_PREFIX = 2; + private static final int IDX_MAX_LEN = 3; + private static final double JW_COEF = 0.1; + private static final int JARO_DIVISOR = 3; + private final double threshold; /** @@ -75,16 +80,20 @@ public final double similarity(final String s1, final String s2) { } int[] mtp = matches(s1, s2); - float m = mtp[0]; + float m = mtp[IDX_MATCHES]; if (m == 0) { return 0f; } - double j = ((m / s1.length() + m / s2.length() + (m - mtp[1]) / m)) - / THREE; + double j = ( m / s1.length() + + m / s2.length() + + (m - mtp[IDX_TRANSPOSITIONS]) / m + ) / JARO_DIVISOR; + double jw = j; if (j > getThreshold()) { - jw = j + Math.min(JW_COEF, 1.0 / mtp[THREE]) * mtp[2] * (1 - j); + jw = j + Math.min(JW_COEF, 1.0 / mtp[IDX_MAX_LEN]) + * mtp[IDX_PREFIX] * (1 - j); } return jw; } diff --git a/src/main/java/info/debatty/java/stringsimilarity/experimental/Sift4.java b/src/main/java/info/debatty/java/stringsimilarity/experimental/Sift4.java index 85f60f7..a8887a6 100644 --- a/src/main/java/info/debatty/java/stringsimilarity/experimental/Sift4.java +++ b/src/main/java/info/debatty/java/stringsimilarity/experimental/Sift4.java @@ -105,10 +105,7 @@ public final double distance(final String s1, final String s2) { * c1 and c2 in the input strings. */ - if (s1.isEmpty()) { - - return s2.length(); } From 00521710b140fdde2cb9517136d5cb31e72303e6 Mon Sep 17 00:00:00 2001 From: mdrihan78 Date: Sun, 28 Jun 2026 17:30:24 +0600 Subject: [PATCH 3/6] Inconsistent Null handling in Sift4.java i also added that --- .../debatty/java/stringsimilarity/experimental/Sift4.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/info/debatty/java/stringsimilarity/experimental/Sift4.java b/src/main/java/info/debatty/java/stringsimilarity/experimental/Sift4.java index a8887a6..d569c69 100644 --- a/src/main/java/info/debatty/java/stringsimilarity/experimental/Sift4.java +++ b/src/main/java/info/debatty/java/stringsimilarity/experimental/Sift4.java @@ -77,8 +77,8 @@ private static final class Offset { /** Validates that neither string is null, consistent with all other algorithms. */ private static void validateInputs(String s1, String s2) { - if (s1 == null) throw new NullPointerException("s1 must not be null"); - if (s2 == null) throw new NullPointerException("s2 must not be null"); + if (s1==null) throw new NullPointerException("s1 must not be null/NULL"); + if (s2==null) throw new NullPointerException("s2 must not be null/NULL"); // added null handling } /** Resolves whether a new match at (c1, c2) is a transposition. */ From ce7273eb1f310d9149017f9bf8f66f757c4eaf6c Mon Sep 17 00:00:00 2001 From: mdrihan78 Date: Sun, 28 Jun 2026 17:42:26 +0600 Subject: [PATCH 4/6] Duplicate Null-Check Boilerplate Across 8+ Classes --- .../debatty/java/stringsimilarity/Damerau.java | 8 +------- .../debatty/java/stringsimilarity/Jaccard.java | 8 +------- .../java/stringsimilarity/JaroWinkler.java | 8 +------- .../java/stringsimilarity/Levenshtein.java | 8 +------- .../LongestCommonSubsequence.java | 8 +------- .../java/stringsimilarity/MetricLCS.java | 8 +------- .../debatty/java/stringsimilarity/NGram.java | 8 +------- .../stringsimilarity/NormalizedLevenshtein.java | 8 +------- .../OptimalStringAlignment.java | 8 +------- .../debatty/java/stringsimilarity/QGram.java | 8 +------- .../stringsimilarity/RatcliffObershelp.java | 8 +------- .../java/stringsimilarity/SorensenDice.java | 8 +------- .../java/stringsimilarity/StringUtils.java | 17 +++++++++++++++++ .../stringsimilarity/WeightedLevenshtein.java | 8 +------- 14 files changed, 30 insertions(+), 91 deletions(-) create mode 100644 src/main/java/info/debatty/java/stringsimilarity/StringUtils.java diff --git a/src/main/java/info/debatty/java/stringsimilarity/Damerau.java b/src/main/java/info/debatty/java/stringsimilarity/Damerau.java index b6fe914..419c86f 100644 --- a/src/main/java/info/debatty/java/stringsimilarity/Damerau.java +++ b/src/main/java/info/debatty/java/stringsimilarity/Damerau.java @@ -57,13 +57,7 @@ public class Damerau implements MetricStringDistance { */ public final double distance(final String s1, final String s2) { - if (s1 == null) { - throw new NullPointerException("s1 must not be null"); - } - - if (s2 == null) { - throw new NullPointerException("s2 must not be null"); - } + StringUtils.requireNonNull(s1, s2); if (s1.equals(s2)) { return 0; diff --git a/src/main/java/info/debatty/java/stringsimilarity/Jaccard.java b/src/main/java/info/debatty/java/stringsimilarity/Jaccard.java index 84417ec..7c6a678 100644 --- a/src/main/java/info/debatty/java/stringsimilarity/Jaccard.java +++ b/src/main/java/info/debatty/java/stringsimilarity/Jaccard.java @@ -76,13 +76,7 @@ public Jaccard() { * @throws NullPointerException if s1 or s2 is null. */ public final double similarity(final String s1, final String s2) { - if (s1 == null) { - throw new NullPointerException("s1 must not be null"); - } - - if (s2 == null) { - throw new NullPointerException("s2 must not be null"); - } + StringUtils.requireNonNull(s1, s2); if (s1.equals(s2)) { return 1; diff --git a/src/main/java/info/debatty/java/stringsimilarity/JaroWinkler.java b/src/main/java/info/debatty/java/stringsimilarity/JaroWinkler.java index 8d748b5..726b276 100644 --- a/src/main/java/info/debatty/java/stringsimilarity/JaroWinkler.java +++ b/src/main/java/info/debatty/java/stringsimilarity/JaroWinkler.java @@ -67,13 +67,7 @@ public final double getThreshold() { * @throws NullPointerException if s1 or s2 is null. */ public final double similarity(final String s1, final String s2) { - if (s1 == null) { - throw new NullPointerException("s1 must not be null"); - } - - if (s2 == null) { - throw new NullPointerException("s2 must not be null"); - } + StringUtils.requireNonNull(s1, s2); if (s1.equals(s2)) { return 1; diff --git a/src/main/java/info/debatty/java/stringsimilarity/Levenshtein.java b/src/main/java/info/debatty/java/stringsimilarity/Levenshtein.java index d2ba4fb..ad0e9f5 100644 --- a/src/main/java/info/debatty/java/stringsimilarity/Levenshtein.java +++ b/src/main/java/info/debatty/java/stringsimilarity/Levenshtein.java @@ -52,13 +52,7 @@ public final double distance(final String s1, final String s2) { */ public final double distance(final String s1, final String s2, final int limit) { - if (s1 == null) { - throw new NullPointerException("s1 must not be null"); - } - - if (s2 == null) { - throw new NullPointerException("s2 must not be null"); - } + StringUtils.requireNonNull(s1, s2); if (s1.equals(s2)) { return 0; diff --git a/src/main/java/info/debatty/java/stringsimilarity/LongestCommonSubsequence.java b/src/main/java/info/debatty/java/stringsimilarity/LongestCommonSubsequence.java index e8d091a..36452a4 100644 --- a/src/main/java/info/debatty/java/stringsimilarity/LongestCommonSubsequence.java +++ b/src/main/java/info/debatty/java/stringsimilarity/LongestCommonSubsequence.java @@ -37,13 +37,7 @@ public class LongestCommonSubsequence implements StringDistance { * @throws NullPointerException if s1 or s2 is null. */ public final double distance(final String s1, final String s2) { - if (s1 == null) { - throw new NullPointerException("s1 must not be null"); - } - - if (s2 == null) { - throw new NullPointerException("s2 must not be null"); - } + StringUtils.requireNonNull(s1, s2); if (s1.equals(s2)) { return 0; diff --git a/src/main/java/info/debatty/java/stringsimilarity/MetricLCS.java b/src/main/java/info/debatty/java/stringsimilarity/MetricLCS.java index ab4cc61..f503bb4 100644 --- a/src/main/java/info/debatty/java/stringsimilarity/MetricLCS.java +++ b/src/main/java/info/debatty/java/stringsimilarity/MetricLCS.java @@ -50,13 +50,7 @@ public class MetricLCS * @throws NullPointerException if s1 or s2 is null. */ public final double distance(final String s1, final String s2) { - if (s1 == null) { - throw new NullPointerException("s1 must not be null"); - } - - if (s2 == null) { - throw new NullPointerException("s2 must not be null"); - } + StringUtils.requireNonNull(s1, s2); if (s1.equals(s2)) { return 0; diff --git a/src/main/java/info/debatty/java/stringsimilarity/NGram.java b/src/main/java/info/debatty/java/stringsimilarity/NGram.java index c2a5f2d..8562bf6 100644 --- a/src/main/java/info/debatty/java/stringsimilarity/NGram.java +++ b/src/main/java/info/debatty/java/stringsimilarity/NGram.java @@ -43,13 +43,7 @@ public NGram() { * @throws NullPointerException if s0 or s1 is null. */ public final double distance(final String s0, final String s1) { - if (s0 == null) { - throw new NullPointerException("s0 must not be null"); - } - - if (s1 == null) { - throw new NullPointerException("s1 must not be null"); - } + StringUtils.requireNonNull(s0, s1); if (s0.equals(s1)) { return 0; diff --git a/src/main/java/info/debatty/java/stringsimilarity/NormalizedLevenshtein.java b/src/main/java/info/debatty/java/stringsimilarity/NormalizedLevenshtein.java index 49eaa21..a020fd2 100644 --- a/src/main/java/info/debatty/java/stringsimilarity/NormalizedLevenshtein.java +++ b/src/main/java/info/debatty/java/stringsimilarity/NormalizedLevenshtein.java @@ -50,13 +50,7 @@ public class NormalizedLevenshtein implements */ public final double distance(final String s1, final String s2) { - if (s1 == null) { - throw new NullPointerException("s1 must not be null"); - } - - if (s2 == null) { - throw new NullPointerException("s2 must not be null"); - } + StringUtils.requireNonNull(s1, s2); if (s1.equals(s2)) { return 0; diff --git a/src/main/java/info/debatty/java/stringsimilarity/OptimalStringAlignment.java b/src/main/java/info/debatty/java/stringsimilarity/OptimalStringAlignment.java index f7fbba5..32d2c0a 100644 --- a/src/main/java/info/debatty/java/stringsimilarity/OptimalStringAlignment.java +++ b/src/main/java/info/debatty/java/stringsimilarity/OptimalStringAlignment.java @@ -52,13 +52,7 @@ public final class OptimalStringAlignment implements StringDistance { * @throws NullPointerException if s1 or s2 is null. */ public double distance(final String s1, final String s2) { - if (s1 == null) { - throw new NullPointerException("s1 must not be null"); - } - - if (s2 == null) { - throw new NullPointerException("s2 must not be null"); - } + StringUtils.requireNonNull(s1, s2); if (s1.equals(s2)) { return 0; diff --git a/src/main/java/info/debatty/java/stringsimilarity/QGram.java b/src/main/java/info/debatty/java/stringsimilarity/QGram.java index ae6f9fc..41c9d13 100644 --- a/src/main/java/info/debatty/java/stringsimilarity/QGram.java +++ b/src/main/java/info/debatty/java/stringsimilarity/QGram.java @@ -60,13 +60,7 @@ public QGram() { * @throws NullPointerException if s1 or s2 is null. */ public final double distance(final String s1, final String s2) { - if (s1 == null) { - throw new NullPointerException("s1 must not be null"); - } - - if (s2 == null) { - throw new NullPointerException("s2 must not be null"); - } + StringUtils.requireNonNull(s1, s2); if (s1.equals(s2)) { return 0; diff --git a/src/main/java/info/debatty/java/stringsimilarity/RatcliffObershelp.java b/src/main/java/info/debatty/java/stringsimilarity/RatcliffObershelp.java index 22e4ea3..c015676 100644 --- a/src/main/java/info/debatty/java/stringsimilarity/RatcliffObershelp.java +++ b/src/main/java/info/debatty/java/stringsimilarity/RatcliffObershelp.java @@ -57,13 +57,7 @@ public class RatcliffObershelp implements */ @Override public final double similarity(final String s1, final String s2) { - if (s1 == null) { - throw new NullPointerException("s1 must not be null"); - } - - if (s2 == null) { - throw new NullPointerException("s2 must not be null"); - } + StringUtils.requireNonNull(s1, s2); if (s1.equals(s2)) { return 1.0d; diff --git a/src/main/java/info/debatty/java/stringsimilarity/SorensenDice.java b/src/main/java/info/debatty/java/stringsimilarity/SorensenDice.java index dec376b..c7710f9 100644 --- a/src/main/java/info/debatty/java/stringsimilarity/SorensenDice.java +++ b/src/main/java/info/debatty/java/stringsimilarity/SorensenDice.java @@ -78,13 +78,7 @@ public SorensenDice() { * @throws NullPointerException if s1 or s2 is null. */ public final double similarity(final String s1, final String s2) { - if (s1 == null) { - throw new NullPointerException("s1 must not be null"); - } - - if (s2 == null) { - throw new NullPointerException("s2 must not be null"); - } + StringUtils.requireNonNull(s1, s2); if (s1.equals(s2)) { return 1; diff --git a/src/main/java/info/debatty/java/stringsimilarity/StringUtils.java b/src/main/java/info/debatty/java/stringsimilarity/StringUtils.java new file mode 100644 index 0000000..bee31e8 --- /dev/null +++ b/src/main/java/info/debatty/java/stringsimilarity/StringUtils.java @@ -0,0 +1,17 @@ +package info.debatty.java.stringsimilarity; + +/** + * Shared validation helpers for all similarity/distance methods. + */ + + +public class StringUtils { + private StringUtils() {} // utility class — no instantiation + + + static void requireNonNull(String s1, String s2) { + if (s1 == null) throw new NullPointerException("s1 must not be null"); + if (s2 == null) throw new NullPointerException("s2 must not be null"); + } + +} diff --git a/src/main/java/info/debatty/java/stringsimilarity/WeightedLevenshtein.java b/src/main/java/info/debatty/java/stringsimilarity/WeightedLevenshtein.java index 69413e2..6e018b1 100644 --- a/src/main/java/info/debatty/java/stringsimilarity/WeightedLevenshtein.java +++ b/src/main/java/info/debatty/java/stringsimilarity/WeightedLevenshtein.java @@ -80,13 +80,7 @@ public final double distance(final String s1, final String s2) { */ public final double distance(final String s1, final String s2, final double limit) { - if (s1 == null) { - throw new NullPointerException("s1 must not be null"); - } - - if (s2 == null) { - throw new NullPointerException("s2 must not be null"); - } + StringUtils.requireNonNull(s1, s2); if (s1.equals(s2)) { return 0; From f84c6a0ea446c28ad4b17ade1568a27f83aa269d Mon Sep 17 00:00:00 2001 From: mdrihan78 Date: Sun, 28 Jun 2026 17:52:36 +0600 Subject: [PATCH 5/6] Commented-Out Dead Code in Levenshtein.java --- .../java/info/debatty/java/stringsimilarity/Levenshtein.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/info/debatty/java/stringsimilarity/Levenshtein.java b/src/main/java/info/debatty/java/stringsimilarity/Levenshtein.java index ad0e9f5..9f24f1b 100644 --- a/src/main/java/info/debatty/java/stringsimilarity/Levenshtein.java +++ b/src/main/java/info/debatty/java/stringsimilarity/Levenshtein.java @@ -105,8 +105,7 @@ public final double distance(final String s1, final String s2, return limit; } - // copy v1 (current row) to v0 (previous row) for next iteration - //System.arraycopy(v1, 0, v0, 0, v0.length); + // Flip references to current and previous row vtemp = v0; From 247431a99684ec6ade4f638ddfaf0fb2c1b6df85 Mon Sep 17 00:00:00 2001 From: mdrihan78 Date: Sun, 28 Jun 2026 18:00:56 +0600 Subject: [PATCH 6/6] Package-Private Constructor in ShingleBased --- .../java/info/debatty/java/stringsimilarity/Levenshtein.java | 2 +- .../java/info/debatty/java/stringsimilarity/ShingleBased.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/info/debatty/java/stringsimilarity/Levenshtein.java b/src/main/java/info/debatty/java/stringsimilarity/Levenshtein.java index 9f24f1b..25eb791 100644 --- a/src/main/java/info/debatty/java/stringsimilarity/Levenshtein.java +++ b/src/main/java/info/debatty/java/stringsimilarity/Levenshtein.java @@ -105,7 +105,7 @@ public final double distance(final String s1, final String s2, return limit; } - + // Flip references to current and previous row vtemp = v0; diff --git a/src/main/java/info/debatty/java/stringsimilarity/ShingleBased.java b/src/main/java/info/debatty/java/stringsimilarity/ShingleBased.java index 24017e2..2d103bc 100644 --- a/src/main/java/info/debatty/java/stringsimilarity/ShingleBased.java +++ b/src/main/java/info/debatty/java/stringsimilarity/ShingleBased.java @@ -77,7 +77,7 @@ public ShingleBased(final int k) { /** * */ - ShingleBased() { + protected ShingleBased() { this(DEFAULT_K); }