Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
target/
/.idea/
.env
Empty file modified mvnw
100644 → 100755
Empty file.
67 changes: 36 additions & 31 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,65 +4,70 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.example</groupId>
<artifactId>javafx</artifactId>
<groupId>org.example</groupId>
<artifactId>JavaFXChatApp</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.release>25</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.jupiter.version>6.0.0</junit.jupiter.version>
<assertj.core.version>3.27.6</assertj.core.version>
<mockito.version>5.20.0</mockito.version>
<javafx.version>25</javafx.version>
<junit.jupiter.version>5.13.4</junit.jupiter.version>
</properties>

<dependencies>
<!-- JavaFX -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>25</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj.core.version}</version>
<scope>test</scope>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>25</version>
</dependency>

<!-- Dotenv -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
<groupId>io.github.cdimascio</groupId>
<artifactId>dotenv-java</artifactId>
<version>3.2.0</version>
</dependency>

<!-- Jackson for JSON parsing -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>

<!-- JUnit for testing -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
Comment on lines +7 to 51
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify dependency and plugin versions (Java 25, JavaFX 25, JUnit 5.13.4, Surefire 3.2.5)

The structure of the POM looks good and aligns with the new features (JavaFX, dotenv, Jackson, JUnit 5 + Surefire). The main thing to double‑check is that the chosen versions actually exist in your environment and match the JDK you’re using:

  • maven.compiler.release = 25 and JavaFX 25 → ensure you’re really building against a JDK/JavaFX SDK that supports this combo.
  • junit.jupiter.version = 5.13.4 → confirm this version is published and matches the Surefire version.
  • maven-surefire-plugin = 3.2.5 with org.junit.jupiter:junit-jupiter → in general this pairing should work, but still worth validating with mvn test.

You can quickly verify by running a dependency resolution + tests locally and checking for version resolution errors.

Also applies to: 55-71


🏁 Script executed:

#!/bin/bash
# From project root: verify dependencies resolve and JUnit tests run.

set -e

echo "==> Resolving dependencies"
mvn -q -DskipTests dependency:resolve

echo "==> Running tests"
mvn -q test

Length of output: 68


🏁 Script executed:

#!/bin/bash
# Verify versions exist by checking Maven Central directly (no Maven binary needed).
# Use curl to query Maven Central metadata for each dependency.

echo "==> Checking Java/JDK 25 release status..."
curl -s "https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter/5.13.4" | grep -o "5.13.4" | head -1 || echo "JUnit 5.13.4 not found"

echo ""
echo "==> Checking JavaFX 25..."
curl -s "https://mvnrepository.com/artifact/org.openjfx/javafx-controls/25" | grep -o "25" | head -1 || echo "JavaFX 25 not found"

echo ""
echo "==> Checking Maven Surefire 3.2.5..."
curl -s "https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-surefire-plugin/3.2.5" | grep -o "3.2.5" | head -1 || echo "Surefire 3.2.5 not found"

Length of output: 123


🌐 Web query:

Java 25 release date and availability

💡 Result:

Java 25 (JDK 25) reached general availability on September 16, 2025. Downloads/ builds are available from Oracle (Oracle JDK), the OpenJDK reference builds (jdk.java.net/25), and major vendors (e.g., Microsoft Build of OpenJDK). [1][2][3][4]

Sources:

  • Oracle announcement / blog (GA announcement). [1]
  • OpenJDK announce (jdk.java.net/25 GA notice). [2]
  • Oracle press/release notes for JDK 25. [3]
  • Microsoft Build of OpenJDK 25 availability. [4]

🌐 Web query:

JavaFX 25 release availability

💡 Result:

JavaFX 25 is generally available. The GA release (JavaFX 25) was published September 15, 2025, with binaries (SDK/jmods) downloadable from the official JavaFX page (jdk.java.net/javafx25) and openjfx.io. A subsequent 25.0.1 update was released October 21, 2025. [1][2][3]

Sources:

  • JavaFX 25 / 25.0.1 downloads and release page. [1]
  • OpenJFX mailing announcement (GA release info). [2]
  • Gluon OpenJFX 25 release notes (lists 25 and 25.0.1 release dates). [3]

🌐 Web query:

JUnit 5.13.4 published Maven Central

💡 Result:

Yes. JUnit 5.13.4 was released and published to Maven Central on July 21, 2025. [1][2]

Sources:
[1] JUnit 5.13.4 release notes (July 21, 2025).
[2] Maven Central (org/junit/jupiter/junit-jupiter/5.13.4) listing (artifacts dated July 21, 2025).


🌐 Web query:

Maven Surefire plugin 3.2.5 compatibility JUnit 5

💡 Result:

Short answer: Yes — Maven Surefire Plugin 3.2.5 natively supports running JUnit 5 (the JUnit Platform / Jupiter) via the built-in junit-platform provider. [1][2]

Caveat: 3.2.5 ships with/controls JUnit Platform launcher/engine versions (it pulls junit-platform-launcher / engine 1.9.3), so it can conflict with newer JUnit Jupiter/Platform releases (e.g. JUnit 5.10+ / platform 1.10.x). If you use a newer JUnit 5 (Jupiter) version, either:

  • override the platform/engine versions used by Surefire (add explicit junit-platform-engine/junit-platform-launcher dependencies or use dependencyManagement), or
  • upgrade to a newer Maven Surefire release that aligns with the JUnit Platform version you need.

References:

  • Surefire JUnit Platform usage & provider (3.2.5 docs). [1]
  • Surefire 3.2.5 provider dependency list (shows junit-platform-launcher 1.9.3 / junit-jupiter-engine used in tests). [2]
  • Reported incompatibility / managed-version conflict between Surefire’s managed platform (1.9.3) and newer JUnit Platform (1.10.x). [3]

If you want, tell me which JUnit Jupiter/platform version you're using and I’ll give the exact pom.xml changes to make it work.


Critical: Maven Surefire 3.2.5 incompatible with JUnit 5.13.4

All specified versions (Java 25, JavaFX 25, JUnit 5.13.4, Surefire 3.2.5) are published and available. However, verification uncovered a compatibility issue: Maven Surefire Plugin 3.2.5 ships with JUnit Platform 1.9.3, which can conflict with newer JUnit Jupiter/Platform releases like JUnit 5.10+ that require Platform 1.10.x. JUnit 5.13.4 (released July 2025) falls into this newer range and will likely trigger version conflicts during builds or test execution.

Fix: Upgrade maven-surefire-plugin to version 3.3.x or later, or downgrade junit.jupiter.version to 5.9.x/5.10.x that aligns with Platform 1.9.3. After resolving, run mvn test locally to confirm compatibility.

🤖 Prompt for AI Agents
In pom.xml around lines 7 to 51, the project declares JUnit Jupiter 5.13.4 which
is incompatible with Maven Surefire Plugin 3.2.5 (bundled with JUnit Platform
1.9.3); update the build to use a compatible Surefire or JUnit version: either
upgrade maven-surefire-plugin to 3.3.x or later (add/update the plugin under
<build><plugins>) so it uses JUnit Platform 1.10.x, or change
junit.jupiter.version to a 5.9.x/5.10.x release that matches Platform 1.9.3;
after changing the pom, run mvn test locally to verify tests execute without
platform/version conflicts.


<build>
<plugins>
<!-- JavaFX plugin -->
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<configuration>
<mainClass>com.example.HelloFX</mainClass>
<options>
<option>--enable-native-access=javafx.graphics</option>
</options>
<launcher>javafx</launcher>
<stripDebug>true</stripDebug>
<noHeaderFiles>true</noHeaderFiles>
<noManPages>true</noManPages>
</configuration>
</plugin>

<!-- Surefire för JUnit-körning -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
</plugin>
</plugins>
</build>
</project>
54 changes: 45 additions & 9 deletions src/main/java/com/example/HelloController.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,58 @@
package com.example;

import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.stage.FileChooser;
import javafx.scene.control.Button;

import java.io.File;

/**
* Controller layer: mediates between the view (FXML) and the model.
*/
public class HelloController {

private final HelloModel model = new HelloModel();
@FXML
private TextArea chatArea;

@FXML
private TextField inputField;

@FXML
private Button sendButton;

@FXML
private Label messageLabel;
private Button attachButton;
Comment on lines 23 to +24
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove unused attachButton field.

The attachButton field is declared but never used in the code. The attach functionality is triggered directly from the FXML onAction handler.

-    @FXML
-    private Button attachButton;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@FXML
private Label messageLabel;
private Button attachButton;
🤖 Prompt for AI Agents
In src/main/java/com/example/HelloController.java around lines 23 to 24, the
private field "attachButton" is declared but never referenced; remove the unused
field declaration from the class, and after removal ensure there are no
remaining unused imports related to JavaFX controls (clean up imports) and
rebuild to verify no references remain in code—do not change the FXML onAction
handler (it can stay as-is).


private HelloModel model;

@FXML
public void initialize() {
// Läser BACKEND_URL och TOPIC från .env via HelloModel
model = new HelloModel();

// Lyssna på inkommande meddelanden
model.listen(msg -> {
Platform.runLater(() -> chatArea.appendText(msg + "\n"));
System.out.println("📩 Mottaget: " + msg);
});
}
Comment on lines +28 to +38
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add error handling for model initialization.

The HelloModel constructor may throw IllegalStateException if environment variables are missing (as shown in the test). This should be handled gracefully with user feedback rather than allowing the application to fail silently.

     @FXML
     public void initialize() {
-        // Läser BACKEND_URL och TOPIC från .env via HelloModel
-        model = new HelloModel();
-
-        // Lyssna på inkommande meddelanden
-        model.listen(msg -> {
-            Platform.runLater(() -> chatArea.appendText(msg + "\n"));
-            System.out.println("📩 Mottaget: " + msg);
-        });
+        try {
+            // Läser BACKEND_URL och TOPIC från .env via HelloModel
+            model = new HelloModel();
+
+            // Lyssna på inkommande meddelanden
+            model.listen(msg -> {
+                Platform.runLater(() -> chatArea.appendText(msg + "\n"));
+                System.out.println("📩 Mottaget: " + msg);
+            });
+        } catch (IllegalStateException e) {
+            Platform.runLater(() -> {
+                chatArea.appendText("⚠️ Fel: Kunde inte initiera chat. Kontrollera .env-filen.\n");
+                chatArea.appendText("Meddelande: " + e.getMessage() + "\n");
+            });
+            sendButton.setDisable(true);
+        }
     }
🤖 Prompt for AI Agents
In src/main/java/com/example/HelloController.java around lines 28 to 38, the
HelloModel() constructor can throw IllegalStateException when env vars are
missing; wrap the model = new HelloModel() call in a try-catch for
IllegalStateException, and in the catch create user-visible feedback (e.g., show
a JavaFX Alert with a clear message about missing BACKEND_URL/TOPIC), log the
exception details (System.err or logger), and disable or hide UI controls that
depend on the model to prevent further errors; only call model.listen(...) when
initialization succeeds.


@FXML
protected void onSendButtonClick() {
String message = inputField.getText().trim();
if (!message.isEmpty()) {
model.sendMessage(message);
inputField.clear();
}
}
Comment on lines +40 to +47
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add error handling for message sending failures.

The sendMessage call may fail due to network issues, but errors are not handled or displayed to the user. Consider wrapping in try-catch and providing user feedback.

     @FXML
     protected void onSendButtonClick() {
         String message = inputField.getText().trim();
         if (!message.isEmpty()) {
-            model.sendMessage(message);
-            inputField.clear();
+            try {
+                model.sendMessage(message);
+                inputField.clear();
+            } catch (Exception e) {
+                Platform.runLater(() -> 
+                    chatArea.appendText("⚠️ Kunde inte skicka meddelande: " + e.getMessage() + "\n")
+                );
+            }
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@FXML
protected void onSendButtonClick() {
String message = inputField.getText().trim();
if (!message.isEmpty()) {
model.sendMessage(message);
inputField.clear();
}
}
@FXML
protected void onSendButtonClick() {
String message = inputField.getText().trim();
if (!message.isEmpty()) {
try {
model.sendMessage(message);
inputField.clear();
} catch (Exception e) {
Platform.runLater(() ->
chatArea.appendText("⚠️ Kunde inte skicka meddelande: " + e.getMessage() + "\n")
);
}
}
}
🤖 Prompt for AI Agents
In src/main/java/com/example/HelloController.java around lines 40 to 47, the
call to model.sendMessage(...) is unprotected: wrap the sendMessage call in a
try-catch (catch Exception) so failures are handled, log the exception with your
logger, and show user feedback by displaying an Alert or status label update on
the JavaFX Application Thread (use Platform.runLater if needed); still clear the
input only on success (or re-enable UI appropriately) so the user can retry if
sending fails.


@FXML
private void initialize() {
if (messageLabel != null) {
messageLabel.setText(model.getGreeting());
protected void onAttachFileClick() {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Välj en fil att skicka");
File file = fileChooser.showOpenDialog(chatArea.getScene().getWindow());
if (file != null) {
model.sendFile(file);
}
}
Comment on lines 49 to 57
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add error handling for file sending failures.

Similar to message sending, file operations may fail. Add error handling and user feedback.

     @FXML
     protected void onAttachFileClick() {
         FileChooser fileChooser = new FileChooser();
         fileChooser.setTitle("Välj en fil att skicka");
         File file = fileChooser.showOpenDialog(chatArea.getScene().getWindow());
         if (file != null) {
-            model.sendFile(file);
+            try {
+                model.sendFile(file);
+                Platform.runLater(() -> 
+                    chatArea.appendText("📎 Skickade fil: " + file.getName() + "\n")
+                );
+            } catch (Exception e) {
+                Platform.runLater(() -> 
+                    chatArea.appendText("⚠️ Kunde inte skicka fil: " + e.getMessage() + "\n")
+                );
+            }
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@FXML
private void initialize() {
if (messageLabel != null) {
messageLabel.setText(model.getGreeting());
protected void onAttachFileClick() {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Välj en fil att skicka");
File file = fileChooser.showOpenDialog(chatArea.getScene().getWindow());
if (file != null) {
model.sendFile(file);
}
}
@FXML
protected void onAttachFileClick() {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Välj en fil att skicka");
File file = fileChooser.showOpenDialog(chatArea.getScene().getWindow());
if (file != null) {
try {
model.sendFile(file);
Platform.runLater(() ->
chatArea.appendText("📎 Skickade fil: " + file.getName() + "\n")
);
} catch (Exception e) {
Platform.runLater(() ->
chatArea.appendText("⚠️ Kunde inte skicka fil: " + e.getMessage() + "\n")
);
}
}
}
🤖 Prompt for AI Agents
In src/main/java/com/example/HelloController.java around lines 49 to 57, the
onAttachFileClick handler lacks error handling for file send failures; wrap the
model.sendFile(file) call in a try-catch, log the exception (e.g.,
logger.error(..., e)), and show user feedback via a JavaFX Alert on failure with
a clear message and optional instructions; ensure the UI thread is used for the
Alert (Platform.runLater if needed) and keep the original behavior when send
succeeds.

}
13 changes: 10 additions & 3 deletions src/main/java/com/example/HelloFX.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,23 @@ public class HelloFX extends Application {

@Override
public void start(Stage stage) throws Exception {
// Ladda FXML
FXMLLoader fxmlLoader = new FXMLLoader(HelloFX.class.getResource("hello-view.fxml"));
Parent root = fxmlLoader.load();

// Skapa scenen
Scene scene = new Scene(root, 640, 480);
stage.setTitle("Hello MVC");

// Koppla in CSS-styling
scene.getStylesheets().add(HelloFX.class.getResource("style.css").toExternalForm());

// Sätt titel
stage.setTitle("Java Chat");
stage.setScene(scene);
stage.show();
}

public static void main(String[] args) {
launch();
}

}
}
145 changes: 135 additions & 10 deletions src/main/java/com/example/HelloModel.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,140 @@
package com.example;

/**
* Model layer: encapsulates application data and business logic.
*/
import io.github.cdimascio.dotenv.Dotenv;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;

import java.io.File;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.util.concurrent.CompletableFuture;

public class HelloModel {
/**
* Returns a greeting based on the current Java and JavaFX versions.
*/
public String getGreeting() {
String javaVersion = System.getProperty("java.version");
String javafxVersion = System.getProperty("javafx.version");
return "Hello, JavaFX " + javafxVersion + ", running on Java " + javaVersion + ".";

private static final ObjectMapper mapper = new ObjectMapper();

private final HttpClient client = HttpClient.newHttpClient();
private final String topic;
private final String backendUrl;

/** Standardkonstruktor som läser från .env */
public HelloModel() {
Dotenv dotenv = Dotenv.configure().ignoreIfMissing().load();
this.backendUrl = dotenv.get("BACKEND_URL", System.getenv("BACKEND_URL"));
this.topic = dotenv.get("TOPIC", System.getenv("TOPIC"));
if (backendUrl == null || topic == null) {
throw new IllegalStateException("BACKEND_URL eller TOPIC saknas i .env");
}
}

/** Alternativ konstruktor för tester */
HelloModel(String topic, String backendUrl) {
if (backendUrl == null || backendUrl.isBlank()) {
throw new IllegalArgumentException("backendUrl must not be null/blank");
}
this.backendUrl = backendUrl;
this.topic = topic;
}
Comment on lines +17 to +40
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validate topic in the test constructor to avoid malformed URLs

HelloModel(String topic, String backendUrl) only validates backendUrl, but topic can be null or blank and is later concatenated into URLs (backendUrl + "/" + topic), which would silently turn null into the literal "null" segment.

Consider enforcing non-null/non-blank topic similarly to backendUrl:

-    HelloModel(String topic, String backendUrl) {
-        if (backendUrl == null || backendUrl.isBlank()) {
-            throw new IllegalArgumentException("backendUrl must not be null/blank");
-        }
-        this.backendUrl = backendUrl;
-        this.topic = topic;
-    }
+    HelloModel(String topic, String backendUrl) {
+        if (backendUrl == null || backendUrl.isBlank()) {
+            throw new IllegalArgumentException("backendUrl must not be null/blank");
+        }
+        if (topic == null || topic.isBlank()) {
+            throw new IllegalArgumentException("topic must not be null/blank");
+        }
+        this.backendUrl = backendUrl;
+        this.topic = topic;
+    }
🤖 Prompt for AI Agents
In src/main/java/com/example/HelloModel.java around lines 17 to 40, the test
constructor HelloModel(String topic, String backendUrl) currently only validates
backendUrl but allows topic to be null or blank which can produce malformed
URLs; add a null/blank check for topic (similar to backendUrl), throw an
IllegalArgumentException with a clear message if topic is null or blank, and
only assign this.topic after validation.


public void sendMessage(String message) {
String sender = "[Eric Chat App]";
String fullMessage = sender + " " + message;

String json = "{\"message\": \"" + fullMessage.replace("\"", "\\\"") + "\"}";
String url = backendUrl + "/" + topic;

HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();

client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenAccept(response -> {
if (response.statusCode() >= 300) {
System.err.println("⚠️ Misslyckades att skicka: " + response.statusCode() + " - " + response.body());
}
})
.exceptionally(ex -> {
System.err.println("⚠️ Nätverksfel vid sendMessage: " + ex.getMessage());
return null;
});
}
Comment on lines +42 to +65
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid manual JSON string building in sendMessage (escaping bugs for newlines, backslashes, etc.)

Right now JSON is constructed by hand and only double quotes are escaped. Messages containing newlines, backslashes, or other special characters will generate invalid JSON and may fail on the server.

Since you already have Jackson, it’s safer to let it build the payload:

-    public void sendMessage(String message) {
-        String sender = "[Eric Chat App]";
-        String fullMessage = sender + " " + message;
-
-        String json = "{\"message\": \"" + fullMessage.replace("\"", "\\\"") + "\"}";
+    public void sendMessage(String message) {
+        String sender = "[Eric Chat App]";
+        String fullMessage = sender + " " + message;
+
+        String json;
+        try {
+            json = mapper.writeValueAsString(java.util.Map.of("message", fullMessage));
+        } catch (Exception e) {
+            System.err.println("⚠️ Kunde inte serialisera meddelande: " + e.getMessage());
+            return;
+        }

Optionally, return the CompletableFuture<?> from sendAsync so callers/tests can observe completion, but that’s not strictly required for this PR.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/main/java/com/example/HelloModel.java around lines 42 to 65, replace the
manual JSON string construction with Jackson serialization to avoid incorrect
escaping (newlines, backslashes, etc.); build a small DTO or Map with the
message value (sender + message) and call ObjectMapper.writeValueAsString(...)
to produce the request body, use that string in BodyPublishers.ofString(...),
and catch/handle JsonProcessingException by logging and failing the
CompletableFuture; optionally change sendMessage to return the
CompletableFuture<HttpResponse<String>> from client.sendAsync(...) so
callers/tests can observe completion.


public void sendFile(File file) {
try {
String url = backendUrl + "/" + topic;
String contentType = Files.probeContentType(file.toPath());
if (contentType == null) contentType = "application/octet-stream";

HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", contentType)
.header("X-Filename", file.getName())
.header("Title", "File: " + file.getName())
.POST(HttpRequest.BodyPublishers.ofFile(file.toPath()))
.build();

client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenAccept(response -> {
if (response.statusCode() >= 300) {
System.err.println("⚠️ Filupload misslyckades: " + response.statusCode() + " - " + response.body());
} else {
System.out.println("✅ Fil skickad: " + file.getName());
}
})
.exceptionally(ex -> {
System.err.println("⚠️ Nätverksfel vid sendFile: " + ex.getMessage());
return null;
});
} catch (Exception e) {
System.err.println("⚠️ Kunde inte läsa/skicka fil: " + e.getMessage());
}
}
Comment on lines +67 to +96
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add basic validation for file in sendFile

sendFile assumes file is non-null and points to a regular, readable file. Passing null, a directory, or a missing path will currently fall into the generic catch and just log, which makes debugging harder.

You can fail fast with clearer messages:

-    public void sendFile(File file) {
-        try {
+    public void sendFile(File file) {
+        if (file == null) {
+            throw new IllegalArgumentException("file must not be null");
+        }
+        if (!file.isFile() || !file.canRead()) {
+            System.err.println("⚠️ Ogiltig fil: " + file);
+            return;
+        }
+        try {
             String url = backendUrl + "/" + topic;
             String contentType = Files.probeContentType(file.toPath());

This keeps the async flow but avoids ambiguous failures.

🤖 Prompt for AI Agents
In src/main/java/com/example/HelloModel.java around lines 67 to 96, add explicit
precondition checks for the file parameter (null, exists, isFile, canRead) at
the start of sendFile and fail fast with clear, specific messages instead of
letting everything hit the generic catch; if file is null log or throw
IllegalArgumentException ("file is null"), if !file.exists() log/throw ("file
does not exist: <path>"), if !file.isFile() log/throw ("not a regular file:
<path>"), and if !file.canRead() log/throw ("file not readable: <path>"), then
return early (or propagate) before building the request so the async flow only
runs for valid, readable files.


public CompletableFuture<Void> listen(MessageHandler handler) {
String url = backendUrl + "/" + topic + "/json";
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).build();

return client.sendAsync(request, HttpResponse.BodyHandlers.ofLines())
.thenAccept(response -> response.body().forEach(line -> {
String parsed = parseIncomingLine(line);
if (!parsed.isEmpty()) {
handler.onMessage(parsed);
System.out.println("📩 Meddelande: " + parsed);
}
}))
.exceptionally(ex -> {
System.err.println("⚠️ Nätverksfel vid listen: " + ex.getMessage());
return null;
});
}
Comment on lines +98 to +114
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Consider handling non-2xx status codes in listen before parsing lines

listen streams response.body().forEach(...) without checking response.statusCode(). If the backend returns e.g. 4xx/5xx with a non-JSON body, every line will hit the JSON parser and log as parse errors.

A small guard can improve diagnostics:

-        return client.sendAsync(request, HttpResponse.BodyHandlers.ofLines())
-                .thenAccept(response -> response.body().forEach(line -> {
+        return client.sendAsync(request, HttpResponse.BodyHandlers.ofLines())
+                .thenAccept(response -> {
+                    if (response.statusCode() >= 300) {
+                        System.err.println("⚠️ Lyssning misslyckades: " + response.statusCode());
+                        return;
+                    }
+                    response.body().forEach(line -> {
                         String parsed = parseIncomingLine(line);
                         if (!parsed.isEmpty()) {
                             handler.onMessage(parsed);
                             System.out.println("📩 Meddelande: " + parsed);
                         }
-                    }
-                }))
+                    });
+                })

Also ensure that MessageHandler implementations touching JavaFX UI wrap updates in Platform.runLater (looks like you handle this from the controller).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public CompletableFuture<Void> listen(MessageHandler handler) {
String url = backendUrl + "/" + topic + "/json";
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).build();
return client.sendAsync(request, HttpResponse.BodyHandlers.ofLines())
.thenAccept(response -> response.body().forEach(line -> {
String parsed = parseIncomingLine(line);
if (!parsed.isEmpty()) {
handler.onMessage(parsed);
System.out.println("📩 Meddelande: " + parsed);
}
}))
.exceptionally(ex -> {
System.err.println("⚠️ Nätverksfel vid listen: " + ex.getMessage());
return null;
});
}
public CompletableFuture<Void> listen(MessageHandler handler) {
String url = backendUrl + "/" + topic + "/json";
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).build();
return client.sendAsync(request, HttpResponse.BodyHandlers.ofLines())
.thenAccept(response -> {
if (response.statusCode() >= 300) {
System.err.println("⚠️ Lyssning misslyckades: " + response.statusCode());
return;
}
response.body().forEach(line -> {
String parsed = parseIncomingLine(line);
if (!parsed.isEmpty()) {
handler.onMessage(parsed);
System.out.println("📩 Meddelande: " + parsed);
}
});
})
.exceptionally(ex -> {
System.err.println("⚠️ Nätverksfel vid listen: " + ex.getMessage());
return null;
});
}
🤖 Prompt for AI Agents
In src/main/java/com/example/HelloModel.java around lines 98 to 114, the listen
method currently streams and parses response lines without checking HTTP status;
add a guard that inspects response.statusCode() and if it's not a 2xx, log an
error with the status code and a short preview of the response body (e.g.
collect first N lines), do not pass those lines to the JSON parser or the
handler, and complete the future normally/exceptionally as appropriate; if the
status is 2xx, continue streaming and parsing as before. Also remind consumers
that any MessageHandler that updates JavaFX UI must wrap updates in
Platform.runLater.


String parseIncomingLine(String line) {
try {
JsonNode outer = mapper.readTree(line);
String raw = outer.path("message").asText("");
if (raw.isEmpty()) return "";

String clean = raw.startsWith("{")
? mapper.readTree(raw).path("message").asText(raw)
: raw;

if (!clean.contains("[Eric Chat App]") && !clean.contains("[Javafx-chat]")) {
clean = "[Javafx-chat] " + clean;
}

return "💬 " + clean;
} catch (Exception e) {
System.err.println("⚠️ Kunde inte tolka rad: " + line + " | " + e.getMessage());
return "";
}
}

public interface MessageHandler {
void onMessage(String message);
}
}
6 changes: 6 additions & 0 deletions src/main/java/com/example/NtfyMessageDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.example;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public record NtfyMessageDto(String event, String topic, String message) {}
8 changes: 6 additions & 2 deletions src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
module hellofx {
requires javafx.controls;
requires javafx.fxml;
requires java.net.http;
requires io.github.cdimascio.dotenv.java;
requires com.fasterxml.jackson.databind;
requires com.fasterxml.jackson.annotation;

opens com.example to javafx.fxml;
opens com.example to javafx.fxml, com.fasterxml.jackson.databind;
exports com.example;
}
}
Loading
Loading