Fixe SonarQube Reliability-Issues S2789, S3077 und S2184

S2789 (32 Stellen): null-Checks auf Optional-Feldern entfernt bzw. durch
Objects.requireNonNullElse(field, Optional.empty()) ersetzt. Die zuvor
defensive Behandlung von null-Optionals erfolgt jetzt ueber den
Bibliotheksaufruf, sodass das Verhalten unveraendert bleibt, aber die
direkte Null-Pruefung gegen Optional entfaellt.

S3077 (5 Stellen): volatile-Felder mit Objekt-Referenzen durch
AtomicReference ersetzt (ScheduledExecutorServiceSchedulerAdapter,
BootstrapRunner.guiApplicationRunContext, PdfPreviewPane.currentDocument/
currentRenderer/currentSourceFile, SingleInstanceGuard.socket). Die
PdfPreviewPane-Felder werden auf JavaFX- bzw. Worker-Thread genutzt;
AtomicReference bietet hier konsistente atomare Publikation ohne
Verhaltensaenderung.

S2184 (3 Stellen): Integer-Division SECONDARY_SPACING / 2 durch
SECONDARY_SPACING / 2.0 ersetzt, damit das Insets-Argument als double
ohne implizite Truncierung berechnet wird.
This commit is contained in:
2026-05-07 17:11:29 +02:00
parent 11eac074ef
commit 32e32a9b27
18 changed files with 76 additions and 80 deletions
@@ -4,6 +4,7 @@ import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -105,7 +106,7 @@ public final class GuiSchedulerTab {
public GuiSchedulerTab(
Optional<SchedulerControlUseCase> schedulerUseCase,
Supplier<Boolean> isConfigDirty) {
this.schedulerUseCase = schedulerUseCase == null ? Optional.empty() : schedulerUseCase;
this.schedulerUseCase = Objects.requireNonNullElse(schedulerUseCase, Optional.empty());
this.isConfigDirty = isConfigDirty != null ? isConfigDirty : () -> false;
tab.setClosable(false);
buildUi();
@@ -146,8 +146,8 @@ public record GuiStartupContext(
*/
public GuiStartupContext {
initialState = Objects.requireNonNull(initialState, "initialState must not be null");
startupNotice = startupNotice == null ? Optional.empty() : startupNotice;
applicationContextError = applicationContextError == null ? Optional.empty() : applicationContextError;
startupNotice = Objects.requireNonNullElse(startupNotice, Optional.empty());
applicationContextError = Objects.requireNonNullElse(applicationContextError, Optional.empty());
configurationFileLoader = Objects.requireNonNull(configurationFileLoader,
"configurationFileLoader must not be null");
configurationFileWriter = Objects.requireNonNull(configurationFileWriter,
@@ -191,8 +191,8 @@ public record GuiStartupContext(
"promptEditorPortFactory must not be null");
createNewDatabasePort = Objects.requireNonNull(createNewDatabasePort,
"createNewDatabasePort must not be null");
schedulerControlUseCase = schedulerControlUseCase == null ? Optional.empty() : schedulerControlUseCase;
configurationFileLockPort = configurationFileLockPort == null ? Optional.empty() : configurationFileLockPort;
schedulerControlUseCase = Objects.requireNonNullElse(schedulerControlUseCase, Optional.empty());
configurationFileLockPort = Objects.requireNonNullElse(configurationFileLockPort, Optional.empty());
applicationContextInitializer = applicationContextInitializer == null
? GuiApplicationContextInitializer.noOp() : applicationContextInitializer;
}
@@ -276,7 +276,7 @@ public final class GuiBatchRunCoordinator {
this.historicalDocumentContextPort = Objects.requireNonNull(
historicalDocumentContextPort, "historicalDocumentContextPort must not be null");
this.configurationFileLockPort =
configurationFileLockPort == null ? Optional.empty() : configurationFileLockPort;
Objects.requireNonNullElse(configurationFileLockPort, Optional.empty());
}
/**
@@ -33,7 +33,7 @@ public record GuiBatchRunLaunchOutcome(
* Compact constructor normalising the failure message holder.
*/
public GuiBatchRunLaunchOutcome {
failureMessage = failureMessage == null ? Optional.empty() : failureMessage;
failureMessage = Objects.requireNonNullElse(failureMessage, Optional.empty());
}
/**
@@ -88,16 +88,16 @@ public record GuiBatchRunResultRow(
}
Objects.requireNonNull(fingerprint, "fingerprint must not be null");
Objects.requireNonNull(status, "status must not be null");
finalFileName = finalFileName == null ? Optional.empty() : finalFileName;
correctedFileName = correctedFileName == null ? Optional.empty() : correctedFileName;
resolvedDate = resolvedDate == null ? Optional.empty() : resolvedDate;
aiReasoning = aiReasoning == null ? Optional.empty() : aiReasoning;
aiFailureMessage = aiFailureMessage == null ? Optional.empty() : aiFailureMessage;
finalFileName = Objects.requireNonNullElse(finalFileName, Optional.empty());
correctedFileName = Objects.requireNonNullElse(correctedFileName, Optional.empty());
resolvedDate = Objects.requireNonNullElse(resolvedDate, Optional.empty());
aiReasoning = Objects.requireNonNullElse(aiReasoning, Optional.empty());
aiFailureMessage = Objects.requireNonNullElse(aiFailureMessage, Optional.empty());
Objects.requireNonNull(processingDuration, "processingDuration must not be null");
if (processingDuration.isNegative()) {
throw new IllegalArgumentException("processingDuration must not be negative");
}
historicalContext = historicalContext == null ? Optional.empty() : historicalContext;
historicalContext = Objects.requireNonNullElse(historicalContext, Optional.empty());
}
/**
@@ -302,7 +302,7 @@ public final class GuiBatchRunTab {
targetFolderSupplier, "targetFolderSupplier must not be null");
Optional<ConfigurationFileLockPort> effectiveLockPort =
configurationFileLockPort == null ? Optional.empty() : configurationFileLockPort;
Objects.requireNonNullElse(configurationFileLockPort, Optional.empty());
this.coordinator = new GuiBatchRunCoordinator(
(configPath, observer, token) ->
launcherSupplier.get().launch(configPath, observer, token),
@@ -618,7 +618,7 @@ public final class GuiBatchRunTab {
HBox selectionButtonBar = new HBox(SECONDARY_SPACING, reprocessButton, resetStatusButton);
selectionButtonBar.setAlignment(Pos.CENTER_LEFT);
selectionButtonBar.setPadding(new Insets(SECONDARY_SPACING / 2, 0, SECONDARY_SPACING / 2, 0));
selectionButtonBar.setPadding(new Insets(SECONDARY_SPACING / 2.0, 0, SECONDARY_SPACING / 2.0, 0));
// Meldungsbereich unterhalb der Selektions-Buttons (linke Spalte)
messageArea.setId("batch-run-message-area");
@@ -1246,7 +1246,7 @@ public final class GuiBatchRunTab {
HBox runButtonBar = new HBox(SECONDARY_SPACING, startButton, cancelButton);
runButtonBar.setAlignment(Pos.CENTER_LEFT);
runButtonBar.setPadding(new Insets(SECONDARY_SPACING / 2, 0, 0, 0));
runButtonBar.setPadding(new Insets(SECONDARY_SPACING / 2.0, 0, 0, 0));
return runButtonBar;
}
@@ -8,6 +8,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -170,18 +171,18 @@ public final class PdfPreviewPane {
/**
* Aktuell geöffnetes PDF-Dokument. Zugriff ausschließlich vom Worker-Thread.
* {@code null} wenn kein Dokument geöffnet ist.
* Leerer Referenzwert wenn kein Dokument geöffnet ist.
*/
private volatile PDDocument currentDocument = null;
private final AtomicReference<PDDocument> currentDocument = new AtomicReference<>();
/**
* Renderer für das aktuell geöffnete Dokument. Zugriff ausschließlich vom Worker-Thread.
* {@code null} wenn kein Dokument geöffnet ist.
* Leerer Referenzwert wenn kein Dokument geöffnet ist.
*/
private volatile PDFRenderer currentRenderer = null;
private final AtomicReference<PDFRenderer> currentRenderer = new AtomicReference<>();
/** Aktuell geladene Quelldatei; null wenn keine Selektion vorliegt. */
private volatile Path currentSourceFile = null;
/** Aktuell geladene Quelldatei; leerer Referenzwert wenn keine Selektion vorliegt. */
private final AtomicReference<Path> currentSourceFile = new AtomicReference<>();
/** Aktuell angezeigte Seite (1-basiert; 0 wenn keine Datei geladen). */
private volatile int currentPage = 0;
@@ -303,7 +304,7 @@ public final class PdfPreviewPane {
clear();
return;
}
currentSourceFile = sourceFile;
currentSourceFile.set(sourceFile);
currentPage = 0;
totalPages = -1;
pageCache.clear();
@@ -318,7 +319,7 @@ public final class PdfPreviewPane {
* Muss auf dem JavaFX Application Thread aufgerufen werden.
*/
public void clear() {
currentSourceFile = null;
currentSourceFile.set(null);
currentPage = 0;
totalPages = -1;
pageCache.clear();
@@ -472,12 +473,13 @@ public final class PdfPreviewPane {
try {
PDDocument doc = Loader.loadPDF(ioFile);
currentDocument = doc;
currentRenderer = new PDFRenderer(doc);
currentDocument.set(doc);
PDFRenderer renderer = new PDFRenderer(doc);
currentRenderer.set(renderer);
int pages = Math.max(1, doc.getNumberOfPages());
BufferedImage buffered =
currentRenderer.renderImageWithDPI(0, RENDER_DPI, ImageType.RGB);
renderer.renderImageWithDPI(0, RENDER_DPI, ImageType.RGB);
Image fxImage = SwingFXUtils.toFXImage(buffered, null);
final int totalPagesFinal = pages;
@@ -513,7 +515,7 @@ public final class PdfPreviewPane {
* @param seq die Sequenznummer dieser Anforderung
*/
private void renderPageOnWorker(int page, long seq) {
PDFRenderer renderer = currentRenderer;
PDFRenderer renderer = currentRenderer.get();
if (renderer == null) {
// Dokument wurde zwischenzeitlich geschlossen nichts zu tun
return;
@@ -542,9 +544,8 @@ public final class PdfPreviewPane {
* auf dem Worker-Thread und ist idempotent.
*/
private void closeCurrentDocumentOnWorker() {
PDDocument doc = currentDocument;
currentDocument = null;
currentRenderer = null;
PDDocument doc = currentDocument.getAndSet(null);
currentRenderer.set(null);
if (doc != null) {
try {
doc.close();
@@ -824,7 +825,7 @@ public final class PdfPreviewPane {
}
private void updateNavigationButtons() {
boolean canNavigate = enabled && currentSourceFile != null && totalPages > 0;
boolean canNavigate = enabled && currentSourceFile.get() != null && totalPages > 0;
prevButton.setDisable(!canNavigate || currentPage <= 1);
nextButton.setDisable(!canNavigate || currentPage >= totalPages);
}
@@ -29,10 +29,10 @@ public record GuiConfigurationEditorState(
* @param values current editable configuration values; must not be {@code null}
*/
public GuiConfigurationEditorState {
loadedFileSnapshot = loadedFileSnapshot == null ? Optional.empty() : loadedFileSnapshot;
loadedFileSnapshot = Objects.requireNonNullElse(loadedFileSnapshot, Optional.empty());
baselineValues = Objects.requireNonNull(baselineValues, "baselineValues must not be null");
values = Objects.requireNonNull(values, "values must not be null");
pendingMigrationMessage = pendingMigrationMessage == null ? Optional.empty() : pendingMigrationMessage;
pendingMigrationMessage = Objects.requireNonNullElse(pendingMigrationMessage, Optional.empty());
}
/**
@@ -39,7 +39,7 @@ public record GuiMessageEntry(
Objects.requireNonNull(severity, "severity must not be null");
Objects.requireNonNull(text, "text must not be null");
Objects.requireNonNull(timestamp, "timestamp must not be null");
source = source == null ? Optional.empty() : source;
source = Objects.requireNonNullElse(source, Optional.empty());
}
/**