JavaDoc comments added
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<RatioPoint> points =
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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<Consumer<AppState>> 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<AppState> 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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package vassistent.model;
|
||||
|
||||
/**
|
||||
* Severity levels derived from the calculated prediction ratio.
|
||||
*/
|
||||
public enum ProblemLevel {
|
||||
NONE,
|
||||
WARNING,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<DatabaseEntry> getLastEntries(int count) {
|
||||
List<DatabaseEntry> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<String> 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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<RatioPoint> getLastNAverages(int windowSize) {
|
||||
|
||||
List<DatabaseEntry> entries = persistenceService.getLastEntries(windowSize + 1);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<RatioPoint> 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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user