SonarQube: fix alle BLOCKER- und CRITICAL-Issues (S3252, S2479, S1186, S1192, S2699, S5783, S3776)

- S3252: GuiStatusRefreshTimeline nutzt Animation.INDEFINITE statt Timeline.INDEFINITE
- S2479: Narrow-No-Break-Space (U+202F) in GuiTooltipTexts durch normales Leerzeichen ersetzt
- S1186: 134 leere Stub-Methoden in 18 Test- und Produktionsdateien kommentiert
- S1192: ~49 duplizierte String-Literale in ~25 Klassen als Konstanten extrahiert
- S2699: fehlende Assertions in SqliteSchemaInitializationAdapterTest und FilesystemTargetFolderAdapterTest ergaenzt
- S5783: Lambda-geprufte Ausnahme in SqliteSchemaInitializationAdapterTest in private Hilfsmethode extrahiert
- S3776: kognitive Komplexitaet in 8 Methoden durch Methodenextraktion auf unter 15 gesenkt
  (EarlyLogDirectoryInitializer, CliArgumentParser, GuiConfigurationEditorWorkspace,
   GuiHistoryTab x2, GuiBatchRunTab x2, DefaultManualFileCopyUseCase)
- Kompilierungsfehler behoben: private-Modifier in CorrectionOutcome-Interface entfernt,
  selbstreferenzielle Konstante in ModelCatalogResult korrigiert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-06 21:27:59 +02:00
parent 14da7ee789
commit b7f9184344
49 changed files with 974 additions and 511 deletions
@@ -209,6 +209,14 @@ import de.gecheckt.pdf.umbenenner.domain.model.RunId;
* </ul>
*/
public class BootstrapRunner {
private static final String CONFIG_FILE_NOT_NULL = "configFilePath must not be null";
private static final String FINGERPRINT_NOT_NULL = "fingerprint must not be null";
private static final String UNEXPECTED_ERROR_PREFIX = "Unerwarteter Fehler: ";
private static final String CONFIG_LOAD_FAILED_PREFIX = "Konfiguration konnte nicht geladen werden: ";
private static final String CONFIG_FILE_NOT_FOUND_PREFIX = "Konfigurationsdatei nicht gefunden: ";
private static final String CONFIG_NOT_RUNNABLE_PREFIX = "Die Konfiguration ist nicht lauffähig: ";
private static final Logger LOG = LogManager.getLogger(BootstrapRunner.class);
private static final Path DEFAULT_CONFIG_PATH = Paths.get("config/application.properties");
@@ -942,7 +950,7 @@ public class BootstrapRunner {
configPath.toAbsolutePath());
return new GuiStartupContext(
GuiConfigurationEditorStateFactory.createBlankStartState(),
Optional.of("Konfigurationsdatei nicht gefunden: " + configPath.toAbsolutePath()
Optional.of(CONFIG_FILE_NOT_FOUND_PREFIX + configPath.toAbsolutePath()
+ "\nDie GUI startet ohne Konfigurationsdatei."),
loader,
writer,
@@ -995,7 +1003,7 @@ public class BootstrapRunner {
e.getMessage(), e);
return new GuiStartupContext(
GuiConfigurationEditorStateFactory.createBlankStartState(),
Optional.of("Konfiguration konnte nicht geladen werden: " + e.getMessage()),
Optional.of(CONFIG_LOAD_FAILED_PREFIX + e.getMessage()),
loader,
writer,
modelCatalogPort,
@@ -1074,7 +1082,7 @@ public class BootstrapRunner {
} catch (RuntimeException e) {
LOG.error("Scheduler-Tick: Unerwarteter Fehler: {}", e.getMessage(), e);
return new BatchRunTriggerResult.Failed(
"Unerwarteter Fehler: " + e.getMessage(),
UNEXPECTED_ERROR_PREFIX + e.getMessage(),
e.getClass().getSimpleName());
}
};
@@ -1199,7 +1207,7 @@ public class BootstrapRunner {
LOG.warn("GUI-Anwendungskontext: Konfiguration konnte nicht geladen werden: {}",
e.getMessage());
guiApplicationRunContext = Optional.empty();
return Optional.of("Konfiguration konnte nicht geladen werden: " + e.getMessage());
return Optional.of(CONFIG_LOAD_FAILED_PREFIX + e.getMessage());
} catch (InvalidStartConfigurationException e) {
LOG.warn("GUI-Anwendungskontext: Konfiguration nicht lauffähig: {}", e.getMessage());
guiApplicationRunContext = Optional.empty();
@@ -1329,7 +1337,7 @@ public class BootstrapRunner {
}
} catch (RuntimeException e) {
LOG.error("GUI-Status-Reset: Unerwarteter Fehler: {}", e.getMessage(), e);
return allFailures(fingerprints, "Unerwarteter Fehler: "
return allFailures(fingerprints, UNEXPECTED_ERROR_PREFIX
+ (e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage()));
}
}
@@ -1360,7 +1368,7 @@ public class BootstrapRunner {
Path configFilePath,
de.gecheckt.pdf.umbenenner.application.port.in.BatchRunProgressObserver progressObserver,
de.gecheckt.pdf.umbenenner.application.port.in.BatchRunCancellationToken cancellationToken) {
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
Objects.requireNonNull(progressObserver, "progressObserver must not be null");
Objects.requireNonNull(cancellationToken, "cancellationToken must not be null");
LOG.info("GUI-Verarbeitungslauf: Startanforderung für Konfiguration {}.", configFilePath);
@@ -1390,7 +1398,7 @@ public class BootstrapRunner {
LOG.error("GUI-Verarbeitungslauf: Konfiguration konnte nicht geladen werden: {}",
e.getMessage(), e);
return GuiBatchRunLaunchOutcome.rejected(
"Konfiguration konnte nicht geladen werden: " + e.getMessage());
CONFIG_LOAD_FAILED_PREFIX + e.getMessage());
} catch (InvalidStartConfigurationException e) {
LOG.error("GUI-Verarbeitungslauf: Konfiguration ist nicht lauffähig: {}", e.getMessage());
return GuiBatchRunLaunchOutcome.rejected(
@@ -1448,7 +1456,7 @@ public class BootstrapRunner {
Set<DocumentFingerprint> fingerprintFilter,
de.gecheckt.pdf.umbenenner.application.port.in.BatchRunProgressObserver progressObserver,
de.gecheckt.pdf.umbenenner.application.port.in.BatchRunCancellationToken cancellationToken) {
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
Objects.requireNonNull(fingerprintFilter, "fingerprintFilter must not be null");
Objects.requireNonNull(progressObserver, "progressObserver must not be null");
Objects.requireNonNull(cancellationToken, "cancellationToken must not be null");
@@ -1481,7 +1489,7 @@ public class BootstrapRunner {
LOG.error("GUI-Mini-Verarbeitungslauf: Konfiguration konnte nicht geladen werden: {}",
e.getMessage(), e);
return GuiBatchRunLaunchOutcome.rejected(
"Konfiguration konnte nicht geladen werden: " + e.getMessage());
CONFIG_LOAD_FAILED_PREFIX + e.getMessage());
} catch (InvalidStartConfigurationException e) {
LOG.error("GUI-Mini-Verarbeitungslauf: Konfiguration ist nicht lauffähig: {}", e.getMessage());
return GuiBatchRunLaunchOutcome.rejected(
@@ -1533,7 +1541,7 @@ public class BootstrapRunner {
ResetDocumentStatusResult resetDocumentStatusForGui(
Path configFilePath,
Set<DocumentFingerprint> fingerprints) {
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
Objects.requireNonNull(fingerprints, "fingerprints must not be null");
LOG.info("GUI-Status-Reset: {} Dokument(e) zurücksetzen, Konfiguration {}.",
fingerprints.size(), configFilePath);
@@ -1545,7 +1553,7 @@ public class BootstrapRunner {
}
if (!Files.exists(configFilePath)) {
String msg = "Konfigurationsdatei nicht gefunden: " + configFilePath;
String msg = CONFIG_FILE_NOT_FOUND_PREFIX + configFilePath;
LOG.error("GUI-Status-Reset: {}", msg);
return allFailures(fingerprints, msg);
}
@@ -1587,16 +1595,16 @@ public class BootstrapRunner {
} catch (ConfigurationLoadingException e) {
LOG.error("GUI-Status-Reset: Konfiguration konnte nicht geladen werden: {}", e.getMessage(), e);
return allFailures(fingerprints, "Konfiguration konnte nicht geladen werden: " + e.getMessage());
return allFailures(fingerprints, CONFIG_LOAD_FAILED_PREFIX + e.getMessage());
} catch (InvalidStartConfigurationException e) {
LOG.error("GUI-Status-Reset: Konfiguration ist nicht lauffähig: {}", e.getMessage());
return allFailures(fingerprints, "Die Konfiguration ist nicht lauffähig: " + e.getMessage());
return allFailures(fingerprints, CONFIG_NOT_RUNNABLE_PREFIX + e.getMessage());
} catch (DocumentPersistenceException e) {
LOG.error("GUI-Status-Reset: SQLite-Initialisierung fehlgeschlagen: {}", e.getMessage(), e);
return allFailures(fingerprints, "SQLite-Datenbank konnte nicht vorbereitet werden: " + e.getMessage());
} catch (RuntimeException e) {
LOG.error("GUI-Status-Reset: Unerwarteter Fehler: {}", e.getMessage(), e);
return allFailures(fingerprints, "Unerwarteter Fehler: "
return allFailures(fingerprints, UNEXPECTED_ERROR_PREFIX
+ (e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage()));
}
}
@@ -1684,13 +1692,13 @@ public class BootstrapRunner {
ManualFileRenameResult performGuiManualFileRename(
Path configFilePath,
ManualFileRenameRequest request) {
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
Objects.requireNonNull(request, "request must not be null");
LOG.info("GUI-Umbenennung: Anfrage für Fingerprint={}, Zielname={}.",
request.fingerprint().sha256Hex(), request.desiredBaseFileName());
if (!Files.exists(configFilePath)) {
String msg = "Konfigurationsdatei nicht gefunden: " + configFilePath;
String msg = CONFIG_FILE_NOT_FOUND_PREFIX + configFilePath;
LOG.error("GUI-Umbenennung: {}", msg);
return new de.gecheckt.pdf.umbenenner.application.port.in
.ManualFileRenameFileSystemFailure(msg);
@@ -1709,12 +1717,12 @@ public class BootstrapRunner {
e.getMessage(), e);
return new de.gecheckt.pdf.umbenenner.application.port.in
.ManualFileRenamePersistenceFailure(
"Konfiguration konnte nicht geladen werden: " + e.getMessage());
CONFIG_LOAD_FAILED_PREFIX + e.getMessage());
} catch (InvalidStartConfigurationException e) {
LOG.error("GUI-Umbenennung: Konfiguration ist nicht lauffähig: {}", e.getMessage());
return new de.gecheckt.pdf.umbenenner.application.port.in
.ManualFileRenamePersistenceFailure(
"Die Konfiguration ist nicht lauffähig: " + e.getMessage());
CONFIG_NOT_RUNNABLE_PREFIX + e.getMessage());
} catch (DocumentPersistenceException e) {
LOG.error("GUI-Umbenennung: SQLite-Initialisierung fehlgeschlagen: {}",
e.getMessage(), e);
@@ -1725,7 +1733,7 @@ public class BootstrapRunner {
LOG.error("GUI-Umbenennung: Unerwarteter Fehler: {}", e.getMessage(), e);
return new de.gecheckt.pdf.umbenenner.application.port.in
.ManualFileRenameFileSystemFailure(
"Unerwarteter Fehler: "
UNEXPECTED_ERROR_PREFIX
+ (e.getMessage() == null
? e.getClass().getSimpleName()
: e.getMessage()));
@@ -1748,13 +1756,13 @@ public class BootstrapRunner {
ManualFileCopyResult performGuiManualFileCopy(
Path configFilePath,
ManualFileCopyRequest request) {
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
Objects.requireNonNull(request, "request must not be null");
LOG.info("GUI-Dateikopie: Anfrage für Fingerprint={}, Zielname={}.",
request.fingerprint().sha256Hex(), request.desiredBaseFileName());
if (!Files.exists(configFilePath)) {
String msg = "Konfigurationsdatei nicht gefunden: " + configFilePath;
String msg = CONFIG_FILE_NOT_FOUND_PREFIX + configFilePath;
LOG.error("GUI-Dateikopie: {}", msg);
return new de.gecheckt.pdf.umbenenner.application.port.in
.ManualFileCopyFileSystemFailure(msg);
@@ -1773,12 +1781,12 @@ public class BootstrapRunner {
e.getMessage(), e);
return new de.gecheckt.pdf.umbenenner.application.port.in
.ManualFileCopyPersistenceFailure(
"Konfiguration konnte nicht geladen werden: " + e.getMessage());
CONFIG_LOAD_FAILED_PREFIX + e.getMessage());
} catch (InvalidStartConfigurationException e) {
LOG.error("GUI-Dateikopie: Konfiguration ist nicht lauffähig: {}", e.getMessage());
return new de.gecheckt.pdf.umbenenner.application.port.in
.ManualFileCopyPersistenceFailure(
"Die Konfiguration ist nicht lauffähig: " + e.getMessage());
CONFIG_NOT_RUNNABLE_PREFIX + e.getMessage());
} catch (DocumentPersistenceException e) {
LOG.error("GUI-Dateikopie: SQLite-Initialisierung fehlgeschlagen: {}",
e.getMessage(), e);
@@ -1789,7 +1797,7 @@ public class BootstrapRunner {
LOG.error("GUI-Dateikopie: Unerwarteter Fehler: {}", e.getMessage(), e);
return new de.gecheckt.pdf.umbenenner.application.port.in
.ManualFileCopyFileSystemFailure(
"Unerwarteter Fehler: "
UNEXPECTED_ERROR_PREFIX
+ (e.getMessage() == null
? e.getClass().getSimpleName()
: e.getMessage()));
@@ -1814,8 +1822,8 @@ public class BootstrapRunner {
Optional<HistoricalDocumentContext> resolveHistoricalDocumentContextForGui(
Path configFilePath,
DocumentFingerprint fingerprint) {
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
Objects.requireNonNull(fingerprint, "fingerprint must not be null");
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
Objects.requireNonNull(fingerprint, FINGERPRINT_NOT_NULL);
if (!Files.exists(configFilePath)) {
LOG.debug("Historischer Kontext: Konfigurationsdatei nicht gefunden: {}", configFilePath);
@@ -1853,7 +1861,7 @@ public class BootstrapRunner {
DefaultHistoryOverviewUseCase.HistoryOverviewResult loadHistoryOverviewForGui(
Path configFilePath,
de.gecheckt.pdf.umbenenner.application.port.out.history.HistoryQuery query) {
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
Objects.requireNonNull(query, "query must not be null");
try {
migrateConfigurationIfNeeded(configFilePath);
@@ -1883,8 +1891,8 @@ public class BootstrapRunner {
Optional<DefaultHistoryDetailsUseCase.HistoryDetailsResult> loadHistoryDetailsForGui(
Path configFilePath,
DocumentFingerprint fingerprint) {
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
Objects.requireNonNull(fingerprint, "fingerprint must not be null");
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
Objects.requireNonNull(fingerprint, FINGERPRINT_NOT_NULL);
try {
migrateConfigurationIfNeeded(configFilePath);
StartConfiguration config = loadAndValidateConfiguration(configFilePath);
@@ -1913,8 +1921,8 @@ public class BootstrapRunner {
void resetHistoryDocumentStatusForGui(
Path configFilePath,
DocumentFingerprint fingerprint) {
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
Objects.requireNonNull(fingerprint, "fingerprint must not be null");
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
Objects.requireNonNull(fingerprint, FINGERPRINT_NOT_NULL);
LOG.info("Historien-Status-Reset für Fingerprint: {}", fingerprint.sha256Hex());
try {
migrateConfigurationIfNeeded(configFilePath);
@@ -1945,8 +1953,8 @@ public class BootstrapRunner {
void deleteDocumentHistoryForGui(
Path configFilePath,
DocumentFingerprint fingerprint) {
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
Objects.requireNonNull(fingerprint, "fingerprint must not be null");
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
Objects.requireNonNull(fingerprint, FINGERPRINT_NOT_NULL);
LOG.info("Historien-Löschen für Fingerprint: {}", fingerprint.sha256Hex());
try {
migrateConfigurationIfNeeded(configFilePath);
@@ -69,6 +69,7 @@ public final class GuiConfigurationPropertiesWriter implements GuiConfigurationF
* Creates a new properties writer.
*/
public GuiConfigurationPropertiesWriter() {
// intentionally empty no initialization required
}
/**
@@ -28,6 +28,9 @@ import java.util.Optional;
* This class is stateless and safe for concurrent use once instantiated.
*/
public class CliArgumentParser {
private static final String OPTION_PREFIX = "Option ";
private static final String OPTION_HEADLESS = "--headless";
private static final String OPTION_CONFIG = "--config";
@@ -82,21 +85,12 @@ public class CliArgumentParser {
return new StartupArgumentsParseResult.Invalid(
"Duplicate option: " + OPTION_CONFIG);
}
if (i + 1 >= args.length) {
return new StartupArgumentsParseResult.Invalid(
"Option " + OPTION_CONFIG + " requires a path argument but none was provided");
}
String pathToken = args[i + 1];
if (pathToken.startsWith("--")) {
return new StartupArgumentsParseResult.Invalid(
"Option " + OPTION_CONFIG + " requires a path argument, but got option: " + pathToken);
}
if (pathToken.isBlank()) {
return new StartupArgumentsParseResult.Invalid(
"Option " + OPTION_CONFIG + " requires a non-blank path argument");
StartupArgumentsParseResult validation = validateConfigPathToken(args, i);
if (validation != null) {
return validation;
}
configSeen = true;
configPath = Optional.of(pathToken);
configPath = Optional.of(args[i + 1]);
i += 2;
}
default -> {
@@ -108,4 +102,29 @@ public class CliArgumentParser {
return new StartupArgumentsParseResult.Valid(new StartupArguments(mode, configPath));
}
/**
* Validates that a path token follows the {@code --config} option at position {@code i}.
*
* @param args the argument array
* @param i the index of the {@code --config} token
* @return an {@link StartupArgumentsParseResult.Invalid} if the path token is missing or
* invalid, {@code null} if the token is acceptable
*/
private StartupArgumentsParseResult validateConfigPathToken(String[] args, int i) {
if (i + 1 >= args.length) {
return new StartupArgumentsParseResult.Invalid(
OPTION_PREFIX + OPTION_CONFIG + " requires a path argument but none was provided");
}
String pathToken = args[i + 1];
if (pathToken.startsWith("--")) {
return new StartupArgumentsParseResult.Invalid(
OPTION_PREFIX + OPTION_CONFIG + " requires a path argument, but got option: " + pathToken);
}
if (pathToken.isBlank()) {
return new StartupArgumentsParseResult.Invalid(
OPTION_PREFIX + OPTION_CONFIG + " requires a non-blank path argument");
}
return null;
}
}
@@ -46,27 +46,35 @@ public final class EarlyLogDirectoryInitializer {
*/
public static void applyFromArgs(String[] args) {
try {
if (System.getProperty(SYSTEM_PROPERTY_KEY) != null
&& !System.getProperty(SYSTEM_PROPERTY_KEY).isBlank()) {
if (isLogPropertyAlreadySet()) {
return;
}
Path configPath = resolveConfigPath(args);
if (configPath == null || !Files.isRegularFile(configPath)) {
return;
}
Properties properties = new Properties();
try (InputStream in = Files.newInputStream(configPath)) {
properties.load(in);
}
String value = properties.getProperty(CONFIG_PROPERTY_KEY);
if (value != null && !value.isBlank()) {
System.setProperty(SYSTEM_PROPERTY_KEY, value.trim());
}
applyLogDirectoryFromConfig(configPath);
} catch (IOException | RuntimeException ignored) {
// bewusst still: Log4j2-Fallback aus log4j2.xml übernimmt ansonsten
}
}
private static boolean isLogPropertyAlreadySet() {
String val = System.getProperty(SYSTEM_PROPERTY_KEY);
return val != null && !val.isBlank();
}
private static void applyLogDirectoryFromConfig(Path configPath) throws IOException {
Properties properties = new Properties();
try (InputStream in = Files.newInputStream(configPath)) {
properties.load(in);
}
String value = properties.getProperty(CONFIG_PROPERTY_KEY);
if (value != null && !value.isBlank()) {
System.setProperty(SYSTEM_PROPERTY_KEY, value.trim());
}
}
private static Path resolveConfigPath(String[] args) {
if (args != null) {
for (int i = 0; i < args.length - 1; i++) {