Erweiterung für V2.0: M9 umgesetzt

This commit is contained in:
2026-04-13 13:36:54 +02:00
parent f74e3d6d73
commit 3f149b2017
24 changed files with 2363 additions and 156 deletions
@@ -0,0 +1,96 @@
package de.gecheckt.pdf.umbenenner.adapter.in.gui;
import java.util.Optional;
import javafx.application.Application;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* Entry point for the JavaFX desktop GUI inbound adapter.
* <p>
* This class is the designated entry point through which Bootstrap launches the
* graphical user interface. It acts as the inbound adapter boundary: it receives
* control from Bootstrap and delegates to the JavaFX application lifecycle via
* {@link PdfUmbenennerGuiApplication}.
* <p>
* Responsibilities of this adapter:
* <ul>
* <li>Accept the GUI start signal from Bootstrap</li>
* <li>Launch the JavaFX application lifecycle via {@link Application#launch}</li>
* <li>Ensure that all UI operations are dispatched on the JavaFX Application Thread</li>
* <li>Ensure that all blocking operations (file I/O, network, database) run on
* background worker threads and never block the UI thread</li>
* </ul>
* <p>
* This class must not be instantiated or called by any module other than Bootstrap.
* Domain, application, CLI adapter, and outbound adapter modules must remain
* free of dependencies on this class and on JavaFX in general.
* <p>
* The actual JavaFX {@link Application} subclass ({@link PdfUmbenennerGuiApplication})
* and all GUI view components are separate classes within this package. This entry
* point class coordinates the hand-off from the Bootstrap layer into the JavaFX lifecycle.
*
* <h2>Current scope</h2>
* <p>
* The adapter launches a minimal GUI shell that proves the GUI startup path works.
* It does not provide a configuration editor, file operations, validation, provider
* controls, or any other functionality beyond the technical startup proof.
*/
public class GuiAdapter {
private static final Logger LOG = LogManager.getLogger(GuiAdapter.class);
/**
* Creates a new {@code GuiAdapter} instance.
* <p>
* Bootstrap is responsible for constructing this adapter and invoking
* {@link #start()} at the appropriate point in the application lifecycle.
* No JavaFX initialization is performed during construction; the JavaFX
* runtime is only started when {@link #start()} is called.
*/
public GuiAdapter() {
// Bootstrap constructs this adapter before deciding to start the GUI.
// JavaFX initialization is deferred to start() to ensure the headless
// path is never burdened with premature JavaFX class loading.
}
/**
* Starts the JavaFX GUI and blocks until the user closes the window.
* <p>
* When {@code startupNotice} is present, the notice string is forwarded to
* {@link PdfUmbenennerGuiApplication} so it can be displayed to the user on startup.
* This is used when Bootstrap has detected a problem with the supplied
* {@code --config} path (e.g. the file was not found) and wishes to inform the
* user before the normal GUI shell is shown.
* <p>
* This method delegates to {@link Application#launch(Class, String...)} with
* {@link PdfUmbenennerGuiApplication} as the application class. The call blocks
* until the JavaFX application terminates (typically when the user closes the
* main window).
* <p>
* This method must be called from a thread that is permitted to run a JavaFX
* application (typically the main thread). It must not be called on the JavaFX
* Application Thread itself, and must not be called more than once per JVM process.
* <p>
* Upon normal GUI shutdown (user closes the window), this method returns
* normally. Bootstrap is responsible for deriving the appropriate exit code
* from the return.
*
* @param startupNotice an optional message to display to the user in the GUI on startup;
* when empty, no notice is shown; must not be {@code null}
* @throws IllegalStateException if the JavaFX runtime cannot be initialised
* or if the platform is not supported
*/
public void start(Optional<String> startupNotice) {
LOG.info("GUI-Adapter: JavaFX-Start wird eingeleitet.");
if (startupNotice.isPresent()) {
Application.launch(PdfUmbenennerGuiApplication.class,
PdfUmbenennerGuiApplication.STARTUP_NOTICE_ARG_PREFIX + startupNotice.get());
} else {
Application.launch(PdfUmbenennerGuiApplication.class);
}
LOG.info("GUI-Adapter: JavaFX-Anwendung wurde beendet.");
}
}
@@ -0,0 +1,153 @@
package de.gecheckt.pdf.umbenenner.adapter.in.gui;
import java.util.List;
import java.util.Optional;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* Minimal JavaFX {@link Application} subclass that establishes the GUI shell.
* <p>
* This class is the JavaFX lifecycle entry point launched by
* {@link GuiAdapter#start(java.util.Optional)}. It creates the primary stage with a
* minimal, neutral layout that proves the GUI startup path is technically functional.
* The layout is deliberately structured as a {@link BorderPane} so that later milestones
* can populate individual regions (header, center content, bottom message area) without
* requiring an architectural restructuring.
*
* <h2>Startup notice</h2>
* <p>
* When Bootstrap forwards a startup notice (e.g. because the supplied {@code --config}
* path was not found), the notice is passed as an application parameter prefixed with
* {@link #STARTUP_NOTICE_ARG_PREFIX}. This class reads the parameter via
* {@link Application#getParameters()} and displays it prominently in the center region
* instead of the default placeholder.
*
* <h2>Current scope</h2>
* <p>
* The shell displays the application window with a neutral title and either a static
* placeholder label or a startup notice. It contains no configuration editor, no file
* operations, no validation, no provider controls, and no message area. These are the
* responsibility of later milestones.
*
* <h2>Explicit non-goals</h2>
* <ul>
* <li>No configuration editor or editable input fields</li>
* <li>No file operations (Neu, Oeffnen, Speichern, Speichern unter)</li>
* <li>No validation or provider controls</li>
* <li>No message area or technical tests</li>
* <li>No welcome text in the final product sense</li>
* </ul>
*
* <h2>Threading</h2>
* <p>
* The {@link #start(Stage)} method is called by the JavaFX runtime on the
* JavaFX Application Thread. No blocking operations are performed during
* stage setup.
*/
public class PdfUmbenennerGuiApplication extends Application {
private static final Logger LOG = LogManager.getLogger(PdfUmbenennerGuiApplication.class);
/**
* Argument prefix used to forward a startup notice from {@link GuiAdapter} to this
* application via {@link Application#launch(Class, String[])}.
* <p>
* When an argument beginning with this prefix is present in the raw parameter list,
* the remainder of the argument string is treated as the notice text to display.
*/
static final String STARTUP_NOTICE_ARG_PREFIX = "--startup-notice=";
private static final String WINDOW_TITLE = "PDF-Umbenenner";
private static final double DEFAULT_WIDTH = 800;
private static final double DEFAULT_HEIGHT = 600;
/**
* Creates a new instance of the JavaFX application.
* <p>
* This no-argument constructor is required by the JavaFX runtime, which
* instantiates the {@link Application} subclass reflectively.
*/
public PdfUmbenennerGuiApplication() {
// Required by JavaFX runtime for reflective instantiation.
}
/**
* Initializes and shows the primary stage with a minimal GUI shell.
* <p>
* The stage is set up with a {@link BorderPane} root layout. When a startup notice is
* present (forwarded via application parameters by {@link GuiAdapter}), the notice is
* displayed prominently in red in the center region. Otherwise a neutral placeholder
* label is shown. The layout structure is chosen to allow incremental extension in later
* milestones without requiring a root-level restructuring.
* <p>
* Start and shutdown events are logged via Log4j2 to satisfy the GUI logging requirements.
*
* @param primaryStage the primary stage provided by the JavaFX runtime; never {@code null}
*/
@Override
public void start(Stage primaryStage) {
LOG.info("GUI-Shell: JavaFX-Oberfläche wird initialisiert.");
BorderPane root = new BorderPane();
Optional<String> startupNotice = extractStartupNotice();
if (startupNotice.isPresent()) {
Label noticeLabel = new Label(startupNotice.get());
noticeLabel.setWrapText(true);
noticeLabel.setAlignment(Pos.CENTER);
noticeLabel.setStyle("-fx-font-size: 13px; -fx-text-fill: #cc0000;");
root.setCenter(noticeLabel);
LOG.info("GUI-Shell: Starthinweis wird angezeigt.");
} else {
Label placeholderLabel = new Label("PDF-Umbenenner GUI wird vorbereitet …");
placeholderLabel.setStyle("-fx-font-size: 14px; -fx-text-fill: #666666;");
root.setCenter(placeholderLabel);
}
Scene scene = new Scene(root, DEFAULT_WIDTH, DEFAULT_HEIGHT);
primaryStage.setTitle(WINDOW_TITLE);
primaryStage.setScene(scene);
primaryStage.setOnCloseRequest(event ->
LOG.info("GUI-Shell: Fenster wird vom Benutzer geschlossen."));
primaryStage.show();
LOG.info("GUI-Shell: Hauptfenster erfolgreich angezeigt.");
}
/**
* Extracts the startup notice from the application parameters, if present.
* <p>
* Searches the raw parameter list for an argument beginning with
* {@link #STARTUP_NOTICE_ARG_PREFIX} and returns the remainder as the notice text.
* Returns an empty Optional when no such argument is present.
*
* @return the startup notice text, or an empty Optional if no notice was forwarded
*/
private Optional<String> extractStartupNotice() {
List<String> rawParams = getParameters().getRaw();
return rawParams.stream()
.filter(p -> p.startsWith(STARTUP_NOTICE_ARG_PREFIX))
.map(p -> p.substring(STARTUP_NOTICE_ARG_PREFIX.length()))
.findFirst();
}
/**
* Called by the JavaFX runtime when the application is stopping.
* <p>
* Logs the GUI shutdown event. No additional cleanup is required
* for the minimal shell.
*/
@Override
public void stop() {
LOG.info("GUI-Shell: JavaFX-Anwendung wird beendet.");
}
}
@@ -0,0 +1,24 @@
/**
* Inbound adapter for the JavaFX desktop GUI.
* <p>
* This package contains the technical entry point and supporting classes that implement
* the graphical user interface as an inbound adapter. The GUI adapter depends exclusively
* on the application layer's inbound port contracts and domain types; it introduces no
* dependencies into domain, application, CLI adapter, or outbound adapter modules.
* <p>
* Architectural position:
* <ul>
* <li>Role: Inbound adapter (GUI entry point)</li>
* <li>Depends on: {@code pdf-umbenenner-application} and {@code pdf-umbenenner-domain}</li>
* <li>Must not be depended on by: domain, application, or any other adapter</li>
* <li>JavaFX is introduced exclusively in this module; all other modules remain JavaFX-free</li>
* </ul>
* <p>
* Threading contract: every potentially blocking operation (file I/O, network, database access)
* must run on a background worker thread. All UI updates must be dispatched via
* {@code Platform.runLater} on the JavaFX Application Thread.
* <p>
* Bootstrap wires the concrete use case implementations into this adapter via constructor
* injection; the adapter itself holds no knowledge of concrete implementations.
*/
package de.gecheckt.pdf.umbenenner.adapter.in.gui;