Konsolidiere Titelzeichen-Validierung in TitleCharacterRule

Doppelte private isAllowedTitleCharacters-Methoden in AiResponseValidator
und TargetFilenameBuildingService werden durch eine kanonische
TitleCharacterRule.isAllowed()-Methode ersetzt. Beide Services delegieren
jetzt dorthin statt eigene Kopien zu pflegen.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-23 07:32:41 +02:00
parent 2e6d0b1d6d
commit d1cffe8ef9
6 changed files with 153 additions and 37 deletions
@@ -100,7 +100,7 @@ public final class AiResponseValidator {
AiErrorClassification.FUNCTIONAL);
}
if (!isAllowedTitleCharacters(title)) {
if (!TitleCharacterRule.isAllowed(title)) {
return AiValidationResult.invalid(
"Title contains disallowed characters (only letters, digits, spaces, and hyphens are permitted): '"
+ title + "'",
@@ -137,25 +137,6 @@ public final class AiResponseValidator {
return AiValidationResult.valid(proposal);
}
// -------------------------------------------------------------------------
// Helpers
// -------------------------------------------------------------------------
/**
* Returns {@code true} if every character in the title is a letter, digit, space, or hyphen.
* <p>
* Permits Unicode letters including German Umlauts (ä, ö, ü, Ä, Ö, Ü) and ß.
*/
private static boolean isAllowedTitleCharacters(String title) {
for (int i = 0; i < title.length(); i++) {
char c = title.charAt(i);
if (!Character.isLetter(c) && !Character.isDigit(c) && c != ' ' && c != '-') {
return false;
}
}
return true;
}
/**
* Returns {@code true} if the title is a known generic placeholder.
* Comparison is case-insensitive.
@@ -149,7 +149,7 @@ public final class TargetFilenameBuildingService {
}
// After cleaning, verify that only letters, digits, spaces, and hyphens remain
if (!isAllowedTitleCharacters(cleanedTitle)) {
if (!TitleCharacterRule.isAllowed(cleanedTitle)) {
return new InconsistentProposalState(
"After Windows-compatibility cleaning, title contains disallowed characters "
+ "(only letters, digits, spaces, and hyphens are permitted): '"
@@ -165,20 +165,6 @@ public final class TargetFilenameBuildingService {
// Helpers
// -------------------------------------------------------------------------
/**
* Returns {@code true} if every character in the title is a letter, a digit, space, or hyphen.
* Unicode letters (including German Umlauts and ß) are permitted.
*/
private static boolean isAllowedTitleCharacters(String title) {
for (int i = 0; i < title.length(); i++) {
char c = title.charAt(i);
if (!Character.isLetter(c) && !Character.isDigit(c) && c != ' ' && c != '-') {
return false;
}
}
return true;
}
/**
* Removes characters that are incompatible with Windows filenames.
* <p>
@@ -0,0 +1,40 @@
package de.gecheckt.pdf.umbenenner.application.service;
/**
* Canonical rule for allowed title characters.
* <p>
* A title may contain only Unicode letters (including German Umlauts and ß),
* decimal digits, ASCII spaces, and hyphens ({@code -}).
* All other characters are considered disallowed.
* <p>
* This class is the single authoritative implementation of the character-allowlist
* rule. All services that need to validate or verify title characters must delegate
* to {@link #isAllowed(String)} instead of maintaining their own copies.
*/
public final class TitleCharacterRule {
private TitleCharacterRule() {
// utility class
}
/**
* Returns {@code true} if every character in {@code title} is a letter, digit,
* ASCII space, or hyphen ({@code -}).
* <p>
* Unicode letters — including German Umlauts (ä, ö, ü, Ä, Ö, Ü) and ß — are
* permitted. An empty string is considered allowed.
*
* @param title the title to check; must not be null
* @return {@code true} if all characters pass the allowlist; {@code false} otherwise
* @throws NullPointerException if {@code title} is null
*/
public static boolean isAllowed(String title) {
for (int i = 0; i < title.length(); i++) {
char c = title.charAt(i);
if (!Character.isLetter(c) && !Character.isDigit(c) && c != ' ' && c != '-') {
return false;
}
}
return true;
}
}
@@ -22,6 +22,8 @@
* two-level persistence</li>
* <li>{@link de.gecheckt.pdf.umbenenner.application.service.AiRequestComposer}
* — Deterministic composition of AI request representations from prompt and document text</li>
* <li>{@link de.gecheckt.pdf.umbenenner.application.service.TitleCharacterRule}
* — Canonical character-allowlist rule for document titles (single authoritative implementation)</li>
* </ul>
*
* <h2>Document processing flow ({@code DocumentProcessingCoordinator})</h2>