Skip to content

Commit 68f04d0

Browse files
committed
tersify comments
1 parent 31c6651 commit 68f04d0

3 files changed

Lines changed: 29 additions & 94 deletions

File tree

tika-encoding-detectors/tika-encoding-detector-mojibuster/src/main/java/org/apache/tika/ml/chardetect/MojibusterEncodingDetector.java

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -174,16 +174,10 @@ public class MojibusterEncodingDetector implements EncodingDetector {
174174
*/
175175
private static final int UTF8_MAX_TOLERATED_ERRORS = 1;
176176

177-
/**
178-
* Minimum count of complete, valid multi-byte UTF-8 sequences required before
179-
* a tolerated (NOT_UTF8-but-within-error-budget) probe is promoted to a
180-
* STRUCTURAL UTF-8 candidate. Tolerance alone isn't enough evidence at any
181-
* length — 1 error in a 20-byte zip entry name is a 5% error rate, easily a
182-
* coincidentally-valid legacy-encoded string, not corrupted UTF-8. Requiring
183-
* substantial genuine multi-byte evidence (mirrors {@link
184-
* CjkDecodeValidator#MIN_HIGH_BYTES}) separates that short-probe false-positive
185-
* risk from the long-document case this tolerance mechanism exists for.
186-
*/
177+
/** Minimum valid multi-byte UTF-8 sequences before a tolerated (not clean)
178+
* probe is promoted to STRUCTURAL — else a short filename could false-
179+
* positive on a single coincidental error (mirrors {@link
180+
* CjkDecodeValidator#MIN_HIGH_BYTES}). */
187181
private static final int MIN_TOLERATED_UTF8_SEQUENCES = 30;
188182

189183
/** Windows-1252: the WHATWG-canonical default for unlabeled Western content. */
@@ -367,21 +361,15 @@ public List<EncodingResult> detect(byte[] probe, Metadata metadata) {
367361
}
368362
}
369363
LOG.trace("mojibuster utf8Check={} tolerated={}", utf8, utf8Tolerated);
370-
// Emit a structural UTF-8 candidate when the grammar is definitively clean
371-
// (LIKELY_UTF8), OR when it's tolerated AND backed by abundant genuine
372-
// multi-byte evidence (evidenceTolerated below). Bare tolerance is not
373-
// promoted: on a short probe (e.g. a zip entry name — ZipParser routes
374-
// entry-name bytes through this same detector) NB's UTF-8 result is
375-
// already kept as a STATISTICAL candidate (see NOT_UTF8 disqualifier
376-
// above), and a single tolerated error there is more likely a
377-
// coincidentally-valid legacy-encoded string than corrupted UTF-8 — regr-
378-
// ession-tested in ToleratedUtf8StructuralRegressionTest. On a long,
379-
// overwhelmingly-UTF-8 document a single stray legacy byte (e.g. a raw
380-
// 0xA9 copyright sign) must not cost the whole document its STRUCTURAL
381-
// proof: NB can come back with an empty pool for some scripts, leaving
382-
// nothing for JunkFilter to prefer over the declared charset — real-world
383-
// regression from commit 360b3d354 (2026-06-10), which dropped this
384-
// branch entirely on the assumption that an NB fallback always exists.
364+
// Promote on LIKELY_UTF8, or on tolerated errors backed by abundant
365+
// evidence (evidenceTolerated). Bare tolerance isn't enough: on a short
366+
// probe (e.g. a zip entry name, routed here by ZipParser) NB already
367+
// covers a real UTF-8 case as STATISTICAL, so a lone tolerated error is
368+
// more likely a coincidentally-valid legacy string. But on a long,
369+
// genuinely-UTF-8 document NB can return an empty pool for some scripts
370+
// (TIKA-4810) — without this, one stray legacy byte costs the whole
371+
// document its STRUCTURAL proof and JunkFilter has nothing to prefer
372+
// over the declared charset.
385373
boolean evidenceTolerated = utf8Tolerated
386374
&& StructuralEncodingRules.countUtf8Sequences(probe) >= MIN_TOLERATED_UTF8_SEQUENCES;
387375
if (utf8 == StructuralEncodingRules.Utf8Result.LIKELY_UTF8 || evidenceTolerated) {

tika-encoding-detectors/tika-encoding-detector-mojibuster/src/main/java/org/apache/tika/ml/chardetect/StructuralEncodingRules.java

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -894,17 +894,9 @@ public static int countUtf8Errors(byte[] bytes, int offset, int length) {
894894
return errors;
895895
}
896896

897-
/**
898-
* Counts complete, valid multi-byte UTF-8 sequences in the sample —
899-
* companion to {@link #countUtf8Errors}, same walk, opposite tally. Used
900-
* to gauge how much genuine UTF-8 evidence a probe carries independent of
901-
* its error count: a probe with one tolerated error and hundreds of valid
902-
* sequences is overwhelmingly UTF-8; a probe with one tolerated error and
903-
* two or three valid sequences (a short filename, say) is not distinguishable
904-
* from a coincidentally-valid legacy-encoded string.
905-
*
906-
* @return number of complete, well-formed multi-byte UTF-8 sequences
907-
*/
897+
/** Counts complete, valid multi-byte UTF-8 sequences — companion to
898+
* {@link #countUtf8Errors}, same walk, opposite tally. Gauges how much
899+
* genuine UTF-8 evidence a probe carries independent of its error count. */
908900
public static int countUtf8Sequences(byte[] bytes) {
909901
return countUtf8Sequences(bytes, 0, bytes.length);
910902
}

tika-encoding-detectors/tika-encoding-detector-mojibuster/src/test/java/org/apache/tika/ml/chardetect/ToleratedUtf8StructuralRegressionTest.java

Lines changed: 13 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -30,32 +30,13 @@
3030
import org.apache.tika.detect.EncodingResult;
3131

3232
/**
33-
* Regression test for a real-world failure: a genuinely UTF-8 HTML page whose
34-
* only single-byte "legacy" artifact (a stray {@code &copy;} written as raw
35-
* {@code 0xA9} rather than an entity) sits before the bulk of the document's
36-
* real multi-byte content. {@link StructuralEncodingRules#checkUtf8} correctly
37-
* reports {@code NOT_UTF8} for the whole probe (one malformed lead byte), and
38-
* the tolerance mechanism in {@link MojibusterEncodingDetector} is supposed to
39-
* recognize this as "essentially UTF-8" when there's abundant genuine
40-
* multi-byte evidence.
41-
*
42-
* <p>Commit 360b3d354 ("merge conflict and flaky test", 2026-06-10) dropped the
43-
* {@code || utf8Tolerated} branch that used to promote this case to a
44-
* STRUCTURAL UTF-8 candidate, on the assumption that the NB statistical layer
45-
* would independently propose UTF-8 as a fallback. That assumption doesn't
46-
* hold for every script/corpus (verified against a real Bengali-language news
47-
* page): NB's own candidate pool can come back completely empty, leaving
48-
* Mojibuster with nothing but the {@code windows-1252} "give up" default —
49-
* silent, complete mojibake on an otherwise-clean UTF-8 document.</p>
50-
*
51-
* <p>The companion {@link #shortProbeWithOneStrayByteIsNotPromoted()} test
52-
* guards the reason that branch was narrowed in the first place: zip entry
53-
* names are typically 9-30 bytes, and {@link
54-
* org.apache.tika.parser.pkg.ZipParser} runs them through this same detector
55-
* (see {@code ZipParser#isDetectCharsetsInEntryNames}). A single coincidental
56-
* error byte in a short, genuinely-legacy-encoded filename must NOT be enough
57-
* to promote it to STRUCTURAL UTF-8 — that would re-open the false-positive
58-
* this detector is relied on to avoid for filenames.</p>
33+
* TIKA-4810: commit 360b3d354 (2026-06-10) dropped the {@code || utf8Tolerated}
34+
* branch that promoted a tolerated (near-clean) probe to STRUCTURAL UTF-8,
35+
* assuming NB's statistical layer always covers the fallback. It doesn't (a
36+
* real Bengali news page's NB pool came back empty) — but restoring the branch
37+
* unconditionally would re-open a false positive on short zip entry names
38+
* (9-30 bytes, routed through this detector by {@code ZipParser}), which is
39+
* why it was narrowed in the first place.
5940
*/
6041
public class ToleratedUtf8StructuralRegressionTest {
6142

@@ -70,10 +51,6 @@ private static MojibusterEncodingDetector newDetector() {
7051
}
7152
}
7253

73-
/**
74-
* Long document, abundant genuine multi-byte UTF-8 evidence, exactly one
75-
* tolerated error byte before it. Must still be recognized as UTF-8.
76-
*/
7754
@Test
7855
public void longDocumentWithOneStrayByteIsStillUtf8() throws IOException {
7956
byte[] probe = buildProbe(30);
@@ -87,19 +64,11 @@ public void longDocumentWithOneStrayByteIsStillUtf8() throws IOException {
8764
+ "results were: " + results);
8865
}
8966

90-
/**
91-
* Short probe (the zip-entry-name shape), exactly one error byte, only a
92-
* handful of genuine multi-byte sequences. Must NOT be promoted to
93-
* STRUCTURAL UTF-8 on the strength of tolerance alone — that's the
94-
* false-positive TIKA-4752-era filename detection depends on avoiding.
95-
*/
67+
/** Zip-entry-name-shaped probe: must not be promoted on tolerance alone. */
9668
@Test
9769
public void shortProbeWithOneStrayByteIsNotPromoted() throws IOException {
98-
// ~20 bytes: one legacy high byte + a couple of genuine multi-byte
99-
// UTF-8 chars — the shape of a real (short) zip entry name, not a
100-
// full document.
10170
ByteArrayOutputStream bo = new ByteArrayOutputStream();
102-
bo.write(0xA9); // stray legacy byte, invalid as a UTF-8 lead
71+
bo.write(0xA9); // raw © byte: invalid as a UTF-8 lead
10372
bo.writeBytes("café-Köln.txt".getBytes(StandardCharsets.UTF_8));
10473
byte[] probe = bo.toByteArray();
10574

@@ -113,13 +82,7 @@ public void shortProbeWithOneStrayByteIsNotPromoted() throws IOException {
11382
+ "results were: " + results);
11483
}
11584

116-
/**
117-
* Real embedded-file-name regression from {@code attachment_name_diffs.xlsx}
118-
* (commoncrawl3/5D/5DXWH7R4A5Q6VAWBAMBSUZM5PNEVAE63): a GBK zip entry name
119-
* ({@code 说明.txt}) must stay GB18030, not get pulled toward STRUCTURAL
120-
* UTF-8 by tolerance — the same false-positive risk as the Latin case,
121-
* CJK-flavored.
122-
*/
85+
/** Real GBK filename from attachment_name_diffs.xlsx; must stay GB18030. */
12386
@Test
12487
public void chineseGbkFilenameIsNotPromotedToUtf8() {
12588
byte[] probe = "说明.txt".getBytes(Charset.forName("GBK"));
@@ -134,12 +97,7 @@ public void chineseGbkFilenameIsNotPromotedToUtf8() {
13497
"Expected a GB18030/GBK candidate; results were: " + results);
13598
}
13699

137-
/**
138-
* Real embedded-file-name regression from {@code attachment_name_diffs.xlsx}
139-
* (bug_trackers/MOZILLA/240463-316268/MOZILLA-296795-4.zip): a windows-1252
140-
* zip entry name ({@code Sauté.txt}) must stay legacy SBCS, not get promoted
141-
* to STRUCTURAL UTF-8 by tolerance.
142-
*/
100+
/** Real windows-1252 filename from attachment_name_diffs.xlsx. */
143101
@Test
144102
public void sauteFilenameIsNotPromotedToUtf8() {
145103
byte[] probe = "Sauté.txt".getBytes(Charset.forName("windows-1252"));
@@ -152,10 +110,7 @@ public void sauteFilenameIsNotPromotedToUtf8() {
152110
+ "UTF-8 on a single tolerated error alone; results were: " + results);
153111
}
154112

155-
/** HTML wrapper + {@code repeatCount} copies of a real Bengali sentence,
156-
* with a single raw {@code 0xA9} (not a UTF-8 encoded {@code ©}) planted
157-
* in a meta tag before the real content — matches the real-world
158-
* failure exactly (declared windows-1252, genuinely UTF-8 body). */
113+
/** Declared-windows-1252 HTML page, genuinely UTF-8, one stray raw © byte. */
159114
private static byte[] buildProbe(int repeatCount) throws IOException {
160115
StringBuilder body = new StringBuilder();
161116
for (int i = 0; i < repeatCount; i++) {
@@ -166,7 +121,7 @@ private static byte[] buildProbe(int repeatCount) throws IOException {
166121
+ "content=\"text/html; charset=windows-1252\">")
167122
.getBytes(StandardCharsets.US_ASCII));
168123
bo.writeBytes("<meta name=\"copyright\" content=\"".getBytes(StandardCharsets.US_ASCII));
169-
bo.write(0xA9); // stray legacy byte, invalid as a UTF-8 lead
124+
bo.write(0xA9); // raw © byte: invalid as a UTF-8 lead
170125
bo.writeBytes(" 2013\"></head><body><title>".getBytes(StandardCharsets.US_ASCII));
171126
bo.writeBytes(body.toString().getBytes(StandardCharsets.UTF_8));
172127
bo.writeBytes("</title></body></html>".getBytes(StandardCharsets.US_ASCII));

0 commit comments

Comments
 (0)