From 3cf2cf9288d26b425e1042007a0642c9cb76f5f0 Mon Sep 17 00:00:00 2001 From: naumueller Date: Thu, 5 Mar 2026 15:58:01 +0100 Subject: [PATCH] JavaDoc comments added --- readme.md | 148 ++++++++++++++++++ src/main/java/vassistent/App.java | 13 +- .../bootstrap/ApplicationContext.java | 83 ++++++++++ .../bootstrap/ApplicationInitializer.java | 11 ++ .../bootstrap/ApplicationShutdownManager.java | 17 ++ .../controller/AppWindowController.java | 27 ++++ .../controller/DashboardController.java | 15 ++ .../controller/StreamingController.java | 16 ++ src/main/java/vassistent/model/AppState.java | 31 ++++ .../java/vassistent/model/DatabaseEntry.java | 19 +++ .../java/vassistent/model/ProblemLevel.java | 3 + .../java/vassistent/model/RatioPoint.java | 19 +++ .../service/AnimationFileService.java | 14 ++ .../service/BinaryEventService.java | 14 ++ .../service/DataPersistenceService.java | 35 ++++- .../vassistent/service/EvaluationService.java | 19 +++ .../vassistent/service/MqttClientService.java | 41 +++++ .../service/ProcessManagerService.java | 40 +++++ .../vassistent/service/StatisticsService.java | 20 +++ src/main/java/vassistent/ui/AppWindow.java | 45 +++++- .../java/vassistent/ui/DashboardView.java | 33 ++++ .../vassistent/ui/PixelStreamingView.java | 23 +++ .../java/vassistent/ui/ProblemLevelBar.java | 28 ++++ .../java/vassistent/util/ConfigLoader.java | 12 ++ src/main/java/vassistent/util/Logger.java | 72 +++++++-- .../service/BinaryEventServiceTest.java | 20 ++- .../service/DataPersistenceServiceTest.java | 25 ++- .../service/EvaluationServiceTest.java | 17 +- .../service/StatisticsServiceTest.java | 25 ++- 29 files changed, 864 insertions(+), 21 deletions(-) create mode 100644 readme.md diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..1194368 --- /dev/null +++ b/readme.md @@ -0,0 +1,148 @@ +# Virtueller Gesundheitsassistent + +Java desktop application that receives binary health predictions via MQTT, stores them in SQLite, computes rolling risk ratios, and visualizes the result in a dashboard with an avatar streaming view. + +## Features + +- Swing desktop UI with two tabs: +- `Avatar Streaming` (embedded JCEF browser, default `http://localhost`) +- `Dashboard` (time series chart + problem level bar) +- MQTT client subscriber (`tcp://localhost:1883`) for prediction events +- SQLite persistence (`data/health.db`) +- Rolling ratio evaluation and automatic risk level classification: +- `NONE` for `< 0.5` +- `WARNING` for `>= 0.5` +- `HIGH` for `>= 0.8` +- `DISASTER` for `>= 0.9` +- Writes animation state JSON for Unreal integration +- Optional external process startup: +- Python MQTT simulator +- Unreal/Pixel Streaming scripts +- Custom async logger with file output + rotation + +## Tech Stack + +- Java 17 +- Maven +- Swing + FlatLaf +- JFreeChart +- Eclipse Paho MQTT +- SQLite JDBC +- JCEF (`jcefmaven`) +- JUnit 5 + Mockito (tests) + +## Runtime Flow + +1. App initializes look and feel and services. +2. MQTT subscriber listens on topic from config (`mqtt.topic`). +3. Incoming JSON payloads are validated in `BinaryEventService`. +4. `prediction` (`0`/`1`) is stored in SQLite. +5. `EvaluationService` computes ratio over last 20 values. +6. `AppState` updates notify UI controllers. +7. Dashboard chart and status bar refresh. +8. Animation state file is written for Unreal consumption. + +## Payload Format + +Incoming MQTT payload must be JSON like: + +```json +{ + "valid": true, + "_id": 123, + "prediction": 0 +} +``` + +## Prerequisites + +- JDK 17 +- Maven 3.9+ +- Local MQTT broker on `localhost:1883` (or adjust code/config) +- Windows environment if using bundled process scripts/paths +- Optional: Python (if MQTT simulator should be auto-started) +- Optional: Unreal + Pixel Streaming setup (path-based integration) + +## Configuration + +Main config: `src/main/resources/config/application.properties` + +- `app.mode`: current mode flag (`test`) +- `python.path`: Python executable for simulator startup +- `mqtt.topic`: subscribed topic (default `PREDICTION`) +- `mqtt_sim.enabled`: start simulator process on app startup +- `mqtt_sim.script`: simulator script path +- `unreal.enabled`: start Unreal-related processes +- `unreal.executable`: PowerShell script path for Unreal start +- `unreal.signalling_server.script`: signalling server batch path + +Logger config: `src/main/resources/config/logger.properties` + +- `logger.level` +- `logger.file.enabled` +- `logger.file` +- `logger.max.size.mb` + +## Run + +### 1) Start MQTT Broker + +Start a broker on `localhost:1883` (for example Mosquitto). + +### 2) Start the App + +```powershell +mvn clean compile +mvn org.codehaus.mojo:exec-maven-plugin:3.5.0:java -Dexec.mainClass=vassistent.App +``` + +Or run `vassistent.App` directly from IntelliJ. + +### 3) Send Test Data + +Option A: enable simulator in `application.properties`: + +- set `mqtt_sim.enabled=true` +- verify `python.path` and `mqtt_sim.script` + +Option B: publish messages manually to topic `PREDICTION`. + +## Tests + +Run: + +```powershell +mvn test +``` + +Current state in this repository on Java 17: + +- `DataPersistenceServiceTest` and `StatisticsServiceTest` execute. +- Mockito-based tests fail because dependency is `mockito-all:2.0.2-beta` (legacy CGLIB + Java module access issue on modern JDKs). + +## Project Structure + +```text +src/main/java/vassistent + App.java + bootstrap/ # wiring, context, shutdown sequencing + controller/ # Swing controllers + model/ # state + data types + service/ # MQTT, DB, stats, evaluation, processes + ui/ # App window, dashboard, streaming, widgets + util/ # config + logger + +src/main/resources + config/application.properties + config/logger.properties + scripts/mqtt_simulator.py + scripts/start_avatar.ps1 +``` + +## Important Notes + +- `AnimationFileService` currently writes to a hardcoded absolute path: +- `C:\Users\Student\Documents\Dannick\Prototyp1\Saved\animation.json` +- Unreal process handling also uses hardcoded PID/script paths. +- On app shutdown, `data/health.db` is deleted by `App.deleteDatabase()`. +- The signalling server process startup in `ProcessManagerService` is prepared but currently not launched (`pb.start()` commented). diff --git a/src/main/java/vassistent/App.java b/src/main/java/vassistent/App.java index cfb1213..09b50df 100644 --- a/src/main/java/vassistent/App.java +++ b/src/main/java/vassistent/App.java @@ -10,8 +10,16 @@ import javax.swing.*; import java.io.File; +/** + * Entry point for the virtual health assistant desktop application. + */ public class App { + /** + * Starts the desktop application, initializes dependencies, and opens the main window. + * + * @param args command-line arguments (currently unused) + */ public static void main(String[] args) { ApplicationInitializer.initLookAndFeel(); @@ -34,6 +42,9 @@ public class App { }); } + /** + * Deletes the runtime SQLite database file if it exists. + */ private static void deleteDatabase() { try { @@ -55,4 +66,4 @@ public class App { Logger.error("SYSTEM", "DB Löschung fehlgeschlagen", e); } } -} \ No newline at end of file +} diff --git a/src/main/java/vassistent/bootstrap/ApplicationContext.java b/src/main/java/vassistent/bootstrap/ApplicationContext.java index 3adaabb..032d333 100644 --- a/src/main/java/vassistent/bootstrap/ApplicationContext.java +++ b/src/main/java/vassistent/bootstrap/ApplicationContext.java @@ -3,6 +3,9 @@ package vassistent.bootstrap; import vassistent.model.AppState; import vassistent.service.*; +/** + * Lightweight container that stores shared application state and services. + */ public class ApplicationContext { private AppState appState; @@ -15,66 +18,146 @@ public class ApplicationContext { private BinaryEventService binaryEventService; private ProcessManagerService processManagerService; + /** + * Stores the shared in-memory application state instance. + * + * @param appState state container used by UI and services + */ public void setAppState(AppState appState) { this.appState = appState; } + /** + * Stores the persistence service responsible for database access. + * + * @param persistenceService configured persistence service + */ public void setPersistenceService(DataPersistenceService persistenceService) { this.persistenceService = persistenceService; } + /** + * Stores the statistics service used for ratio calculations. + * + * @param statisticsService configured statistics service + */ public void setStatisticsService(StatisticsService statisticsService) { this.statisticsService = statisticsService; } + /** + * Stores the evaluation service used to derive problem levels. + * + * @param evaluationService configured evaluation service + */ public void setEvaluationService(EvaluationService evaluationService) { this.evaluationService = evaluationService; } + /** + * Stores the animation integration service. + * + * @param unrealService configured animation file service + */ public void setAnimationFileService(AnimationFileService unrealService) { this.unrealService = unrealService; } + /** + * Stores the MQTT client service. + * + * @param mqttService configured MQTT service + */ public void setMqttService(MqttClientService mqttService) { this.mqttService = mqttService; } + /** + * Stores the binary event service handling incoming predictions. + * + * @param binaryEventService configured event service + */ public void setBinaryEventService(BinaryEventService binaryEventService) { this.binaryEventService = binaryEventService; } + /** + * Returns the shared observable application state. + * + * @return application state + */ public AppState getAppState() { return appState; } + /** + * Returns the persistence service instance. + * + * @return persistence service + */ public DataPersistenceService getPersistenceService() { return persistenceService; } + /** + * Returns the statistics service instance. + * + * @return statistics service + */ public StatisticsService getStatisticsService() { return statisticsService; } + /** + * Returns the evaluation service instance. + * + * @return evaluation service + */ public EvaluationService getEvaluationService() { return evaluationService; } + /** + * Returns the animation file service instance. + * + * @return animation file service + */ public AnimationFileService getAnimationFileService() { return unrealService; } + /** + * Returns the MQTT service instance. + * + * @return MQTT service + */ public MqttClientService getMqttService() { return mqttService; } + /** + * Returns the binary event service instance. + * + * @return binary event service + */ public BinaryEventService getBinaryEventService() { return binaryEventService; } + /** + * Returns the external process manager service. + * + * @return process manager service + */ public ProcessManagerService getProcessManagerService() { return processManagerService; } + /** + * Stores the process manager service used for external helper processes. + * + * @param processManagerService configured process manager service + */ public void setProcessManagerService(ProcessManagerService processManagerService) { this.processManagerService = processManagerService; } diff --git a/src/main/java/vassistent/bootstrap/ApplicationInitializer.java b/src/main/java/vassistent/bootstrap/ApplicationInitializer.java index 0229c20..e63a3d7 100644 --- a/src/main/java/vassistent/bootstrap/ApplicationInitializer.java +++ b/src/main/java/vassistent/bootstrap/ApplicationInitializer.java @@ -8,6 +8,9 @@ import vassistent.util.ConfigLoader; import javax.swing.*; import java.util.Properties; +/** + * Bootstraps application services, subscriptions, and UI look-and-feel. + */ public class ApplicationInitializer { private static Properties config = @@ -15,6 +18,11 @@ public class ApplicationInitializer { "config/application.properties" ); + /** + * Builds and wires the full application context including subscriptions and process startup. + * + * @return fully initialized application context + */ public static ApplicationContext initialize() { ApplicationContext context = new ApplicationContext(); @@ -62,6 +70,9 @@ public class ApplicationInitializer { return context; } + /** + * Applies the FlatLaf look-and-feel and selected UI defaults. + */ public static void initLookAndFeel() { try { UIManager.setLookAndFeel(new FlatDarkLaf()); diff --git a/src/main/java/vassistent/bootstrap/ApplicationShutdownManager.java b/src/main/java/vassistent/bootstrap/ApplicationShutdownManager.java index b396546..8eef91b 100644 --- a/src/main/java/vassistent/bootstrap/ApplicationShutdownManager.java +++ b/src/main/java/vassistent/bootstrap/ApplicationShutdownManager.java @@ -4,10 +4,18 @@ import vassistent.service.MqttClientService; import vassistent.service.ProcessManagerService; import vassistent.util.Logger; +/** + * Coordinates graceful shutdown of MQTT and managed external processes. + */ public class ApplicationShutdownManager { private final ApplicationContext context; + /** + * Creates a shutdown manager and registers a JVM shutdown hook. + * + * @param context initialized application context + */ public ApplicationShutdownManager(ApplicationContext context) { this.context = context; @@ -16,6 +24,9 @@ public class ApplicationShutdownManager { ); } + /** + * Executes shutdown steps for managed integrations. + */ public void shutdown() { Logger.info("SHUTDOWN", "Shutdown Sequencing gestartet"); @@ -24,6 +35,9 @@ public class ApplicationShutdownManager { stopExternalProcesses(); } + /** + * Disconnects from MQTT if a client service is available. + */ private void disconnectMqtt() { try { @@ -39,6 +53,9 @@ public class ApplicationShutdownManager { } } + /** + * Stops all external processes that were launched by the process manager. + */ private void stopExternalProcesses() { try { diff --git a/src/main/java/vassistent/controller/AppWindowController.java b/src/main/java/vassistent/controller/AppWindowController.java index 647c00e..05a20cc 100644 --- a/src/main/java/vassistent/controller/AppWindowController.java +++ b/src/main/java/vassistent/controller/AppWindowController.java @@ -13,6 +13,9 @@ import java.awt.*; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; +/** + * Controls the main window lifecycle and synchronizes UI elements with app state. + */ public class AppWindowController { private final ApplicationContext context; @@ -20,12 +23,20 @@ public class AppWindowController { private AppWindow window; + /** + * Creates a controller for the main application window. + * + * @param context initialized application context with required services + */ public AppWindowController(ApplicationContext context) { this.context = context; this.shutdownManager = new ApplicationShutdownManager(context); subscribeAppState(); } + /** + * Builds the window, wires child controllers, and makes the UI visible. + */ public void createAndShowWindow() { window = new AppWindow(); @@ -43,6 +54,11 @@ public class AppWindowController { StreamingController previewController = new StreamingController(pixelStreamingView); window.addWindowListener(new WindowAdapter() { + /** + * Triggers application shutdown when the window close button is pressed. + * + * @param e window event provided by Swing + */ @Override public void windowClosing(WindowEvent e) { shutdown(); @@ -52,6 +68,9 @@ public class AppWindowController { window.setVisible(true); } + /** + * Runs application shutdown sequence and exits the process. + */ private void shutdown() { shutdownManager.shutdown(); @@ -62,6 +81,9 @@ public class AppWindowController { System.exit(0); } + /** + * Registers an observer to propagate {@link AppState} changes to the UI. + */ private void subscribeAppState() { Logger.info("Controller", @@ -75,6 +97,11 @@ public class AppWindowController { "AppState Observer registriert"); } + /** + * Reacts to state updates and refreshes problem level and MQTT status labels. + * + * @param appState latest application state snapshot + */ private void onStateChanged(AppState appState) { if (window == null) { diff --git a/src/main/java/vassistent/controller/DashboardController.java b/src/main/java/vassistent/controller/DashboardController.java index afd4195..19d0215 100644 --- a/src/main/java/vassistent/controller/DashboardController.java +++ b/src/main/java/vassistent/controller/DashboardController.java @@ -9,11 +9,21 @@ import vassistent.util.Logger; import javax.swing.*; import java.util.List; +/** + * Updates the dashboard chart and level bar whenever application state changes. + */ public class DashboardController { private final StatisticsService statisticsService; private final DashboardView dashboardView; + /** + * Creates a dashboard controller and subscribes to app state changes. + * + * @param statisticsService service used to compute chart data + * @param dashboardView dashboard UI component + * @param appState observable application state + */ public DashboardController( StatisticsService statisticsService, DashboardView dashboardView, @@ -25,6 +35,11 @@ public class DashboardController { appState.addListener(this::onStateChanged); } + /** + * Recomputes dashboard content when state changes. + * + * @param state latest application state + */ private void onStateChanged(AppState state) { List points = diff --git a/src/main/java/vassistent/controller/StreamingController.java b/src/main/java/vassistent/controller/StreamingController.java index 85d5109..1aa8d58 100644 --- a/src/main/java/vassistent/controller/StreamingController.java +++ b/src/main/java/vassistent/controller/StreamingController.java @@ -2,18 +2,34 @@ package vassistent.controller; import vassistent.ui.PixelStreamingView; +/** + * Exposes basic stream view actions such as reload and focus handling. + */ public class StreamingController { private final PixelStreamingView view; + /** + * Creates a streaming controller for the given view. + * + * @param view pixel streaming view component + */ public StreamingController(PixelStreamingView view) { this.view = view; } + /** + * Loads a new streaming URL in the embedded browser. + * + * @param url stream URL to load + */ public void reloadStream(String url) { view.loadStream(url); } + /** + * Requests focus for the streaming component (used as a fullscreen shortcut hook). + */ public void toggleFullscreen() { view.requestFocus(); } diff --git a/src/main/java/vassistent/model/AppState.java b/src/main/java/vassistent/model/AppState.java index a6d8f1d..a9fb56d 100644 --- a/src/main/java/vassistent/model/AppState.java +++ b/src/main/java/vassistent/model/AppState.java @@ -4,6 +4,9 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; +/** + * Observable in-memory state shared between services and UI controllers. + */ public class AppState { private long dataVersion = 0; @@ -13,28 +16,56 @@ public class AppState { private final List> listeners = new ArrayList<>(); + /** + * Registers a state listener that is notified on relevant updates. + * + * @param listener callback receiving the current state instance + */ public void addListener(Consumer listener) { listeners.add(listener); } + /** + * Notifies all currently registered listeners. + */ private void notifyListeners() { new ArrayList<>(listeners).forEach(l -> l.accept(this)); } + /** + * Returns the current computed problem level. + * + * @return current problem level + */ public ProblemLevel getProblemLevel() { return problemLevel; } + /** + * Updates the problem level and notifies listeners. + * + * @param problemLevel new problem level + */ public void setProblemLevel(ProblemLevel problemLevel) { this.problemLevel = problemLevel; dataVersion++; notifyListeners(); } + /** + * Indicates whether MQTT is currently connected. + * + * @return {@code true} if connected, otherwise {@code false} + */ public boolean isMqttConnected() { return mqttConnected; } + /** + * Updates MQTT connection status and notifies listeners only when changed. + * + * @param mqttConnected new MQTT connection flag + */ public void setMqttConnected(boolean mqttConnected) { if (this.mqttConnected != mqttConnected) { this.mqttConnected = mqttConnected; diff --git a/src/main/java/vassistent/model/DatabaseEntry.java b/src/main/java/vassistent/model/DatabaseEntry.java index 48a0de6..1c6cb8e 100644 --- a/src/main/java/vassistent/model/DatabaseEntry.java +++ b/src/main/java/vassistent/model/DatabaseEntry.java @@ -2,21 +2,40 @@ package vassistent.model; import java.time.LocalDateTime; +/** + * Immutable timestamped value loaded from the persistence layer. + */ public class DatabaseEntry { private final LocalDateTime timestamp; private final double value; + /** + * Creates an immutable database entry model. + * + * @param timestamp event timestamp + * @param value stored binary value as numeric type + */ public DatabaseEntry(LocalDateTime timestamp, double value) { this.timestamp = timestamp; this.value = value; } + /** + * Returns the event timestamp. + * + * @return timestamp of this entry + */ public LocalDateTime getTimestamp() { return timestamp; } + /** + * Returns the stored value. + * + * @return numeric event value + */ public double getValue() { return value; } diff --git a/src/main/java/vassistent/model/ProblemLevel.java b/src/main/java/vassistent/model/ProblemLevel.java index 35fe9ec..3f2d69f 100644 --- a/src/main/java/vassistent/model/ProblemLevel.java +++ b/src/main/java/vassistent/model/ProblemLevel.java @@ -1,5 +1,8 @@ package vassistent.model; +/** + * Severity levels derived from the calculated prediction ratio. + */ public enum ProblemLevel { NONE, WARNING, diff --git a/src/main/java/vassistent/model/RatioPoint.java b/src/main/java/vassistent/model/RatioPoint.java index 9d99025..f2c57ae 100644 --- a/src/main/java/vassistent/model/RatioPoint.java +++ b/src/main/java/vassistent/model/RatioPoint.java @@ -2,21 +2,40 @@ package vassistent.model; import java.time.LocalDateTime; +/** + * Immutable chart data point containing timestamp and ratio value. + */ public class RatioPoint { private final LocalDateTime timestamp; private final double ratio; + /** + * Creates an immutable ratio point for chart rendering. + * + * @param timestamp timestamp associated with the ratio + * @param ratio ratio value in range {@code [0, 1]} + */ public RatioPoint(LocalDateTime timestamp, double ratio) { this.timestamp = timestamp; this.ratio = ratio; } + /** + * Returns the ratio timestamp. + * + * @return point timestamp + */ public LocalDateTime getTimestamp() { return timestamp; } + /** + * Returns the ratio value. + * + * @return ratio for this point + */ public double getRatio() { return ratio; } diff --git a/src/main/java/vassistent/service/AnimationFileService.java b/src/main/java/vassistent/service/AnimationFileService.java index b05ea63..ed8b2f9 100644 --- a/src/main/java/vassistent/service/AnimationFileService.java +++ b/src/main/java/vassistent/service/AnimationFileService.java @@ -10,9 +10,17 @@ import java.net.URI; import java.util.concurrent.atomic.AtomicBoolean; import jakarta.websocket.*; +/** + * Writes the current problem level as animation state JSON for Unreal integration. + */ public class AnimationFileService { private static final String PATH = "C:\\Users\\Student\\Documents\\Dannick\\Prototyp1\\Saved\\animation.json"; + /** + * Writes the mapped animation state for the given level to the configured JSON file. + * + * @param level evaluated problem level + */ public void wirteAnimationState(ProblemLevel level) { String animation = mapLevelToAnimation(level); @@ -38,6 +46,12 @@ public class AnimationFileService { } } + /** + * Maps a problem level to the animation identifier expected by Unreal. + * + * @param level evaluated problem level + * @return animation key written into the JSON payload + */ private String mapLevelToAnimation(ProblemLevel level) { return switch (level) { diff --git a/src/main/java/vassistent/service/BinaryEventService.java b/src/main/java/vassistent/service/BinaryEventService.java index 0b630a9..6950743 100644 --- a/src/main/java/vassistent/service/BinaryEventService.java +++ b/src/main/java/vassistent/service/BinaryEventService.java @@ -4,12 +4,21 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; import vassistent.util.Logger; +/** + * Validates incoming JSON payloads and forwards accepted predictions to storage and evaluation. + */ public class BinaryEventService { private final DataPersistenceService persistenceService; private final EvaluationService evaluationService; private Integer lastId = null; + /** + * Creates a binary event service with required persistence and evaluation dependencies. + * + * @param persistenceService service used to persist accepted predictions + * @param evaluationService service used to recompute risk level after inserts + */ public BinaryEventService( DataPersistenceService persistenceService, EvaluationService evaluationService @@ -18,6 +27,11 @@ public class BinaryEventService { this.evaluationService = evaluationService; } + /** + * Parses, validates, and processes an incoming MQTT payload. + * + * @param payload raw JSON message body + */ public synchronized void handlePayload(String payload) { try { diff --git a/src/main/java/vassistent/service/DataPersistenceService.java b/src/main/java/vassistent/service/DataPersistenceService.java index 206a37b..754a6a5 100644 --- a/src/main/java/vassistent/service/DataPersistenceService.java +++ b/src/main/java/vassistent/service/DataPersistenceService.java @@ -10,11 +10,17 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +/** + * Handles SQLite initialization and read/write operations for binary event data. + */ public class DataPersistenceService { private static final String DB_FOLDER = "data"; private final String DB_URL; + /** + * Creates a persistence service using the default database location. + */ public DataPersistenceService() { DB_URL = "jdbc:sqlite:data/health.db"; createDatabaseFolder(); @@ -22,12 +28,20 @@ public class DataPersistenceService { } // Für Tests + /** + * Creates a persistence service using a custom JDBC URL (primarily for tests). + * + * @param DB_URL JDBC URL to use for all database operations + */ public DataPersistenceService(String DB_URL) { this.DB_URL = DB_URL; createDatabaseFolder(); init(); } + /** + * Ensures that the local database directory exists. + */ public void createDatabaseFolder() { File folder = new File(DB_FOLDER); if (!folder.exists()) { @@ -40,6 +54,9 @@ public class DataPersistenceService { } } + /** + * Initializes the database schema if it does not exist. + */ private void init() { try ( Connection c = DriverManager.getConnection(DB_URL); Statement s = c.createStatement()) { @@ -59,6 +76,11 @@ public class DataPersistenceService { } } + /** + * Stores a binary prediction value with the current timestamp. + * + * @param value prediction value (expected {@code 0} or {@code 1}) + */ public void store(int value) { try ( Connection c = DriverManager.getConnection(DB_URL); PreparedStatement ps = c.prepareStatement( @@ -73,6 +95,12 @@ public class DataPersistenceService { } } + /** + * Loads the newest entries and returns them in chronological order. + * + * @param count maximum number of entries to return + * @return list of entries ordered from oldest to newest within the selected window + */ public List getLastEntries(int count) { List result = new ArrayList<>(); @@ -109,7 +137,12 @@ public class DataPersistenceService { return result; } + /** + * Returns the configured JDBC connection URL. + * + * @return JDBC URL used by this service + */ public String getDbUrl() { return DB_URL; } -} \ No newline at end of file +} diff --git a/src/main/java/vassistent/service/EvaluationService.java b/src/main/java/vassistent/service/EvaluationService.java index 6622965..9751020 100644 --- a/src/main/java/vassistent/service/EvaluationService.java +++ b/src/main/java/vassistent/service/EvaluationService.java @@ -4,11 +4,21 @@ package vassistent.service; import vassistent.model.AppState; import vassistent.model.ProblemLevel; +/** + * Converts statistical ratios into problem levels and triggers animation updates. + */ public class EvaluationService { private final StatisticsService statisticsService; private final AppState appState; private final AnimationFileService animationFileService; + /** + * Creates an evaluation service. + * + * @param statisticsService service used to calculate ratios + * @param appState shared application state to update + * @param animationFileService service used to propagate animation changes + */ public EvaluationService( StatisticsService statisticsService, AppState appState, @@ -20,6 +30,9 @@ public class EvaluationService { this.animationFileService = animationFileService; } + /** + * Recalculates the current ratio, updates state, and writes animation output. + */ public void evaluate() { double ratio = statisticsService.getRatio(20); @@ -29,6 +42,12 @@ public class EvaluationService { animationFileService.wirteAnimationState(level); } + /** + * Maps a ratio value to a problem level threshold. + * + * @param ratio computed ratio in the range {@code [0, 1]} + * @return derived problem level + */ private ProblemLevel calculateLevel(Double ratio) { if (ratio >= 0.9) { return ProblemLevel.DISASTER; diff --git a/src/main/java/vassistent/service/MqttClientService.java b/src/main/java/vassistent/service/MqttClientService.java index 945a336..b082b34 100644 --- a/src/main/java/vassistent/service/MqttClientService.java +++ b/src/main/java/vassistent/service/MqttClientService.java @@ -10,6 +10,9 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; +/** + * MQTT client wrapper that manages connection state, topic listeners, and callbacks. + */ public class MqttClientService implements MqttCallback { private AppState appState; @@ -21,6 +24,11 @@ public class MqttClientService implements MqttCallback { private MqttClient client; + /** + * Creates and connects an MQTT client for the application. + * + * @param appState shared state updated with connection status + */ public MqttClientService(AppState appState) { this.appState = appState; try { @@ -45,6 +53,9 @@ public class MqttClientService implements MqttCallback { } } + /** + * Disconnects the MQTT client when connected. + */ public void disconnect() { try { if (client != null && client.isConnected()) { @@ -56,6 +67,12 @@ public class MqttClientService implements MqttCallback { } } + /** + * Subscribes to a topic and registers a payload listener. + * + * @param topic MQTT topic + * @param listener callback receiving payload strings + */ public void subscribe(String topic, Consumer listener) { topicListeners.put(topic, listener); try { @@ -66,6 +83,13 @@ public class MqttClientService implements MqttCallback { } } + /** + * Publishes a message to a topic. + * + * @param topic MQTT topic + * @param message payload body + * @param qos MQTT quality-of-service level + */ public void publish(String topic, String message, int qos) { try { MqttMessage mqttMessage = @@ -83,6 +107,11 @@ public class MqttClientService implements MqttCallback { } } + /** + * MQTT callback invoked when the broker connection is lost. + * + * @param cause cause of the connection loss + */ @Override public void connectionLost(Throwable cause) { Logger.warn( @@ -93,6 +122,13 @@ public class MqttClientService implements MqttCallback { appState.setMqttConnected(false); } + /** + * MQTT callback invoked for each incoming message. + * + * @param topic topic of the received message + * @param message received MQTT message + * @throws Exception propagated by callback handling if listener execution fails + */ @Override public void messageArrived(String topic, MqttMessage message) throws Exception { String payload = @@ -115,6 +151,11 @@ public class MqttClientService implements MqttCallback { } } + /** + * MQTT callback invoked when a published message is fully delivered. + * + * @param token delivery token associated with the completed publish + */ @Override public void deliveryComplete(IMqttDeliveryToken token) { Logger.debug( diff --git a/src/main/java/vassistent/service/ProcessManagerService.java b/src/main/java/vassistent/service/ProcessManagerService.java index 289c69c..eecb695 100644 --- a/src/main/java/vassistent/service/ProcessManagerService.java +++ b/src/main/java/vassistent/service/ProcessManagerService.java @@ -8,6 +8,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Properties; +/** + * Starts and stops optional external helper processes used by the application. + */ public class ProcessManagerService { private final Properties config; @@ -16,16 +19,27 @@ public class ProcessManagerService { private Process unrealProcess; private Process unrealSignallingProcess; + /** + * Creates a process manager using runtime configuration values. + * + * @param config loaded application properties + */ public ProcessManagerService(Properties config) { this.config = config; } + /** + * Starts optional helper processes according to configuration flags. + */ public void startProcesses() { startPythonIfEnabled(); startUnrealIfEnabled(); } + /** + * Starts the Python MQTT simulator if enabled. + */ private void startPythonIfEnabled() { boolean enabled = Boolean.parseBoolean( @@ -49,6 +63,9 @@ public class ProcessManagerService { } } + /** + * Starts Unreal-related processes if enabled. + */ private void startUnrealIfEnabled() { boolean enabled = Boolean.parseBoolean(config.getProperty("unreal.enabled", "false")); @@ -65,6 +82,11 @@ public class ProcessManagerService { } } + /** + * Starts the Unreal signalling server script. + * + * @throws IOException if process startup fails + */ private void startSignallingServer() throws IOException { String script = @@ -86,6 +108,11 @@ public class ProcessManagerService { "Unreal Signalling Server gestartet" + pb.command()); } + /** + * Starts the configured Unreal executable script via PowerShell. + * + * @throws IOException if process startup fails + */ private void startUnrealEngine() throws IOException { String unrealPsScript = config.getProperty("unreal.executable"); @@ -106,6 +133,9 @@ public class ProcessManagerService { "Unreal Engine gestartet" + pb.command()); } + /** + * Stops all managed processes and attempts cleanup based on known PID files. + */ public void shutdown() { Logger.info("PROCESS", "Shutdown externe Prozesse gestartet"); @@ -125,6 +155,11 @@ public class ProcessManagerService { Logger.info("PROCESS", "Externe Prozesse beendet"); } + /** + * Terminates a process tree for a tracked process handle. + * + * @param process process handle to terminate; ignored when {@code null} + */ private void terminateProcess(Process process) { if (process == null) @@ -153,6 +188,11 @@ public class ProcessManagerService { } } + /** + * Terminates a process tree using a PID read from a file. + * + * @param file absolute path to PID file + */ private void killProcessFromPidFile(String file) { try { diff --git a/src/main/java/vassistent/service/StatisticsService.java b/src/main/java/vassistent/service/StatisticsService.java index 62b3a31..97698d6 100644 --- a/src/main/java/vassistent/service/StatisticsService.java +++ b/src/main/java/vassistent/service/StatisticsService.java @@ -9,13 +9,27 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +/** + * Computes ratios and time-series aggregates from persisted binary events. + */ public class StatisticsService { private final DataPersistenceService persistenceService; + /** + * Creates a statistics service for persisted event data. + * + * @param persistenceService persistence service to query raw events + */ public StatisticsService(DataPersistenceService persistenceService) { this.persistenceService = persistenceService; } + /** + * Computes the average of the newest {@code lastN} binary values. + * + * @param lastN number of newest entries to include + * @return ratio in range {@code [0, 1]} or {@code 0.0} when unavailable + */ public double getRatio(int lastN) { String query = """ SELECT AVG(value) @@ -41,6 +55,12 @@ public class StatisticsService { return 0.0; } + /** + * Computes rolling averages for chart rendering. + * + * @param windowSize number of previous entries considered for each point + * @return chronological list of ratio points + */ public List getLastNAverages(int windowSize) { List entries = persistenceService.getLastEntries(windowSize + 1); diff --git a/src/main/java/vassistent/ui/AppWindow.java b/src/main/java/vassistent/ui/AppWindow.java index 2ff14f5..f9d434c 100644 --- a/src/main/java/vassistent/ui/AppWindow.java +++ b/src/main/java/vassistent/ui/AppWindow.java @@ -7,6 +7,9 @@ import javax.swing.*; import javax.swing.border.Border; import java.awt.*; +/** + * Main Swing frame containing streaming and dashboard tabs plus status indicators. + */ public class AppWindow extends JFrame { private PixelStreamingView streamingView; @@ -16,6 +19,9 @@ public class AppWindow extends JFrame { private JLabel mqttStatusLabel; private JLabel problemLevelLabel; + /** + * Creates and initializes the main application window and its child views. + */ public AppWindow() { setTitle("Virtueller Gesundheitsassistent"); @@ -42,8 +48,11 @@ public class AppWindow extends JFrame { UIManager.put("TabbedPane.font", uiFont); } - // ---------------- Tabs ---------------- - + /** + * Creates the tabbed pane that hosts streaming and dashboard views. + * + * @return configured tabbed pane + */ private JTabbedPane createTabPane() { JTabbedPane tabs = new JTabbedPane(); @@ -58,8 +67,11 @@ public class AppWindow extends JFrame { return tabs; } - // ---------------- Status Bar ---------------- - + /** + * Creates the status bar with MQTT and problem-level indicators. + * + * @return configured status bar panel + */ private JPanel createStatusBar() { JPanel statusBar = new JPanel(new FlowLayout(FlowLayout.LEFT,15,5)); @@ -79,23 +91,48 @@ public class AppWindow extends JFrame { return statusBar; } + /** + * Updates the MQTT connection status label. + * + * @param connected whether MQTT is currently connected + */ public void updateMqttStatus(boolean connected) { mqttStatusLabel.setText("MQTT: " + (connected ? "Connected" : "Disconnected")); } + /** + * Updates the currently displayed problem level label. + * + * @param level problem level name to display + */ public void updateProblemLevel(String level) { problemLevelLabel.setText("Problem: " + level); } + /** + * Returns the dashboard view component. + * + * @return dashboard view + */ public DashboardView getDashboardView() { return dashboardView; } + /** + * Returns the pixel streaming view component. + * + * @return streaming view + */ public PixelStreamingView getStreamingView() { return streamingView; } + /** + * Returns the main tabbed pane. + * + * @return tab container + */ public JTabbedPane getTabs() { return tabs; } diff --git a/src/main/java/vassistent/ui/DashboardView.java b/src/main/java/vassistent/ui/DashboardView.java index c764c99..4ab9c42 100644 --- a/src/main/java/vassistent/ui/DashboardView.java +++ b/src/main/java/vassistent/ui/DashboardView.java @@ -27,6 +27,9 @@ import java.util.Arrays; import java.util.Date; import java.util.List; +/** + * Dashboard panel that renders the ratio history chart and current level indicator. + */ public class DashboardView extends JPanel { private TimeSeries series; @@ -35,6 +38,9 @@ public class DashboardView extends JPanel { private JButton reloadPixelStreamingViewButton; private JButton openFullscreenButton; + /** + * Creates the dashboard panel with problem-level bar and time-series chart. + */ public DashboardView() { setLayout(new BorderLayout()); setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); @@ -118,6 +124,11 @@ public class DashboardView extends JPanel { add(card, BorderLayout.CENTER); } + /** + * Replaces chart data with the given ratio points. + * + * @param points ratio points ordered by time + */ public void updateChart(List points) { if (points == null || points.isEmpty()) return; @@ -139,18 +150,40 @@ public class DashboardView extends JPanel { } } + /** + * Updates the visual problem level bar using the latest ratio. + * + * @param ratio latest computed ratio + */ public void updateProblemLevel(double ratio) { levelBar.setRatio(ratio); } + /** + * Returns the button intended to reload the pixel streaming view. + * + * @return reload button (may be {@code null} until implemented) + */ public JButton getReloadPixelStreamingViewButton() { return reloadPixelStreamingViewButton; } + /** + * Returns the button intended to open fullscreen mode. + * + * @return fullscreen button (may be {@code null} until implemented) + */ public JButton getOpenFullscreenButton() { return openFullscreenButton; } + /** + * Adds a horizontal threshold marker with label to the chart. + * + * @param plot target plot + * @param value y-axis value for the threshold + * @param label label rendered near the marker + */ private void addThresholdLine(XYPlot plot, double value, String label) { ValueMarker marker = new ValueMarker(value); diff --git a/src/main/java/vassistent/ui/PixelStreamingView.java b/src/main/java/vassistent/ui/PixelStreamingView.java index efb99ec..5b884fb 100644 --- a/src/main/java/vassistent/ui/PixelStreamingView.java +++ b/src/main/java/vassistent/ui/PixelStreamingView.java @@ -11,6 +11,9 @@ import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +/** + * Embedded JCEF browser panel used to display the avatar pixel stream. + */ public class PixelStreamingView extends JPanel { private static final long serialVersionUID = -5570653778104813836L; @@ -19,10 +22,22 @@ public class PixelStreamingView extends JPanel { private final CefBrowser browser; private final Component browserUI_; + /** + * Creates an embedded Chromium browser panel. + * + * @param startURL initial URL to load + * @param useOSR whether off-screen rendering is enabled + * @param isTransparent whether transparent rendering is enabled + */ public PixelStreamingView(String startURL, boolean useOSR, boolean isTransparent) { super(new BorderLayout()); CefApp.addAppHandler(new CefAppHandlerAdapter(null) { + /** + * Handles JCEF application state transitions. + * + * @param state latest JCEF lifecycle state + */ @Override public void stateHasChanged(CefApp.CefAppState state) { if (state == CefApp.CefAppState.TERMINATED){ @@ -43,12 +58,20 @@ public class PixelStreamingView extends JPanel { add(browserUI_, BorderLayout.CENTER); } + /** + * Disposes browser resources held by JCEF objects. + */ public void dispose() { if (browser != null) browser.close(true); if (client != null) client.dispose(); if (cefApp != null) cefApp.dispose(); } + /** + * Loads the given stream URL in the embedded browser. + * + * @param url stream URL + */ public void loadStream(String url) { browser.loadURL(url); } diff --git a/src/main/java/vassistent/ui/ProblemLevelBar.java b/src/main/java/vassistent/ui/ProblemLevelBar.java index 4a409d0..ac23172 100644 --- a/src/main/java/vassistent/ui/ProblemLevelBar.java +++ b/src/main/java/vassistent/ui/ProblemLevelBar.java @@ -5,23 +5,40 @@ import vassistent.model.ProblemLevel; import javax.swing.*; import java.awt.*; +/** + * Custom progress-style component that visualizes ratio and mapped problem level. + */ public class ProblemLevelBar extends JPanel { private double ratio = 0.0; private ProblemLevel level = ProblemLevel.NONE; + /** + * Creates the level bar component with preferred sizing and transparent background. + */ public ProblemLevelBar() { setPreferredSize(new Dimension(100, 50)); setOpaque(false); setBorder(BorderFactory.createEmptyBorder(5,5,5,5)); } + /** + * Updates the displayed ratio and derived problem level. + * + * @param ratio ratio value, clamped to {@code [0, 1]} + */ public void setRatio(double ratio) { this.ratio = Math.max(0.0, Math.min(1.0, ratio)); this.level = calculateLevel(this.ratio); repaint(); } + /** + * Maps a ratio to a problem level threshold. + * + * @param ratio ratio value in {@code [0, 1]} + * @return mapped problem level + */ private ProblemLevel calculateLevel(Double ratio) { if (ratio >= 0.9) { return ProblemLevel.DISASTER; @@ -34,6 +51,11 @@ public class ProblemLevelBar extends JPanel { } } + /** + * Paints the bar background, filled level area, and textual status. + * + * @param g graphics context provided by Swing + */ @Override protected void paintComponent(Graphics g) { super.paintComponent(g); @@ -87,6 +109,12 @@ public class ProblemLevelBar extends JPanel { ); } + /** + * Returns a base color for the given problem level. + * + * @param level problem level + * @return display color for the level + */ private Color getColorForLevel(ProblemLevel level) { switch (level) { case DISASTER: diff --git a/src/main/java/vassistent/util/ConfigLoader.java b/src/main/java/vassistent/util/ConfigLoader.java index fe2d9c9..4c8369c 100644 --- a/src/main/java/vassistent/util/ConfigLoader.java +++ b/src/main/java/vassistent/util/ConfigLoader.java @@ -3,10 +3,22 @@ package vassistent.util; import java.io.InputStream; import java.util.Properties; +/** + * Utility for loading classpath-based properties files. + */ public class ConfigLoader { + /** + * Creates a config loader instance. + */ public ConfigLoader() {} + /** + * Loads a properties resource from the application classpath. + * + * @param path classpath-relative resource path + * @return loaded properties, or empty properties when resource is missing + */ public static Properties loadProperties(String path) { Properties props = new Properties(); diff --git a/src/main/java/vassistent/util/Logger.java b/src/main/java/vassistent/util/Logger.java index 40fd56f..41e7af2 100644 --- a/src/main/java/vassistent/util/Logger.java +++ b/src/main/java/vassistent/util/Logger.java @@ -7,8 +7,14 @@ import java.util.Properties; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; +/** + * Asynchronous application logger with level filtering and file rotation support. + */ public class Logger { + /** + * Log severity levels in ascending order. + */ public enum Level { DEBUG, INFO, WARN, ERROR } @@ -30,10 +36,14 @@ public class Logger { startLogWriterThread(); } + /** + * Utility class constructor. + */ private Logger() {} - // ================= CONFIG ================= - + /** + * Loads logger configuration from {@code config/logger.properties}. + */ private static void loadConfig() { try (InputStream is = Logger.class.getClassLoader() .getResourceAsStream("config/logger.properties")) { @@ -63,30 +73,65 @@ public class Logger { } } - // ================= API ================= - + /** + * Writes a debug-level log entry. + * + * @param source logical source component + * @param message log message + */ public static void debug(String source, String message) { log(Level.DEBUG, source, message, null); } + /** + * Writes an info-level log entry. + * + * @param source logical source component + * @param message log message + */ public static void info(String source, String message) { log(Level.INFO, source, message, null); } + /** + * Writes a warning-level log entry. + * + * @param source logical source component + * @param message log message + */ public static void warn(String source, String message) { log(Level.WARN, source, message, null); } + /** + * Writes an error-level log entry without stack trace. + * + * @param source logical source component + * @param message log message + */ public static void error(String source, String message) { log(Level.ERROR, source, message, null); } + /** + * Writes an error-level log entry with stack trace. + * + * @param source logical source component + * @param message log message + * @param t associated throwable + */ public static void error(String source, String message, Throwable t) { log(Level.ERROR, source, message, t); } - // ================= CORE ================= - + /** + * Enqueues a log entry for asynchronous output if the level passes filtering. + * + * @param level severity level + * @param source logical source component + * @param message log message + * @param throwable optional throwable to append + */ public static void log(Level level, String source, String message, Throwable throwable) { if (level.ordinal() < currentLevel.ordinal()) return; @@ -108,8 +153,9 @@ public class Logger { logQueue.offer(sb.toString()); } - // ================= BACKGROUND WRITER ================= - + /** + * Starts the daemon thread that drains the log queue and writes outputs. + */ private static void startLogWriterThread() { Thread writerThread = new Thread(() -> { @@ -145,8 +191,9 @@ public class Logger { writerThread.start(); } - // ================= ROTATION ================= - + /** + * Rotates the active log file when size exceeds configured threshold. + */ private static void rotateIfNeeded() { try { @@ -178,6 +225,9 @@ public class Logger { } } + /** + * Performs a best-effort flush of queued messages during shutdown. + */ public static void shutdown() { System.out.println("Logger Shutdown gestartet"); @@ -196,4 +246,4 @@ public class Logger { System.err.println("Logger Shutdown Fehler: " + e.getMessage()); } } -} \ No newline at end of file +} diff --git a/src/test/java/vassistent/service/BinaryEventServiceTest.java b/src/test/java/vassistent/service/BinaryEventServiceTest.java index 9ee95eb..8286a55 100644 --- a/src/test/java/vassistent/service/BinaryEventServiceTest.java +++ b/src/test/java/vassistent/service/BinaryEventServiceTest.java @@ -6,12 +6,18 @@ import org.junit.jupiter.api.Test; import static org.mockito.Mockito.*; +/** + * Tests for payload validation and processing behavior in {@link BinaryEventService}. + */ class BinaryEventServiceTest { private DataPersistenceService persistenceService; private EvaluationService evaluationService; private BinaryEventService binaryEventService; + /** + * Creates mocked dependencies and the service under test. + */ @BeforeEach void setUp() { persistenceService = mock(DataPersistenceService.class); @@ -19,6 +25,9 @@ class BinaryEventServiceTest { binaryEventService = new BinaryEventService(persistenceService, evaluationService); } + /** + * Clears references after each test run. + */ @AfterEach void tearDown() { persistenceService = null; @@ -26,6 +35,9 @@ class BinaryEventServiceTest { binaryEventService = null; } + /** + * Verifies that a valid payload triggers persistence and evaluation. + */ @Test void handlePayloadValid() { binaryEventService.handlePayload("1"); @@ -33,6 +45,9 @@ class BinaryEventServiceTest { verify(evaluationService).evaluate(); } + /** + * Verifies that an invalid numeric payload value is ignored. + */ @Test void handlePayloadInvalidValue() { binaryEventService.handlePayload("5"); @@ -40,10 +55,13 @@ class BinaryEventServiceTest { verify(evaluationService, never()).evaluate(); } + /** + * Verifies that a non-numeric payload does not trigger downstream actions. + */ @Test void handlePayloadNonNumeric() { binaryEventService.handlePayload("abc"); verify(persistenceService, never()).store(anyInt()); verify(evaluationService, never()).evaluate(); } -} \ No newline at end of file +} diff --git a/src/test/java/vassistent/service/DataPersistenceServiceTest.java b/src/test/java/vassistent/service/DataPersistenceServiceTest.java index 1053f61..91f9512 100644 --- a/src/test/java/vassistent/service/DataPersistenceServiceTest.java +++ b/src/test/java/vassistent/service/DataPersistenceServiceTest.java @@ -11,23 +11,35 @@ import java.sql.Statement; import static org.junit.jupiter.api.Assertions.*; +/** + * Tests basic database initialization and persistence operations. + */ class DataPersistenceServiceTest { private DataPersistenceService service; private static final String TEST_DB = "jdbc:sqlite:data/test_health.db"; private static final String DB_FILE = "data/test_health.db"; + /** + * Initializes a persistence service backed by a test database. + */ @BeforeEach void setup() { service = new DataPersistenceService(TEST_DB); } + /** + * Removes test database artifacts after each test. + */ @AfterEach void cleanup() { deleteTestDatabase(); } + /** + * Deletes the test database file when present. + */ private void deleteTestDatabase() { File db = new File(DB_FILE); if (db.exists()) { @@ -35,6 +47,9 @@ class DataPersistenceServiceTest { } } + /** + * Verifies that the database folder exists after service initialization. + */ @org.junit.jupiter.api.Test void createDatabaseFolder() { File folder = new File("data"); @@ -42,6 +57,11 @@ class DataPersistenceServiceTest { assertTrue(folder.exists(), "Datenbankordner sollte existieren"); } + /** + * Verifies that storing a value inserts at least one row. + * + * @throws Exception if direct JDBC validation fails + */ @org.junit.jupiter.api.Test void store() throws Exception { service.store(1); @@ -59,8 +79,11 @@ class DataPersistenceServiceTest { } } + /** + * Verifies that the service returns its configured JDBC URL. + */ @org.junit.jupiter.api.Test void getDbUrl() { assertEquals(TEST_DB, service.getDbUrl()); } -} \ No newline at end of file +} diff --git a/src/test/java/vassistent/service/EvaluationServiceTest.java b/src/test/java/vassistent/service/EvaluationServiceTest.java index 07b4f5d..4141324 100644 --- a/src/test/java/vassistent/service/EvaluationServiceTest.java +++ b/src/test/java/vassistent/service/EvaluationServiceTest.java @@ -8,6 +8,9 @@ import vassistent.model.ProblemLevel; import static org.mockito.Mockito.*; +/** + * Tests mapping of computed ratios to {@link ProblemLevel} values. + */ class EvaluationServiceTest { private StatisticsService statisticsService; @@ -15,6 +18,9 @@ class EvaluationServiceTest { private AnimationFileService unrealService; private EvaluationService evaluationService; + /** + * Creates mocks and initializes the evaluation service under test. + */ @BeforeEach void setUp() { statisticsService = mock(StatisticsService.class); @@ -23,6 +29,9 @@ class EvaluationServiceTest { evaluationService = new EvaluationService(statisticsService, appState, unrealService); } + /** + * Clears test references. + */ @org.junit.jupiter.api.Test void tearDown() { statisticsService = null; @@ -31,6 +40,9 @@ class EvaluationServiceTest { evaluationService = null; } + /** + * Verifies that high ratios map to {@link ProblemLevel#DISASTER}. + */ @org.junit.jupiter.api.Test void testEvaluateDisasterLevel() { when(statisticsService.getRatio(anyInt())).thenReturn(0.95); @@ -39,6 +51,9 @@ class EvaluationServiceTest { //verify(unrealService).speak("DISASTER"); } + /** + * Verifies that low ratios keep the state at {@link ProblemLevel#NONE}. + */ @Test void testEvaluateNoChange() { appState.setProblemLevel(ProblemLevel.NONE); @@ -46,4 +61,4 @@ class EvaluationServiceTest { evaluationService.evaluate(); //verify(unrealService, never()).speak(anyString()); } -} \ No newline at end of file +} diff --git a/src/test/java/vassistent/service/StatisticsServiceTest.java b/src/test/java/vassistent/service/StatisticsServiceTest.java index fcbb1a4..81e65ae 100644 --- a/src/test/java/vassistent/service/StatisticsServiceTest.java +++ b/src/test/java/vassistent/service/StatisticsServiceTest.java @@ -9,6 +9,9 @@ import java.sql.SQLException; import static org.junit.jupiter.api.Assertions.*; +/** + * Tests ratio calculations over persisted binary events. + */ class StatisticsServiceTest { private String DB_URL; @@ -16,6 +19,9 @@ class StatisticsServiceTest { private StatisticsService statisticsService; private File tempDbFile; + /** + * Creates an isolated temporary SQLite database for each test. + */ @BeforeEach void setUp() { try { @@ -29,6 +35,9 @@ class StatisticsServiceTest { statisticsService = new StatisticsService(persistenceService); } + /** + * Deletes the temporary test database file. + */ @AfterEach void tearDown() { if (tempDbFile != null && tempDbFile.exists()) { @@ -36,12 +45,20 @@ class StatisticsServiceTest { } } + /** + * Verifies ratio behavior when no events are available. + */ @Test void testGetRatioNoData() { double ratio = statisticsService.getRatio(10); assertEquals(0.0, ratio, "Ratio sollte 0 sein, wenn keine Daten vorhanden"); } + /** + * Verifies ratio behavior with exactly one stored value. + * + * @throws SQLException retained for compatibility with current method signature + */ @Test void testGetRatioSingleValue() throws SQLException { persistenceService.store(1); @@ -50,6 +67,9 @@ class StatisticsServiceTest { assertEquals(1.0, ratio, "Ratio sollte 1 sein, wenn nur 1 gespeichert wurde"); } + /** + * Verifies ratio behavior across multiple stored values. + */ @Test void testGetRatioMultipleValues() { persistenceService.store(1); @@ -60,6 +80,9 @@ class StatisticsServiceTest { assertEquals((1 + 0 + 1) / 3.0, ratio, 0.0001, "Ratio sollte Durchschnitt der letzten Werte sein"); } + /** + * Verifies that ratio calculation only considers the latest N values. + */ @Test void testGetRatioLimitLastN() { persistenceService.store(1); @@ -71,4 +94,4 @@ class StatisticsServiceTest { double ratio = statisticsService.getRatio(3); assertEquals((1 + 0 + 0) / 3.0, ratio, 0.0001, "Ratio sollte Durchschnitt der letzten Werte sein"); } -} \ No newline at end of file +}