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
@@ -124,6 +124,12 @@ import javafx.stage.Window;
* Thread via {@code Platform.runLater}.
*/
public final class GuiConfigurationEditorWorkspace {
private static final String NO_PROMPT_PATH_MSG = "Kein Prompt-Pfad konfiguriert.";
private static final String OPERATION_VALIDATE = "Validierung";
private static final String PROPERTIES_FILTER_EXT = "*.properties";
private static final String PROPERTIES_FILTER_DESC = "Properties-Dateien";
private static final Logger LOG = LogManager.getLogger(GuiConfigurationEditorWorkspace.class);
private static final String WELCOME_TEXT =
@@ -985,7 +991,7 @@ public final class GuiConfigurationEditorWorkspace {
Window owner = root.getScene() == null ? null : root.getScene().getWindow();
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Konfiguration öffnen");
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Properties-Dateien", "*.properties"));
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(PROPERTIES_FILTER_DESC, PROPERTIES_FILTER_EXT));
if (owner != null && editorState.hasLoadedFileSnapshot()) {
Path currentPath = editorState.loadedFileSnapshot().orElseThrow().filePath();
Path parent = currentPath.getParent();
@@ -1055,7 +1061,7 @@ public final class GuiConfigurationEditorWorkspace {
FileChooser fileChooser = saveFileChooserFactory.get();
fileChooser.setTitle("Konfiguration speichern");
fileChooser.getExtensionFilters().add(
new FileChooser.ExtensionFilter("Properties-Dateien", "*.properties"));
new FileChooser.ExtensionFilter(PROPERTIES_FILTER_DESC, PROPERTIES_FILTER_EXT));
// Propose the default path relative to the working directory.
Path proposedDir = DEFAULT_SAVE_PATH.getParent();
@@ -1504,7 +1510,7 @@ public final class GuiConfigurationEditorWorkspace {
FileChooser fileChooser = saveFileChooserFactory.get();
fileChooser.setTitle("Konfiguration speichern");
fileChooser.getExtensionFilters().add(
new FileChooser.ExtensionFilter("Properties-Dateien", "*.properties"));
new FileChooser.ExtensionFilter(PROPERTIES_FILTER_DESC, PROPERTIES_FILTER_EXT));
java.io.File proposedDirFile = DEFAULT_SAVE_PATH.getParent().toAbsolutePath().toFile();
if (proposedDirFile.exists()) {
fileChooser.setInitialDirectory(proposedDirFile);
@@ -1604,13 +1610,13 @@ public final class GuiConfigurationEditorWorkspace {
@Override
public de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingResult loadCurrentPrompt() {
return new de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingFailure(
"NO_PATH", "Kein Prompt-Pfad konfiguriert.");
"NO_PATH", NO_PROMPT_PATH_MSG);
}
@Override
public de.gecheckt.pdf.umbenenner.application.port.out.PromptSaveResult save(String content) {
return new de.gecheckt.pdf.umbenenner.application.port.out.PromptSaveResult.WriteFailed(
"Kein Prompt-Pfad konfiguriert.", null);
NO_PROMPT_PATH_MSG, null);
}
@Override
@@ -1619,7 +1625,7 @@ public final class GuiConfigurationEditorWorkspace {
de.gecheckt.pdf.umbenenner.application.validation.technicaltest
.CorrectionSuggestion.CreatePromptFile suggestion) {
return new de.gecheckt.pdf.umbenenner.application.validation.technicaltest
.CorrectionOutcome.NotAttempted(suggestion, "Kein Prompt-Pfad konfiguriert.");
.CorrectionOutcome.NotAttempted(suggestion, NO_PROMPT_PATH_MSG);
}
};
}
@@ -1696,26 +1702,27 @@ public final class GuiConfigurationEditorWorkspace {
// Tab-Wechsel-Schutz: Beim Wechsel weg vom Verarbeitungslauf-Tab prüfen ob
// der Dateiname-Editor ungespeicherte Änderungen hat.
// Gleiches gilt für den Prompt-Tab.
tabPane.getSelectionModel().selectedItemProperty().addListener((obs, oldTab, newTab) -> {
if (oldTab == null || newTab == null) {
return;
tabPane.getSelectionModel().selectedItemProperty().addListener(
(obs, oldTab, newTab) -> handleTabSwitch(oldTab, newTab));
}
private void handleTabSwitch(javafx.scene.control.Tab oldTab, javafx.scene.control.Tab newTab) {
if (oldTab == null || newTab == null) {
return;
}
if (oldTab == batchRunTab.tab() && batchRunTab.hasUnsavedFilenameEdits()) {
boolean shouldDiscard = batchRunTab.confirmDiscardUnsavedFilenameEdits();
if (!shouldDiscard) {
Platform.runLater(() -> tabPane.getSelectionModel().select(oldTab));
}
if (oldTab == batchRunTab.tab() && batchRunTab.hasUnsavedFilenameEdits()) {
// Selektion kurz unterdrücken um Rekursion zu vermeiden
boolean shouldDiscard = batchRunTab.confirmDiscardUnsavedFilenameEdits();
if (!shouldDiscard) {
// Zurück zum Verarbeitungslauf-Tab
Platform.runLater(() -> tabPane.getSelectionModel().select(oldTab));
}
} else if (oldTab == promptEditorTab.tab() && promptEditorTab.hasDirtyContent()) {
boolean shouldDiscard = promptEditorTab.confirmDiscardIfDirty();
if (!shouldDiscard) {
Platform.runLater(() -> tabPane.getSelectionModel().select(oldTab));
} else {
promptEditorTab.discardChanges();
}
} else if (oldTab == promptEditorTab.tab() && promptEditorTab.hasDirtyContent()) {
boolean shouldDiscard = promptEditorTab.confirmDiscardIfDirty();
if (!shouldDiscard) {
Platform.runLater(() -> tabPane.getSelectionModel().select(oldTab));
} else {
promptEditorTab.discardChanges();
}
});
}
}
private void configureActionBar() {
@@ -2610,7 +2617,7 @@ public final class GuiConfigurationEditorWorkspace {
for (EditorValidationFinding finding : report.findings()) {
GuiMessageSeverity severity = toGuiSeverity(finding.severity());
messages.add(GuiMessageEntry.of(severity, finding.message(), "Validierung"));
messages.add(GuiMessageEntry.of(severity, finding.message(), OPERATION_VALIDATE));
if (finding.hasFieldKey()) {
fieldFindings.add(new GuiFieldFinding(finding.fieldKey().orElseThrow(),
severity, finding.message()));
@@ -2619,7 +2626,7 @@ public final class GuiConfigurationEditorWorkspace {
// Replace validation-related entries; preserve model-catalog messages (from coordinator)
pendingMessages.removeIf(m -> m.source().isPresent()
&& "Validierung".equals(m.source().get()));
&& OPERATION_VALIDATE.equals(m.source().get()));
pendingMessages.addAll(messages);
pendingFieldFindings.clear();
@@ -2675,7 +2682,7 @@ public final class GuiConfigurationEditorWorkspace {
// Drop silent auto-validation entries so the central message area is not flooded
// by keystroke-level background checks; explicit action entries always accumulate.
pendingMessages.removeIf(m -> m.source().isPresent()
&& "Validierung".equals(m.source().get()));
&& OPERATION_VALIDATE.equals(m.source().get()));
// Append a timestamped confirmation plus each concrete finding as its own entry.
int findingCount = report.findings().size();
@@ -51,6 +51,10 @@ import javafx.application.Platform;
* {@code Platform.runLater}.
*/
public final class GuiModelCatalogCoordinator {
private static final String LOG_MODEL_FETCH_FMT = "GUI-Modellabruf: {} (Provider: {})";
private static final String OPERATION_MODELLABRUF = "Modellabruf";
private static final Logger LOG = LogManager.getLogger(GuiModelCatalogCoordinator.class);
@@ -203,7 +207,7 @@ public final class GuiModelCatalogCoordinator {
String previousManualValue) {
// Remove any previous message entries from an earlier retrieval so messages do not
// accumulate across repeated triggers of the same retrieval action.
pendingMessages.removeIf(msg -> "Modellabruf".equals(msg.source().orElse("")));
pendingMessages.removeIf(msg -> OPERATION_MODELLABRUF.equals(msg.source().orElse("")));
String displayName = displayNameFor(family);
@@ -213,28 +217,28 @@ public final class GuiModelCatalogCoordinator {
container.applyModelList(models, previousManualValue);
String message = "Modellliste für " + displayName + " geladen ("
+ models.size() + " " + (models.size() == 1 ? "Eintrag" : "Einträge") + ").";
pendingMessages.add(GuiMessageEntry.of(GuiMessageSeverity.INFO, message, "Modellabruf"));
LOG.info("GUI-Modellabruf: {} (Provider: {})", message, family.getIdentifier());
pendingMessages.add(GuiMessageEntry.of(GuiMessageSeverity.INFO, message, OPERATION_MODELLABRUF));
LOG.info(LOG_MODEL_FETCH_FMT, message, family.getIdentifier());
}
case ModelCatalogResult.EmptyList emptyList -> {
container.applyManualFallback(GuiModelSource.LIST_UNAVAILABLE_MANUAL_INPUT);
String message = "Provider " + displayName
+ " liefert aktuell keine Modelle. Manuelle Eingabe aktiv.";
pendingMessages.add(GuiMessageEntry.of(GuiMessageSeverity.HINT, message, "Modellabruf"));
LOG.warn("GUI-Modellabruf: {} (Provider: {})", message, family.getIdentifier());
pendingMessages.add(GuiMessageEntry.of(GuiMessageSeverity.HINT, message, OPERATION_MODELLABRUF));
LOG.warn(LOG_MODEL_FETCH_FMT, message, family.getIdentifier());
}
case ModelCatalogResult.IncompleteConfiguration incomplete -> {
container.applyManualFallback(GuiModelSource.LIST_UNAVAILABLE_MANUAL_INPUT);
String message = "Modellliste nicht abrufbar: " + incomplete.missingReason()
+ ". Manuelle Eingabe aktiv.";
pendingMessages.add(GuiMessageEntry.of(GuiMessageSeverity.WARNING, message, "Modellabruf"));
LOG.warn("GUI-Modellabruf: {} (Provider: {})", message, family.getIdentifier());
pendingMessages.add(GuiMessageEntry.of(GuiMessageSeverity.WARNING, message, OPERATION_MODELLABRUF));
LOG.warn(LOG_MODEL_FETCH_FMT, message, family.getIdentifier());
}
case ModelCatalogResult.TechnicalFailure failure -> {
container.applyManualFallback(GuiModelSource.LIST_FAILED_MANUAL_INPUT);
String message = "Modellliste nicht abrufbar (" + failure.errorCategory()
+ "). Manuelle Eingabe aktiv.";
pendingMessages.add(GuiMessageEntry.of(GuiMessageSeverity.ERROR, message, "Modellabruf"));
pendingMessages.add(GuiMessageEntry.of(GuiMessageSeverity.ERROR, message, OPERATION_MODELLABRUF));
LOG.warn("GUI-Modellabruf: {} Detail: {} (Provider: {})",
message, failure.errorDetail(), family.getIdentifier());
}
@@ -54,6 +54,9 @@ import javafx.scene.layout.VBox;
* Hintergrund-Worker-Thread ({@code gui-scheduler-control}) ausgeführt.
*/
public final class GuiSchedulerTab {
private static final String HEADER_LABEL_STYLE = "-fx-font-size: 14px; -fx-font-weight: bold; -fx-text-fill: #7f8c8d;";
private static final Logger LOG = LogManager.getLogger(GuiSchedulerTab.class);
@@ -177,7 +180,7 @@ public final class GuiSchedulerTab {
}
private VBox buildControlArea() {
statusLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold; -fx-text-fill: #7f8c8d;");
statusLabel.setStyle(HEADER_LABEL_STYLE);
stopButton.setDisable(true);
HBox buttonBox = new HBox(10, startButton, stopButton);
@@ -248,7 +251,7 @@ public final class GuiSchedulerTab {
switch (status.state()) {
case STOPPED -> {
statusLabel.setText("○ Gestoppt");
statusLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold; -fx-text-fill: #7f8c8d;");
statusLabel.setStyle(HEADER_LABEL_STYLE);
}
case STARTING -> {
statusLabel.setText("⟳ Wird gestartet…");
@@ -264,7 +267,7 @@ public final class GuiSchedulerTab {
}
case STOPPING_BATCH_ACTIVE -> {
statusLabel.setText("○ Gestoppt aktueller Lauf läuft noch");
statusLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold; -fx-text-fill: #7f8c8d;");
statusLabel.setStyle(HEADER_LABEL_STYLE);
}
}
}
@@ -101,6 +101,9 @@ public record GuiStartupContext(
Optional<String> applicationContextError,
Optional<SchedulerControlUseCase> schedulerControlUseCase,
Optional<ConfigurationFileLockPort> configurationFileLockPort) {
private static final String NO_PROMPT_PORT_MSG = "Kein Prompt-Editor-Port in diesem Startkontext verfügbar.";
private static final String NO_PORT_MSG = "Kein Port in diesem Startkontext.";
/**
* Creates a fully wired startup context.
@@ -524,21 +527,21 @@ public record GuiStartupContext(
createDirectory(de.gecheckt.pdf.umbenenner.application.validation.technicaltest
.CorrectionSuggestion.CreateDirectory suggestion) {
return new de.gecheckt.pdf.umbenenner.application.validation.technicaltest
.CorrectionOutcome.NotAttempted(suggestion, "Kein Port in diesem Startkontext.");
.CorrectionOutcome.NotAttempted(suggestion, NO_PORT_MSG);
}
@Override
public de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionOutcome
createPromptFile(de.gecheckt.pdf.umbenenner.application.validation.technicaltest
.CorrectionSuggestion.CreatePromptFile suggestion) {
return new de.gecheckt.pdf.umbenenner.application.validation.technicaltest
.CorrectionOutcome.NotAttempted(suggestion, "Kein Port in diesem Startkontext.");
.CorrectionOutcome.NotAttempted(suggestion, NO_PORT_MSG);
}
@Override
public de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionOutcome
prepareSqlitePath(de.gecheckt.pdf.umbenenner.application.validation.technicaltest
.CorrectionSuggestion.PrepareSqlitePath suggestion) {
return new de.gecheckt.pdf.umbenenner.application.validation.technicaltest
.CorrectionOutcome.NotAttempted(suggestion, "Kein Port in diesem Startkontext.");
.CorrectionOutcome.NotAttempted(suggestion, NO_PORT_MSG);
}
};
CorrectionExecutionService noOpCorrectionService = new CorrectionExecutionService(noOpResourceCreationPort);
@@ -599,13 +602,13 @@ public record GuiStartupContext(
@Override
public de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingResult loadCurrentPrompt() {
return new de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingFailure(
"NO_OP", "Kein Prompt-Editor-Port in diesem Startkontext verfügbar.");
"NO_OP", NO_PROMPT_PORT_MSG);
}
@Override
public de.gecheckt.pdf.umbenenner.application.port.out.PromptSaveResult save(String content) {
return new de.gecheckt.pdf.umbenenner.application.port.out.PromptSaveResult.WriteFailed(
"Kein Prompt-Editor-Port in diesem Startkontext verfügbar.", null);
NO_PROMPT_PORT_MSG, null);
}
@Override
@@ -615,7 +618,7 @@ public record GuiStartupContext(
.CorrectionSuggestion.CreatePromptFile suggestion) {
return new de.gecheckt.pdf.umbenenner.application.validation.technicaltest
.CorrectionOutcome.NotAttempted(
suggestion, "Kein Prompt-Editor-Port in diesem Startkontext verfügbar.");
suggestion, NO_PROMPT_PORT_MSG);
}
};
}
@@ -29,6 +29,9 @@ import javafx.scene.layout.Region;
* Die Klasse selbst erzwingt dies nicht; der Aufrufer trägt die Verantwortung.
*/
public final class GuiStatusBar {
private static final String LABEL_STYLE = "-fx-font-size: 11px; -fx-text-fill: #555555;";
/** Anzeigetext wenn keine Konfiguration geladen ist. */
static final String KEIN_PROFIL_TEXT = "Kein Profil geladen";
@@ -58,16 +61,16 @@ public final class GuiStatusBar {
// Linkes Segment: Versionsanzeige
this.versionLabel = new Label(VERSION_PREFIX + this.applicationVersion);
this.versionLabel.setStyle("-fx-font-size: 11px; -fx-text-fill: #555555;");
this.versionLabel.setStyle(LABEL_STYLE);
// Mittleres Segment: Provider und Modell
this.providerLabel = new Label(KEIN_PROFIL_TEXT);
this.providerLabel.setStyle("-fx-font-size: 11px; -fx-text-fill: #555555;");
this.providerLabel.setStyle(LABEL_STYLE);
this.providerLabel.setAlignment(Pos.CENTER);
// Rechtes Segment: Konfigurationspfad
this.configPathLabel = new Label(KEIN_PROFIL_TEXT);
this.configPathLabel.setStyle("-fx-font-size: 11px; -fx-text-fill: #555555;");
this.configPathLabel.setStyle(LABEL_STYLE);
this.configPathLabel.setAlignment(Pos.CENTER_RIGHT);
// Abstandhalter zwischen den Segmenten
@@ -4,6 +4,7 @@ import java.util.Objects;
import java.util.Optional;
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerControlUseCase;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.util.Duration;
@@ -42,7 +43,7 @@ public final class GuiStatusRefreshTimeline {
Objects.requireNonNull(onRefresh, "onRefresh must not be null");
this.timeline = new Timeline(
new KeyFrame(Duration.seconds(1), e -> onRefresh.run()));
this.timeline.setCycleCount(Timeline.INDEFINITE);
this.timeline.setCycleCount(Animation.INDEFINITE);
}
/**
@@ -87,7 +87,7 @@ public final class GuiTooltipTexts {
/** Tooltip für das Eingabefeld „Basis-URL". */
public static final String PROVIDER_BASIS_URL =
"Basis-URL des KI-Dienstes (z.B. https://api.openai.com/v1).";
"Basis-URL des KI-Dienstes (z. B. https://api.openai.com/v1).";
/** Tooltip für das Eingabefeld „Timeout". */
public static final String PROVIDER_TIMEOUT =
@@ -63,6 +63,9 @@ import javafx.scene.control.Alert;
* </ol>
*/
public final class GuiBatchRunCoordinator {
private static final String CONFIG_FILE_NOT_NULL = "configFilePath must not be null";
private static final Logger LOG = LogManager.getLogger(GuiBatchRunCoordinator.class);
private static final String WORKER_THREAD_NAME = "gui-batch-run";
@@ -353,7 +356,7 @@ public final class GuiBatchRunCoordinator {
* @throws NullPointerException if {@code configFilePath} is {@code null}
*/
public boolean start(Path configFilePath) {
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
if (isRunning()) {
return false;
}
@@ -379,7 +382,7 @@ public final class GuiBatchRunCoordinator {
*/
public boolean startMiniRun(Path configFilePath,
Set<DocumentFingerprint> fingerprintFilter) {
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
Objects.requireNonNull(fingerprintFilter, "fingerprintFilter must not be null");
if (isRunning()) {
return false;
@@ -411,7 +414,7 @@ public final class GuiBatchRunCoordinator {
*/
public boolean startReprocessing(Path configFilePath,
Set<DocumentFingerprint> fingerprintFilter) {
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
Objects.requireNonNull(fingerprintFilter, "fingerprintFilter must not be null");
if (isRunning()) {
return false;
@@ -452,7 +455,7 @@ public final class GuiBatchRunCoordinator {
* @throws NullPointerException if any argument is {@code null}
*/
public boolean startReset(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");
if (isRunning()) {
return false;
@@ -111,6 +111,11 @@ import javafx.scene.layout.VBox;
* dafür, Hintergrundereignisse vor dem Callback auf den FX-Thread zu übertragen.
*/
public final class GuiBatchRunTab {
private static final String COPY_FAILED_LOG = "Manuelle Dateikopie fehlgeschlagen: {}";
private static final String RENAME_FAILED_LOG = "Manuelle Dateiumbenennung fehlgeschlagen: {}";
private static final String DIRTY_STATE_MSG = "Dateiname-Editor: Ungespeicherte Änderungen";
private static final Logger LOG = LogManager.getLogger(GuiBatchRunTab.class);
@@ -820,7 +825,7 @@ public final class GuiBatchRunTab {
return;
}
fileNameEditor.discardChanges();
LOG.debug("Dateiname-Editor: Ungespeicherte Änderung Benutzer hat verworfen");
LOG.debug(DIRTY_STATE_MSG);
}
// Neue Zeile laden
@@ -957,55 +962,55 @@ public final class GuiBatchRunTab {
*/
private void handleCopyResult(ManualFileCopyResult result, GuiBatchRunResultRow row) {
switch (result) {
case ManualFileCopySuccess success -> {
LOG.info("Manuelle Dateikopie erfolgreich: {} → {} (Suffix: {})",
row.originalFileName(), success.appliedFileName(),
success.conflictSuffixApplied());
GuiBatchRunResultRow updatedRow = buildSuccessRowAfterCopy(row, success.appliedFileName());
currentlySelectedRow = updatedRow;
fileNameEditor.clearDirtyState();
upsertResultRowByFingerprint(updatedRow);
String targetFolder = targetFolderSupplier.get().orElse("");
fileNameEditor.loadSelection(updatedRow, targetFolder);
String msg = "Datei kopiert und gespeichert: " + success.appliedFileName();
if (success.conflictSuffixApplied()) {
msg += " (Suffix wegen Namenskonflikt angehängt)";
}
showMessage(msg);
refreshAggregateCountersFromItems();
}
case ManualFileCopyNoOpIdenticalTarget noOp -> {
LOG.info("Manuelle Dateikopie: identische Zieldatei {} bereits vorhanden kein Schreibvorgang.",
noOp.existingFileName());
GuiBatchRunResultRow updatedRow = buildSuccessRowAfterCopy(row, noOp.existingFileName());
currentlySelectedRow = updatedRow;
fileNameEditor.clearDirtyState();
upsertResultRowByFingerprint(updatedRow);
String targetFolder = targetFolderSupplier.get().orElse("");
fileNameEditor.loadSelection(updatedRow, targetFolder);
showMessage("Identische Datei bereits vorhanden Status auf SUCCESS gesetzt");
refreshAggregateCountersFromItems();
}
case ManualFileCopySuccess success -> applyCopySuccess(success, row);
case ManualFileCopyNoOpIdenticalTarget noOp -> applyCopyNoOpIdentical(noOp, row);
case ManualFileCopyDocumentNotFound notFound -> {
LOG.warn("Manuelle Dateikopie fehlgeschlagen: {}", notFound.reason());
LOG.warn(COPY_FAILED_LOG, notFound.reason());
showMessage("Fehler: Dokument nicht gefunden " + notFound.reason());
}
case ManualFileCopyInvalidState invalidState -> {
LOG.warn("Manuelle Dateikopie fehlgeschlagen: {}", invalidState.reason());
LOG.warn(COPY_FAILED_LOG, invalidState.reason());
showMessage("Fehler: Ungültiger Dokumentstatus " + invalidState.reason());
}
case ManualFileCopyFileSystemFailure fsFail -> {
LOG.warn("Manuelle Dateikopie fehlgeschlagen: {}", fsFail.message());
LOG.warn(COPY_FAILED_LOG, fsFail.message());
showMessage("Dateisystemfehler: " + fsFail.message());
}
case ManualFileCopyPersistenceFailure persistFail -> {
LOG.warn("Manuelle Dateikopie fehlgeschlagen: {}", persistFail.message());
showMessage("Persistenzfehler (Zielkopie wurde zurückgerollt): "
+ persistFail.message());
LOG.warn(COPY_FAILED_LOG, persistFail.message());
showMessage("Persistenzfehler (Zielkopie wurde zurückgerollt): " + persistFail.message());
}
}
}
private void applyCopySuccess(ManualFileCopySuccess success, GuiBatchRunResultRow row) {
LOG.info("Manuelle Dateikopie erfolgreich: {} → {} (Suffix: {})",
row.originalFileName(), success.appliedFileName(), success.conflictSuffixApplied());
GuiBatchRunResultRow updatedRow = buildSuccessRowAfterCopy(row, success.appliedFileName());
currentlySelectedRow = updatedRow;
fileNameEditor.clearDirtyState();
upsertResultRowByFingerprint(updatedRow);
fileNameEditor.loadSelection(updatedRow, targetFolderSupplier.get().orElse(""));
String msg = "Datei kopiert und gespeichert: " + success.appliedFileName();
if (success.conflictSuffixApplied()) {
msg += " (Suffix wegen Namenskonflikt angehängt)";
}
showMessage(msg);
refreshAggregateCountersFromItems();
}
private void applyCopyNoOpIdentical(ManualFileCopyNoOpIdenticalTarget noOp, GuiBatchRunResultRow row) {
LOG.info("Manuelle Dateikopie: identische Zieldatei {} bereits vorhanden kein Schreibvorgang.",
noOp.existingFileName());
GuiBatchRunResultRow updatedRow = buildSuccessRowAfterCopy(row, noOp.existingFileName());
currentlySelectedRow = updatedRow;
fileNameEditor.clearDirtyState();
upsertResultRowByFingerprint(updatedRow);
fileNameEditor.loadSelection(updatedRow, targetFolderSupplier.get().orElse(""));
showMessage("Identische Datei bereits vorhanden Status auf SUCCESS gesetzt");
refreshAggregateCountersFromItems();
}
/**
* Baut eine neue Zeilen-Sicht für ein Dokument, das per manueller Dateikopie auf
* {@code SUCCESS} gehoben wurde. Status, korrigierter Dateiname und das Zurücksetzen
@@ -1105,24 +1110,24 @@ public final class GuiBatchRunTab {
noOp.existingFileName());
}
case ManualFileRenameDocumentNotFound notFound -> {
LOG.warn("Manuelle Dateiumbenennung fehlgeschlagen: {}", notFound.reason());
LOG.warn(RENAME_FAILED_LOG, notFound.reason());
showMessage("Fehler: Dokument nicht gefunden " + notFound.reason());
}
case ManualFileRenameInvalidState invalidState -> {
LOG.warn("Manuelle Dateiumbenennung fehlgeschlagen: {}", invalidState.reason());
LOG.warn(RENAME_FAILED_LOG, invalidState.reason());
showMessage("Fehler: Ungültiger Dokumentstatus " + invalidState.reason());
}
case ManualFileRenameSourceFileMissing sourceMissing -> {
LOG.warn("Manuelle Dateiumbenennung fehlgeschlagen: {}",
LOG.warn(RENAME_FAILED_LOG,
sourceMissing.expectedFileName());
showMessage("Zieldatei nicht gefunden Umbenennung nicht möglich");
}
case ManualFileRenameFileSystemFailure fsFail -> {
LOG.warn("Manuelle Dateiumbenennung fehlgeschlagen: {}", fsFail.message());
LOG.warn(RENAME_FAILED_LOG, fsFail.message());
showMessage("Dateisystemfehler: " + fsFail.message());
}
case ManualFileRenamePersistenceFailure persistFail -> {
LOG.warn("Manuelle Dateiumbenennung fehlgeschlagen: {}", persistFail.message());
LOG.warn(RENAME_FAILED_LOG, persistFail.message());
showMessage("Persistenzfehler (Dateisystem wurde zurückgerollt): "
+ persistFail.message());
}
@@ -1263,7 +1268,7 @@ public final class GuiBatchRunTab {
return;
}
fileNameEditor.discardChanges();
LOG.debug("Dateiname-Editor: Ungespeicherte Änderung Benutzer hat verworfen");
LOG.debug(DIRTY_STATE_MSG);
}
if (!savedConfigurationReadyCheck.getAsBoolean()) {
showMessage(NO_SAVED_CONFIGURATION_HINT);
@@ -1317,7 +1322,7 @@ public final class GuiBatchRunTab {
return;
}
fileNameEditor.discardChanges();
LOG.debug("Dateiname-Editor: Ungespeicherte Änderung Benutzer hat verworfen");
LOG.debug(DIRTY_STATE_MSG);
}
if (!savedConfigurationReadyCheck.getAsBoolean()) {
showMessage(NO_SAVED_CONFIGURATION_HINT);
@@ -1562,35 +1567,12 @@ public final class GuiBatchRunTab {
return builder.toString();
}
if (row.status() == DocumentCompletionStatus.SKIPPED_ALREADY_PROCESSED) {
builder.append('\n');
row.historicalContext().ifPresentOrElse(ctx -> {
ctx.lastSuccessInstant().ifPresentOrElse(
instant -> builder.append("Bereits erfolgreich verarbeitet am ")
.append(DETAIL_DATE_FORMAT.format(
instant.atZone(ZoneId.systemDefault())))
.append('.'),
() -> builder.append("Bereits erfolgreich verarbeitet."));
ctx.lastTargetFileName().ifPresent(name ->
builder.append('\n').append("Zieldatei: ").append(name).append('.'));
}, () -> builder.append("Bereits erfolgreich verarbeitet."));
return builder.toString();
return appendSkippedAlreadyProcessed(builder, row);
}
if (row.status() == DocumentCompletionStatus.SKIPPED_FINAL_FAILURE) {
builder.append('\n');
row.historicalContext().ifPresentOrElse(ctx ->
ctx.lastFailureInstant().ifPresentOrElse(
instant -> builder.append("Endg\u00fcltig fehlgeschlagen am ")
.append(DETAIL_DATE_FORMAT.format(
instant.atZone(ZoneId.systemDefault())))
.append(". Erneute Verarbeitung nur nach Reset m\u00f6glich."),
() -> builder.append(
"Endg\u00fcltig fehlgeschlagen. Erneute Verarbeitung nur nach Reset m\u00f6glich.")),
() -> builder.append(
"Endg\u00fcltig fehlgeschlagen. Erneute Verarbeitung nur nach Reset m\u00f6glich."));
return builder.toString();
return appendSkippedFinalFailure(builder, row);
}
if (row.status() == DocumentCompletionStatus.FAILED_PERMANENT) {
// Erweiterter Erkl\u00e4rungstext gem\u00e4\u00df Spezifikation #51 \u2013 dauerhaft fehlgeschlagen
builder.append('\n').append(ProcessingStatusPresentation.DETAIL_TEXT_FAILED_PERMANENT);
row.aiFailureMessage().ifPresent(msg ->
builder.append("\n\nFehlerdetail: ")
@@ -1611,6 +1593,34 @@ public final class GuiBatchRunTab {
return builder.toString();
}
private static String appendSkippedAlreadyProcessed(StringBuilder builder, GuiBatchRunResultRow row) {
builder.append('\n');
row.historicalContext().ifPresentOrElse(ctx -> {
ctx.lastSuccessInstant().ifPresentOrElse(
instant -> builder.append("Bereits erfolgreich verarbeitet am ")
.append(DETAIL_DATE_FORMAT.format(instant.atZone(ZoneId.systemDefault())))
.append('.'),
() -> builder.append("Bereits erfolgreich verarbeitet."));
ctx.lastTargetFileName().ifPresent(name ->
builder.append('\n').append("Zieldatei: ").append(name).append('.'));
}, () -> builder.append("Bereits erfolgreich verarbeitet."));
return builder.toString();
}
private static String appendSkippedFinalFailure(StringBuilder builder, GuiBatchRunResultRow row) {
builder.append('\n');
row.historicalContext().ifPresentOrElse(ctx ->
ctx.lastFailureInstant().ifPresentOrElse(
instant -> builder.append("Endg\u00fcltig fehlgeschlagen am ")
.append(DETAIL_DATE_FORMAT.format(instant.atZone(ZoneId.systemDefault())))
.append(". Erneute Verarbeitung nur nach Reset m\u00f6glich."),
() -> builder.append(
"Endg\u00fcltig fehlgeschlagen. Erneute Verarbeitung nur nach Reset m\u00f6glich.")),
() -> builder.append(
"Endg\u00fcltig fehlgeschlagen. Erneute Verarbeitung nur nach Reset m\u00f6glich."));
return builder.toString();
}
private static GuiBatchRunLaunchOutcome rejectingMiniLaunch(
Path p, Set<DocumentFingerprint> f,
de.gecheckt.pdf.umbenenner.application.port.in.BatchRunProgressObserver o,
@@ -20,6 +20,9 @@ import de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus;
* Alle Methoden sind statisch.
*/
public final class ProcessingStatusPresentation {
private static final String STATUS_NOT_NULL = "status darf nicht null sein";
// -------------------------------------------------------------------------
// Icons (Unicode-Zeichen, zuverlässig darstellbar unter Windows 10+)
@@ -166,7 +169,7 @@ public final class ProcessingStatusPresentation {
* @throws NullPointerException wenn {@code status} {@code null} ist
*/
public static String iconFor(DocumentCompletionStatus status) {
Objects.requireNonNull(status, "status darf nicht null sein");
Objects.requireNonNull(status, STATUS_NOT_NULL);
return switch (status) {
case SUCCESS -> ICON_SUCCESS;
case FAILED_RETRYABLE -> ICON_FAILED_RETRYABLE;
@@ -187,7 +190,7 @@ public final class ProcessingStatusPresentation {
* @throws NullPointerException wenn {@code status} {@code null} ist
*/
public static String cssColorFor(DocumentCompletionStatus status) {
Objects.requireNonNull(status, "status darf nicht null sein");
Objects.requireNonNull(status, STATUS_NOT_NULL);
return switch (status) {
case SUCCESS -> COLOR_SUCCESS;
case FAILED_RETRYABLE -> COLOR_FAILED_RETRYABLE;
@@ -205,7 +208,7 @@ public final class ProcessingStatusPresentation {
* @throws NullPointerException wenn {@code status} {@code null} ist
*/
public static String tooltipFor(DocumentCompletionStatus status) {
Objects.requireNonNull(status, "status darf nicht null sein");
Objects.requireNonNull(status, STATUS_NOT_NULL);
return switch (status) {
case SUCCESS -> TOOLTIP_SUCCESS;
case FAILED_RETRYABLE -> TOOLTIP_FAILED_RETRYABLE;
@@ -224,7 +227,7 @@ public final class ProcessingStatusPresentation {
* @throws NullPointerException wenn {@code status} {@code null} ist
*/
public static String summaryCategoryFor(DocumentCompletionStatus status) {
Objects.requireNonNull(status, "status darf nicht null sein");
Objects.requireNonNull(status, STATUS_NOT_NULL);
return switch (status) {
case SUCCESS -> SUMMARY_CATEGORY_SUCCESS;
case FAILED_RETRYABLE -> SUMMARY_CATEGORY_FAILED_RETRYABLE;
@@ -243,7 +246,7 @@ public final class ProcessingStatusPresentation {
* @throws NullPointerException wenn {@code status} {@code null} ist
*/
public static StatusVisuals visualsFor(DocumentCompletionStatus status) {
Objects.requireNonNull(status, "status darf nicht null sein");
Objects.requireNonNull(status, STATUS_NOT_NULL);
return new StatusVisuals(
iconFor(status),
cssColorFor(status),
@@ -264,7 +267,7 @@ public final class ProcessingStatusPresentation {
* @throws NullPointerException wenn {@code status} {@code null} ist
*/
public static String displayTextFor(ProcessingStatus status) {
Objects.requireNonNull(status, "status darf nicht null sein");
Objects.requireNonNull(status, STATUS_NOT_NULL);
return switch (status) {
case SUCCESS -> "✓ Erfolgreich";
case FAILED_RETRYABLE -> "↻ Temporärer Fehler";
@@ -87,6 +87,11 @@ import javafx.scene.layout.VBox;
* Verarbeitungslaufs deaktiviert.
*/
public final class GuiHistoryTab {
private static final String BOLD_STYLE = "-fx-font-weight: bold;";
private static final String NO_ERROR_DETAILS_MSG = "Keine Fehlerdetails gespeichert.";
private static final String NO_CONFIG_LOADED_MSG = "Keine Konfiguration geladen.";
private static final Logger LOG = LogManager.getLogger(GuiHistoryTab.class);
@@ -421,20 +426,20 @@ public final class GuiHistoryTab {
addDetailRow(5, "Aktualisiert:", detailUpdatedLabel);
Label detailTitle = new Label("Dokument-Details");
detailTitle.setStyle("-fx-font-weight: bold;");
detailTitle.setStyle(BOLD_STYLE);
// Versuche-Tabelle
buildAttemptsTable();
Label attemptsTitle = new Label("Verarbeitungsversuche");
attemptsTitle.setStyle("-fx-font-weight: bold;");
attemptsTitle.setStyle(BOLD_STYLE);
// Fehlerursache (aus letztem Fehler-Versuch)
failureArea.setEditable(false);
failureArea.setWrapText(true);
failureArea.setPrefRowCount(3);
failureArea.setPromptText("Keine Fehlerdetails gespeichert.");
failureArea.setPromptText(NO_ERROR_DETAILS_MSG);
Label failureTitle = new Label("Fehlerursache (letzter Fehler-Versuch)");
failureTitle.setStyle("-fx-font-weight: bold;");
failureTitle.setStyle(BOLD_STYLE);
failureArea.setTooltip(new Tooltip(GuiTooltipTexts.VERLAUF_FAILURE_AREA));
@@ -445,7 +450,7 @@ public final class GuiHistoryTab {
reasoningArea.setText(DETAIL_PLACEHOLDER);
reasoningArea.setTooltip(new Tooltip(GuiTooltipTexts.VERLAUF_REASONING_AREA));
Label reasoningTitle = new Label("KI-Begründung (ausgewählter Versuch)");
reasoningTitle.setStyle("-fx-font-weight: bold;");
reasoningTitle.setStyle(BOLD_STYLE);
VBox rightPane = new VBox(8,
detailTitle, detailGrid,
@@ -579,7 +584,7 @@ public final class GuiHistoryTab {
Path configPath = configPathSupplier.get();
if (configPath == null) {
statusBarLabel.setText("Keine Konfiguration geladen bitte zuerst eine Konfigurationsdatei öffnen.");
overviewTable.setPlaceholder(new Label("Keine Konfiguration geladen."));
overviewTable.setPlaceholder(new Label(NO_CONFIG_LOADED_MSG));
return;
}
@@ -666,7 +671,7 @@ public final class GuiHistoryTab {
Path configPath = configPathSupplier.get();
if (configPath == null) {
showInfo("Keine Konfiguration geladen.");
showInfo(NO_CONFIG_LOADED_MSG);
return;
}
@@ -674,28 +679,10 @@ public final class GuiHistoryTab {
.filter(r -> r.overallStatus() == ProcessingStatus.SUCCESS)
.count();
StringBuilder sb = new StringBuilder();
sb.append("Setzt den Status auf READY_FOR_AI zurück.\n");
sb.append("Fehlerzähler und letzter Fehlerzeitpunkt werden gelöscht.\n");
sb.append("Die Versuchshistorie bleibt vollständig erhalten.\n\n");
if (selectedItems.size() == 1) {
sb.append("Quelldatei: ").append(selectedItems.get(0).sourceFileName());
} else {
sb.append(selectedItems.size()).append(" Einträge werden zurückgesetzt.");
}
if (successCount > 0) {
sb.append("\n\nHinweis: ").append(successCount)
.append(" der ausgewählten Einträge ")
.append(successCount == 1 ? "hat" : "haben")
.append(" Status \"Erfolgreich\". ")
.append(successCount == 1 ? "Dieser Eintrag wird" : "Diese Einträge werden")
.append(" erneut verarbeitet.");
}
Alert confirm = new Alert(Alert.AlertType.CONFIRMATION);
confirm.setTitle("Status zurücksetzen");
confirm.setHeaderText("Status zurücksetzen?");
confirm.setContentText(sb.toString());
confirm.setContentText(buildResetConfirmationText(selectedItems, successCount));
Optional<ButtonType> choice = confirm.showAndWait();
if (choice.isEmpty() || choice.get() != ButtonType.OK) return;
@@ -729,6 +716,27 @@ public final class GuiHistoryTab {
});
}
private static String buildResetConfirmationText(List<DocumentHistoryRow> selectedItems, long successCount) {
StringBuilder sb = new StringBuilder();
sb.append("Setzt den Status auf READY_FOR_AI zurück.\n");
sb.append("Fehlerzähler und letzter Fehlerzeitpunkt werden gelöscht.\n");
sb.append("Die Versuchshistorie bleibt vollständig erhalten.\n\n");
if (selectedItems.size() == 1) {
sb.append("Quelldatei: ").append(selectedItems.get(0).sourceFileName());
} else {
sb.append(selectedItems.size()).append(" Einträge werden zurückgesetzt.");
}
if (successCount > 0) {
sb.append("\n\nHinweis: ").append(successCount)
.append(" der ausgewählten Einträge ")
.append(successCount == 1 ? "hat" : "haben")
.append(" Status \"Erfolgreich\". ")
.append(successCount == 1 ? "Dieser Eintrag wird" : "Diese Einträge werden")
.append(" erneut verarbeitet.");
}
return sb.toString();
}
private void handleDeleteAction() {
if (runningCheck.getAsBoolean()) {
showInfo(LAUF_AKTIV_HINWEIS);
@@ -741,7 +749,7 @@ public final class GuiHistoryTab {
Path configPath = configPathSupplier.get();
if (configPath == null) {
showInfo("Keine Konfiguration geladen.");
showInfo(NO_CONFIG_LOADED_MSG);
return;
}
@@ -818,23 +826,26 @@ public final class GuiHistoryTab {
// Fehlerursache aus letztem Fehler-Versuch anzeigen
showLastFailureMessage(result.attempts(), record.overallStatus());
// Neuesten Versuch selektieren und Begründung anzeigen
if (!result.attempts().isEmpty()) {
ProcessingAttempt last = result.attempts().get(result.attempts().size() - 1);
selectLatestAttemptAndShowReasoning(result.attempts());
attemptsTable.getSelectionModel().selectedItemProperty().addListener(
(obs, old, attempt) -> onAttemptSelected(attempt));
}
private void selectLatestAttemptAndShowReasoning(java.util.List<ProcessingAttempt> attempts) {
if (!attempts.isEmpty()) {
ProcessingAttempt last = attempts.get(attempts.size() - 1);
attemptsTable.getSelectionModel().select(last);
showReasoning(last);
} else {
reasoningArea.setText("");
reasoningArea.setPromptText(NO_REASONING_TEXT);
}
}
// KI-Begründung bei Versuchs-Selektion aktualisieren
attemptsTable.getSelectionModel().selectedItemProperty().addListener(
(obs, old, attempt) -> {
if (attempt != null) {
showReasoning(attempt);
}
});
private void onAttemptSelected(ProcessingAttempt attempt) {
if (attempt != null) {
showReasoning(attempt);
}
}
/**
@@ -865,7 +876,7 @@ public final class GuiHistoryTab {
failureArea.setText(failureMessage != null
? AiFailureMessageTranslator.translate(failureMessage) : "");
failureArea.setPromptText("Keine Fehlerdetails gespeichert.");
failureArea.setPromptText(NO_ERROR_DETAILS_MSG);
}
private void showReasoning(ProcessingAttempt attempt) {
@@ -883,7 +894,7 @@ public final class GuiHistoryTab {
clearDetailFields();
attemptsItems.clear();
failureArea.setText("");
failureArea.setPromptText("Keine Fehlerdetails gespeichert.");
failureArea.setPromptText(NO_ERROR_DETAILS_MSG);
reasoningArea.setText(DETAIL_PLACEHOLDER);
}
@@ -906,7 +917,7 @@ public final class GuiHistoryTab {
private void addDetailRow(int row, String labelText, Label valueLabel) {
Label label = new Label(labelText);
label.setStyle("-fx-font-weight: bold;");
label.setStyle(BOLD_STYLE);
valueLabel.setMaxWidth(Double.MAX_VALUE);
GridPane.setHgrow(valueLabel, Priority.ALWAYS);
detailGrid.add(label, 0, row);
@@ -119,9 +119,15 @@ class GuiBatchRunCoordinatorMiniRunTest {
void startReset_invokesResetPortAndDispatchesResult() {
AtomicReference<ResetDocumentStatusResult> captured = new AtomicReference<>();
GuiBatchRunCoordinator.Listener listener = new GuiBatchRunCoordinator.Listener() {
@Override public void onRunStarted(RunId runId, int totalCandidates) { }
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) { }
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) { }
@Override public void onRunStarted(RunId runId, int totalCandidates) {
// intentionally empty
}
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) {
// intentionally empty
}
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) {
// intentionally empty
}
@Override public void onResetCompleted(ResetDocumentStatusResult result) {
captured.set(result);
}
@@ -170,9 +176,15 @@ class GuiBatchRunCoordinatorMiniRunTest {
void startReset_portThrowsException_mapsToAllFailures() {
AtomicReference<ResetDocumentStatusResult> captured = new AtomicReference<>();
GuiBatchRunCoordinator.Listener listener = new GuiBatchRunCoordinator.Listener() {
@Override public void onRunStarted(RunId runId, int totalCandidates) { }
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) { }
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) { }
@Override public void onRunStarted(RunId runId, int totalCandidates) {
// intentionally empty
}
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) {
// intentionally empty
}
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) {
// intentionally empty
}
@Override public void onResetCompleted(ResetDocumentStatusResult result) {
captured.set(result);
}
@@ -198,9 +210,15 @@ class GuiBatchRunCoordinatorMiniRunTest {
void listenerDefaultOnResetCompleted_doesNotThrow() {
// Verify the default implementation is safe to call.
GuiBatchRunCoordinator.Listener listener = new GuiBatchRunCoordinator.Listener() {
@Override public void onRunStarted(RunId runId, int totalCandidates) { }
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) { }
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) { }
@Override public void onRunStarted(RunId runId, int totalCandidates) {
// intentionally empty
}
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) {
// intentionally empty
}
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) {
// intentionally empty
}
};
listener.onResetCompleted(new ResetDocumentStatusResult(0, Set.of(), Map.of()));
}
@@ -223,9 +241,15 @@ class GuiBatchRunCoordinatorMiniRunTest {
private static GuiBatchRunCoordinator.Listener noOpListener() {
return new GuiBatchRunCoordinator.Listener() {
@Override public void onRunStarted(RunId runId, int totalCandidates) { }
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) { }
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) { }
@Override public void onRunStarted(RunId runId, int totalCandidates) {
// intentionally empty
}
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) {
// intentionally empty
}
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) {
// intentionally empty
}
};
}
@@ -247,8 +247,12 @@ class GuiBatchRunCoordinatorTest {
GuiBatchRunCoordinator coordinator = new GuiBatchRunCoordinator(
launcher, syncThreadFactory(), syncDispatcher(),
new GuiBatchRunCoordinator.Listener() {
@Override public void onRunStarted(RunId runId, int totalCandidates) { }
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) { }
@Override public void onRunStarted(RunId runId, int totalCandidates) {
// intentionally empty
}
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) {
// intentionally empty
}
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) {
captured.set(outcome);
}
@@ -270,8 +274,12 @@ class GuiBatchRunCoordinatorTest {
GuiBatchRunCoordinator coordinator = new GuiBatchRunCoordinator(
launcher, syncThreadFactory(), syncDispatcher(),
new GuiBatchRunCoordinator.Listener() {
@Override public void onRunStarted(RunId runId, int totalCandidates) { }
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) { }
@Override public void onRunStarted(RunId runId, int totalCandidates) {
// intentionally empty
}
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) {
// intentionally empty
}
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) {
captured.set(outcome);
}
@@ -322,9 +330,15 @@ class GuiBatchRunCoordinatorTest {
private static GuiBatchRunCoordinator.Listener noOpListener() {
return new GuiBatchRunCoordinator.Listener() {
@Override public void onRunStarted(RunId runId, int totalCandidates) { }
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) { }
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) { }
@Override public void onRunStarted(RunId runId, int totalCandidates) {
// intentionally empty
}
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) {
// intentionally empty
}
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) {
// intentionally empty
}
};
}