Skip to content

Lab3#1

Open
kappsegla wants to merge 11 commits intomainfrom
LAB3
Open

Lab3#1
kappsegla wants to merge 11 commits intomainfrom
LAB3

Conversation

@kappsegla
Copy link

@kappsegla kappsegla commented Nov 25, 2025

Summary by CodeRabbit

  • New Features

    • Chat-style UI with message list, topic selector, message input, send button, version display, and file attachment support for sending files.
  • Tests

    • Added unit tests and test utilities, including a simulated server scenario for messaging.
  • Chores

    • .env added to ignored files for local environment configs.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 25, 2025

📝 Walkthrough

Walkthrough

Adds NTfy-based messaging to the JavaFX app: environment config support, JSON DTO and Jackson parsing, a connection interface plus an HTTP implementation with polling and file attachments, model–view binding and expanded FXML UI, and unit/integration tests (WireMock and test spy).

Changes

Cohort / File(s) Summary
Project Configuration
\.gitignore, pom.xml, src/main/java/module-info.java
Adds .env to gitignore; adds dependencies dotenv-java, jackson-databind, test-scoped wiremock; updates module requires for dotenv, java.net.http, JavaFX graphics, and Jackson.
Messaging Contract & DTO
src/main/java/com/example/NtfyConnection.java, src/main/java/com/example/NtfyMessageDto.java
New NtfyConnection interface (send, default sendFile, receive) and NtfyMessageDto record annotated to ignore unknown JSON properties.
HTTP Connection Implementation
src/main/java/com/example/NtfyConnectionImpl.java
New implementation: loads host from dotenv, async HTTP POST/GET to /mytopic, line-delimited JSON parsing, filters "message" events, persists last_message_id, supports file attachments.
Model & Controller
src/main/java/com/example/HelloModel.java, src/main/java/com/example/HelloController.java, src/main/java/com/example/HelloFX.java
Adds HelloModel with ObservableList and StringProperty; binds UI to model; controller wired to ListView/TextArea, send and attachFile actions; HelloFX imports dotenv.
User Interface
src/main/resources/com/example/hello-view.fxml
Replaces simple FXML with a BorderPane chat-like layout: topic selector, message list, TextArea input, Send button wired to controller handlers.
Tests & Test Utilities
src/test/java/com/example/HelloModelTest.java, src/test/java/com/example/NtfyConnectionSpy.java
Adds NtfyConnectionSpy test utility and tests: one spy-based unit test and one WireMock-based test asserting POST payload to /mytopic.

Sequence Diagram

sequenceDiagram
    participant User
    participant Controller as HelloController
    participant Model as HelloModel
    participant Connection as NtfyConnectionImpl
    participant Server as Ntfy Server

    User->>Controller: click Send
    Controller->>Model: sendMessage()
    Model->>Connection: send(message)
    Connection->>Server: POST /mytopic (async)
    Server-->>Connection: 200 OK
    Note over Connection,Server: Connection polls/streams\nGET /mytopic/json?since=lastId
    Server-->>Connection: message stream (lines of JSON)
    Connection->>Connection: parse & filter "message" events
    Connection->>Model: invoke message handler (Consumer)
    Model->>Controller: ObservableList updated (Platform.runLater)
    Controller->>User: messageView shows new message
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

"I nibble .env and hop through streams,
I stitch JSON dreams into UI beams.
A click, a post, a file takes flight,
The list grows bright in morning light—
Hooray, the chat hops into sight!" 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The pull request title 'Lab3' is vague and does not describe what changes were made; it only indicates a lab assignment number without conveying the actual implementation details. Use a descriptive title that summarizes the main changes, such as 'Add Ntfy messaging integration with HTTP client and JavaFX UI' or 'Implement message sending/receiving with file attachment support'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (7)
pom.xml (1)

48-63: Dependencies align with new features; double‑check WireMock artifact

The added dependencies (dotenv for config, Jackson for JSON, WireMock for HTTP tests) are consistent with the new code paths and module declarations.

  • jackson-databind will also bring in jackson-core transitively, so no extra Maven coord is strictly required for com.fasterxml.jackson.core even though it’s listed in module-info.java.
  • Please confirm that org.wiremock:wiremock:3.12.1 is the intended artifact for the com.github.tomakehurst.wiremock.junit5.WireMockTest extension; some versions split the JUnit 5 integration into a dedicated artifact (e.g. wiremock-junit-jupiter).
src/main/resources/com/example/hello-view.fxml (1)

3-74: FXML layout matches the chat use case

The new BorderPane/VBox layout, messageView list, inputField, and Send button wired to #sendMessage all align with the model/connection design and should work well once the controller fields and handler are in place. UI structure looks consistent and clear.

src/main/java/com/example/NtfyConnection.java (1)

5-9: Interface contract is clear; consider dropping redundant public

The NtfyConnection interface cleanly separates sending and receiving. Since methods in interfaces are implicitly public, you can omit the public keywords to reduce noise:

-public interface NtfyConnection {
-
-    public boolean send(String message);
-
-    public void receive(Consumer<NtfyMessageDto> messageHandler);
+public interface NtfyConnection {
+
+    boolean send(String message);
+
+    void receive(Consumer<NtfyMessageDto> messageHandler);
 }
src/main/java/com/example/HelloController.java (2)

14-14: Consider constructor injection for better testability.

Direct instantiation of NtfyConnectionImpl creates tight coupling. While tests currently work around this by testing HelloModel directly, consider accepting NtfyConnection as a constructor parameter or using a factory for easier controller-level testing in the future.


16-16: Missing @FXML annotation on messageView.

The messageView field is public but lacks the @FXML annotation. If it's injected from FXML, add @FXML. If it's intentionally public for other reasons, consider making it private with a getter.

+    @FXML
     public ListView<NtfyMessageDto> messageView;
src/main/java/com/example/NtfyConnectionImpl.java (2)

74-77: Inefficient: saveLastId called for every message.

saveLastId() writes to disk for every message in the stream. Consider saving only after processing all messages, or at least debouncing the writes.


79-92: Silent error handling loses debugging information.

Both loadLastId() and saveLastId() silently swallow IOException without any logging. This makes debugging file I/O issues difficult.

 private String loadLastId() {
     try {
         if (Files.exists(lastIdFile)) {
             return Files.readString(lastIdFile).trim();
         }
     } catch (IOException e) {
+        System.err.println("Failed to load last message ID: " + e.getMessage());
     }
     return "all";
 }
 private void saveLastId(String id) {
     try {
         Files.writeString(lastIdFile, id);
-    } catch (IOException e) {}
+    } catch (IOException e) {
+        System.err.println("Failed to save last message ID: " + e.getMessage());
+    }
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 21c51e8 and 854c662.

📒 Files selected for processing (12)
  • .gitignore (1 hunks)
  • pom.xml (1 hunks)
  • src/main/java/com/example/HelloController.java (1 hunks)
  • src/main/java/com/example/HelloFX.java (2 hunks)
  • src/main/java/com/example/HelloModel.java (2 hunks)
  • src/main/java/com/example/NtfyConnection.java (1 hunks)
  • src/main/java/com/example/NtfyConnectionImpl.java (1 hunks)
  • src/main/java/com/example/NtfyMessageDto.java (1 hunks)
  • src/main/java/module-info.java (1 hunks)
  • src/main/resources/com/example/hello-view.fxml (1 hunks)
  • src/test/java/com/example/HelloModelTest.java (1 hunks)
  • src/test/java/com/example/NtfyConnectionSpy.java (1 hunks)
🔇 Additional comments (9)
.gitignore (1)

3-3: Good call ignoring .env

Ignoring the .env file is appropriate to keep environment-specific secrets and config out of version control.

src/main/java/com/example/HelloFX.java (1)

3-15: No functional change in HelloFX; ready for env‑based config

The added Dotenv import doesn’t change runtime behavior; the application startup flow (loading hello-view.fxml and showing the stage) remains the same and looks correct.

src/main/java/com/example/NtfyMessageDto.java (1)

1-9: DTO definition is sensible for Jackson deserialization

Using a record with @JsonIgnoreProperties(ignoreUnknown = true) is a clean way to map ntfy messages and stay robust to extra fields. Just ensure the incoming JSON uses the same property names (id, time, event, topic, message); if not, you can add @JsonProperty per field.

src/test/java/com/example/HelloModelTest.java (1)

1-42: Tests nicely cover model–connection wiring

The spy-based test and the WireMock test together give good coverage that:

  • HelloModel forwards messageToSend to the connection, and
  • NtfyConnectionImpl sends the expected payload to /mytopic against a fake server.

This looks solid and should catch regressions in the send path.

src/main/java/module-info.java (1)

4-8: Module requirements line up with new dependencies

The added requires directives for dotenv, java.net.http, JavaFX graphics, and Jackson (com.fasterxml.jackson.core / com.fasterxml.jackson.databind) are consistent with the new HTTP, JSON, and config usage. This should keep the JPMS module graph coherent as long as the corresponding jars are on the module path (with jackson-core arriving transitively from jackson-databind).

src/test/java/com/example/NtfyConnectionSpy.java (1)

5-18: LGTM! Standard test spy implementation.

The spy correctly captures the last sent message for verification in tests. The empty receive() implementation is appropriate for tests focused on send behavior.

src/main/java/com/example/HelloController.java (1)

33-43: LGTM! Cell factory correctly handles empty/null items.

The custom cell factory properly calls super.updateItem() and handles both empty and null cases before displaying the message text.

src/main/java/com/example/HelloModel.java (1)

53-56: LGTM! Correct thread handling for JavaFX.

Using Platform.runLater() ensures the messages list is updated on the JavaFX Application Thread, which is required for UI-bound ObservableLists.

src/main/java/com/example/NtfyConnectionImpl.java (1)

23-23: Relative file path may cause issues in different execution contexts.

last_message_id.txt as a relative path will be created in the current working directory, which varies depending on how the application is launched. Consider using a user-specific location like System.getProperty("user.home").

Comment on lines +27 to +30
if (versionLabel != null) {
versionLabel.setText(model.getGreeting());
}
messageView.setItems(model.getMessages());
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

Inconsistent null checking may cause NPE.

versionLabel is null-checked before use, but messageView is not. If messageView can be null (e.g., if FXML injection fails), line 30 will throw a NullPointerException.

         if (versionLabel != null) {
             versionLabel.setText(model.getGreeting());
         }
-        messageView.setItems(model.getMessages());
+        if (messageView != null) {
+            messageView.setItems(model.getMessages());
+        }
📝 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
if (versionLabel != null) {
versionLabel.setText(model.getGreeting());
}
messageView.setItems(model.getMessages());
if (versionLabel != null) {
versionLabel.setText(model.getGreeting());
}
if (messageView != null) {
messageView.setItems(model.getMessages());
}
🤖 Prompt for AI Agents
In src/main/java/com/example/HelloController.java around lines 27 to 30, the
code null-checks versionLabel but directly calls messageView.setItems(...),
risking a NullPointerException if messageView is null (e.g., FXML injection
failure); fix by either null-checking messageView before calling setItems (use
if (messageView != null) { messageView.setItems(model.getMessages()); } ) or
assert/validate in the controller initialization that messageView is non-null
and throw a clear exception early so the FXML binding problem is detected.

Comment on lines +49 to +52
public void sendMessage() {
connection.send(messageToSend.get());

}
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

Handle null message and send failure.

messageToSend.get() may return null, and the send() return value indicating success/failure is ignored. Consider validating the message and providing feedback on failure.

     public void sendMessage() {
-        connection.send(messageToSend.get());
-
+        String message = messageToSend.get();
+        if (message != null && !message.isBlank()) {
+            if (connection.send(message)) {
+                messageToSend.set(""); // Clear input on success
+            }
+        }
    }
📝 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 void sendMessage() {
connection.send(messageToSend.get());
}
public void sendMessage() {
String message = messageToSend.get();
if (message != null && !message.isBlank()) {
if (connection.send(message)) {
messageToSend.set(""); // Clear input on success
}
}
}
🤖 Prompt for AI Agents
In src/main/java/com/example/HelloModel.java around lines 49 to 52, the code
calls connection.send(messageToSend.get()) without checking for null and ignores
the send() result; update the method to validate messageToSend.get() for null
(return or log/throw a meaningful exception if null), call connection.send(...)
and capture its boolean/return status, and then handle a failed send by logging
the failure and returning/throwing an error or retrying as appropriate so
callers receive clear feedback on send failures.

return null;
}
})
.filter(msg -> msg != null && msg.event().equals("message"))
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

Potential NPE if event field is null.

Using msg.event().equals("message") will throw NullPointerException if event is null. Use null-safe comparison.

-                        .filter(msg -> msg != null && msg.event().equals("message"))
+                        .filter(msg -> msg != null && "message".equals(msg.event()))
🤖 Prompt for AI Agents
In src/main/java/com/example/NtfyConnectionImpl.java around line 73, the lambda
uses msg.event().equals("message") which can throw NPE if event() returns null;
replace the comparison with a null-safe check such as
"message".equals(msg.event()) or Objects.equals("message", msg.event()) so the
code will not throw when event is null.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In @src/main/java/com/example/HelloController.java:
- Line 19: Add the missing @FXML annotation to the messageView field in
HelloController so the FXML loader injects it properly: annotate the public
ListView<NtfyMessageDto> messageView field with @FXML and ensure the
javafx.fxml.FXML import is present (you may also change the field to private if
following the same visibility pattern as other injected fields).
- Around line 53-61: attachFile(ActionEvent) currently calls
messageView.getScene().getWindow() which can throw an NPE if the scene isn't
attached; update attachFile to safely obtain a Window before calling
chooser.showOpenDialog(Window): check messageView.getScene() != null and use its
getWindow(), otherwise try to derive the Window from the event source
((Node)event.getSource()).getScene() if non-null, and if still null call
chooser.showOpenDialog(null) or abort with a user-friendly message; ensure
model.sendFile(file) is only called after a non-null File is returned.

In @src/main/java/com/example/NtfyConnectionImpl.java:
- Around line 75-90: The empty catch blocks in loadLastId() and saveLastId()
swallow IOExceptions; update these methods (loadLastId, saveLastId, which use
lastIdFile) to log the caught IOException at an appropriate level (e.g.,
warn/error) including the exception message and context (e.g., "failed to read
lastId" / "failed to write lastId") so failures are visible and so saveLastId
failures won't silently cause duplicate processing in receive(); do not change
the return behavior of loadLastId() (still return "all" on failure) but ensure
the exception is logged, and for saveLastId() log the error and include the id
value being written for debugability.
- Around line 38-46: The send(String message) method currently returns true
immediately before the async HTTP call completes, preventing callers from
knowing success/failure; modify send to either perform a synchronous call (use
http.send(...) and return based on response status) or change its signature to
CompletableFuture<Boolean> and return http.sendAsync(...).thenApply(resp ->
resp.statusCode() >= 200 && resp.statusCode() < 300).exceptionally(ex -> { log
the error via your logger and return false; }); at minimum, attach
.exceptionally(...) to the existing http.sendAsync(...) invocation to log
failures and return a CompletableFuture<Boolean> so callers can handle errors;
reference the send method, http.sendAsync call, and hostName + "/mytopic" URI
when making the change.
🧹 Nitpick comments (5)
src/main/java/com/example/NtfyConnectionImpl.java (2)

59-72: Consider checking HTTP response status before processing body.

The code processes the response body without verifying the HTTP status code. A 4xx/5xx response might return error content that fails to parse as NtfyMessageDto, causing silent failures.

Add status check
         http.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofLines())
                 .thenAccept(response -> {
+                    if (response.statusCode() >= 400) {
+                        System.err.println("Failed to receive messages: HTTP " + response.statusCode());
+                        return;
+                    }
                     response.body()
                         .map(line -> {
                             try {
                                 return mapper.readValue(line, NtfyMessageDto.class);
                             } catch (JsonProcessingException e) {
                                 return null;
                             }
                         })
                         .filter(msg -> msg != null && msg.event().equals("message"))
                         .forEach(msg -> {
                             messageHandler.accept(msg);
                             saveLastId(msg.id());
                         });
+                });
-                });

21-23: Consider implementing AutoCloseable for proper HttpClient resource management.

HttpClient is AutoCloseable in Java 21+ (this project targets Java 25). If the application requires graceful shutdown, implement AutoCloseable on this class and close the HttpClient in a close() method.

src/main/java/com/example/HelloModel.java (1)

51-54: Consider clearing the message field after sending and adding null check.

The method sends without verifying that messageToSend has content, and doesn't clear the input after a successful send. This could lead to:

  • Sending empty/null messages
  • Users accidentally re-sending the same message
Proposed improvement
     public void sendMessage() {
-        connection.send(messageToSend.get());
-
+        String message = messageToSend.get();
+        if (message != null && !message.isBlank()) {
+            connection.send(message);
+            messageToSend.set("");
+        }
    }
src/main/java/com/example/HelloController.java (1)

30-33: Inconsistent null checking between versionLabel and messageView.

versionLabel has a null check before use, but messageView does not. Either both should be checked, or neither (if FXML guarantees injection). Since messageView is used for the FileChooser parent window on line 56, a null value would cause a NullPointerException.

src/main/resources/com/example/hello-view.fxml (1)

51-52: messageLabel has fx:id but is not referenced in the controller.

The label has fx:id="messageLabel" but HelloController doesn't declare a corresponding field. Either remove the fx:id if it's not needed, or add a field in the controller if dynamic updates are planned.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 854c662 and b044060.

📒 Files selected for processing (5)
  • src/main/java/com/example/HelloController.java
  • src/main/java/com/example/HelloModel.java
  • src/main/java/com/example/NtfyConnection.java
  • src/main/java/com/example/NtfyConnectionImpl.java
  • src/main/resources/com/example/hello-view.fxml
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/example/NtfyConnection.java
🔇 Additional comments (5)
src/main/java/com/example/HelloModel.java (2)

55-58: Good use of Platform.runLater for thread-safe UI updates.

The callback correctly marshals message additions to the JavaFX Application Thread, preventing concurrent modification issues with the ObservableList.


22-25: Constructor starts receiving messages immediately.

Calling receiveMessage() in the constructor means message fetching begins as soon as the model is created. This is acceptable for this use case, but be aware that any setup issues with NtfyConnection will surface during construction.

src/main/java/com/example/HelloController.java (1)

36-46: Cell factory implementation looks correct.

The custom ListCell properly handles empty cells and null items, displaying only the message text as intended.

src/main/resources/com/example/hello-view.fxml (2)

26-34: ComboBox appears non-functional—no fx:id or selection handler.

The ComboBox is populated with topics but has no fx:id for controller access and no onAction handler. The TODO comment on line 16 suggests this may be intentional WIP, but currently selecting a topic has no effect.

Is the topic selection functionality planned for a future iteration, or should it be wired up in this PR?


10-75: UI layout structure is well-organized.

The BorderPane layout with a sidebar for navigation and a center area for messaging is appropriate for a chat application. The input area with TextArea, attachment button, and send button provides good UX for message composition.

private final HelloModel model = new HelloModel();
private final HelloModel model = new HelloModel(new NtfyConnectionImpl());

public ListView<NtfyMessageDto> messageView;
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

Missing @FXML annotation on messageView.

The field is public but lacks the @FXML annotation. For consistency with other FXML-injected fields and to ensure proper injection, add the annotation.

+    @FXML
     public ListView<NtfyMessageDto> messageView;
📝 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 ListView<NtfyMessageDto> messageView;
@FXML
public ListView<NtfyMessageDto> messageView;
🤖 Prompt for AI Agents
In @src/main/java/com/example/HelloController.java at line 19, Add the missing
@FXML annotation to the messageView field in HelloController so the FXML loader
injects it properly: annotate the public ListView<NtfyMessageDto> messageView
field with @FXML and ensure the javafx.fxml.FXML import is present (you may also
change the field to private if following the same visibility pattern as other
injected fields).

Comment on lines +53 to 61
public void attachFile(ActionEvent event) {
FileChooser chooser = new FileChooser();
chooser.setTitle("Choose file");
File file = chooser.showOpenDialog(messageView.getScene().getWindow());

if (file != null) {
model.sendFile(file);
}
}
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

Potential NPE if scene is not yet attached.

messageView.getScene() can return null if called before the scene graph is fully constructed. Consider adding a null check or using a different approach to get the window.

Safer window retrieval
     public void attachFile(ActionEvent event) {
         FileChooser chooser = new FileChooser();
         chooser.setTitle("Choose file");
-        File file = chooser.showOpenDialog(messageView.getScene().getWindow());
+        var scene = messageView.getScene();
+        File file = chooser.showOpenDialog(scene != null ? scene.getWindow() : null);

         if (file != null) {
             model.sendFile(file);
         }
     }
📝 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 void attachFile(ActionEvent event) {
FileChooser chooser = new FileChooser();
chooser.setTitle("Choose file");
File file = chooser.showOpenDialog(messageView.getScene().getWindow());
if (file != null) {
model.sendFile(file);
}
}
public void attachFile(ActionEvent event) {
FileChooser chooser = new FileChooser();
chooser.setTitle("Choose file");
var scene = messageView.getScene();
File file = chooser.showOpenDialog(scene != null ? scene.getWindow() : null);
if (file != null) {
model.sendFile(file);
}
}
🤖 Prompt for AI Agents
In @src/main/java/com/example/HelloController.java around lines 53 - 61,
attachFile(ActionEvent) currently calls messageView.getScene().getWindow() which
can throw an NPE if the scene isn't attached; update attachFile to safely obtain
a Window before calling chooser.showOpenDialog(Window): check
messageView.getScene() != null and use its getWindow(), otherwise try to derive
the Window from the event source ((Node)event.getSource()).getScene() if
non-null, and if still null call chooser.showOpenDialog(null) or abort with a
user-friendly message; ensure model.sendFile(file) is only called after a
non-null File is returned.

Comment on lines +38 to +46
public boolean send(String message) {
HttpRequest httpRequest = HttpRequest.newBuilder()
.POST(HttpRequest.BodyPublishers.ofString(message))
.uri(URI.create(hostName + "/mytopic"))
.build();

http.sendAsync(httpRequest, HttpResponse.BodyHandlers.discarding());
return true;
}
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

send() returns true before the async request completes.

The method returns true immediately, regardless of whether the HTTP request succeeds or fails. This makes error handling impossible for callers. Consider either:

  • Making the call synchronous if callers need result feedback
  • Returning a CompletableFuture<Boolean> for async error handling
  • At minimum, adding .exceptionally() to log failures
Proposed fix using exceptionally handler
     @Override
     public boolean send(String message) {
         HttpRequest httpRequest = HttpRequest.newBuilder()
                 .POST(HttpRequest.BodyPublishers.ofString(message))
                 .uri(URI.create(hostName + "/mytopic"))
                 .build();

-        http.sendAsync(httpRequest, HttpResponse.BodyHandlers.discarding());
+        http.sendAsync(httpRequest, HttpResponse.BodyHandlers.discarding())
+                .exceptionally(e -> {
+                    System.err.println("Error sending message: " + e.getMessage());
+                    return null;
+                });
         return true;
     }
📝 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 boolean send(String message) {
HttpRequest httpRequest = HttpRequest.newBuilder()
.POST(HttpRequest.BodyPublishers.ofString(message))
.uri(URI.create(hostName + "/mytopic"))
.build();
http.sendAsync(httpRequest, HttpResponse.BodyHandlers.discarding());
return true;
}
public boolean send(String message) {
HttpRequest httpRequest = HttpRequest.newBuilder()
.POST(HttpRequest.BodyPublishers.ofString(message))
.uri(URI.create(hostName + "/mytopic"))
.build();
http.sendAsync(httpRequest, HttpResponse.BodyHandlers.discarding())
.exceptionally(e -> {
System.err.println("Error sending message: " + e.getMessage());
return null;
});
return true;
}
🤖 Prompt for AI Agents
In @src/main/java/com/example/NtfyConnectionImpl.java around lines 38 - 46, The
send(String message) method currently returns true immediately before the async
HTTP call completes, preventing callers from knowing success/failure; modify
send to either perform a synchronous call (use http.send(...) and return based
on response status) or change its signature to CompletableFuture<Boolean> and
return http.sendAsync(...).thenApply(resp -> resp.statusCode() >= 200 &&
resp.statusCode() < 300).exceptionally(ex -> { log the error via your logger and
return false; }); at minimum, attach .exceptionally(...) to the existing
http.sendAsync(...) invocation to log failures and return a
CompletableFuture<Boolean> so callers can handle errors; reference the send
method, http.sendAsync call, and hostName + "/mytopic" URI when making the
change.

Comment on lines +75 to +90
private String loadLastId() {
try {
if (Files.exists(lastIdFile)) {
return Files.readString(lastIdFile).trim();
}
} catch (IOException e) {
}
return "all";
}

private void saveLastId(String id) {
try {
Files.writeString(lastIdFile, id);
} catch (IOException e) {
}
}
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

Empty catch blocks silently swallow IO errors.

Both loadLastId() and saveLastId() have empty catch blocks that hide failures. For saveLastId() in particular, a silent failure means the same messages could be reprocessed on the next receive() call.

Add minimal logging
     private String loadLastId() {
         try {
             if (Files.exists(lastIdFile)) {
                 return Files.readString(lastIdFile).trim();
             }
         } catch (IOException e) {
+            System.err.println("Failed to load last message ID: " + e.getMessage());
         }
         return "all";
     }

     private void saveLastId(String id) {
         try {
             Files.writeString(lastIdFile, id);
         } catch (IOException e) {
+            System.err.println("Failed to save last message ID: " + e.getMessage());
         }
     }
🤖 Prompt for AI Agents
In @src/main/java/com/example/NtfyConnectionImpl.java around lines 75 - 90, The
empty catch blocks in loadLastId() and saveLastId() swallow IOExceptions; update
these methods (loadLastId, saveLastId, which use lastIdFile) to log the caught
IOException at an appropriate level (e.g., warn/error) including the exception
message and context (e.g., "failed to read lastId" / "failed to write lastId")
so failures are visible and so saveLastId failures won't silently cause
duplicate processing in receive(); do not change the return behavior of
loadLastId() (still return "all" on failure) but ensure the exception is logged,
and for saveLastId() log the error and include the id value being written for
debugability.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @src/main/java/com/example/NtfyConnectionImpl.java:
- Around line 44-51: In the catch block of the send routine in
NtfyConnectionImpl (the try around http.send and the catch catching IOException
| InterruptedException e), restore the thread interrupt status when e is an
InterruptedException by calling Thread.currentThread().interrupt(); and replace
the e.printStackTrace() with proper logging (or at minimum log the exception)
before returning false so the interruption contract is preserved and the error
is recorded.
🧹 Nitpick comments (3)
src/main/java/com/example/NtfyConnectionImpl.java (3)

54-78: Consider adding error handling for the async operation.

The async operation has no .exceptionally() handler. If the HTTP request fails, the error will be silently lost.

♻️ Suggested improvement
         http.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofLines())
                 .thenAccept(response -> response.body()
                         .map(line -> {
                             try {
                                 return mapper.readValue(line, NtfyMessageDto.class);
                             } catch (JsonProcessingException e) {
                                 return null;
                             }
                         })
                         .filter(msg -> msg != null && msg.event().equals("message"))
                         .forEach(msg -> {
                             messageHandler.accept(msg);
                             saveLastId(msg.id());
-                        }));
+                        }))
+                .exceptionally(ex -> {
+                    ex.printStackTrace();
+                    return null;
+                });

80-88: Empty catch block silently swallows exceptions.

If reading the file fails for reasons other than non-existence (e.g., permission denied), the error is hidden, making debugging difficult.

♻️ Suggested improvement
     private String loadLastId() {
         try {
             if (Files.exists(lastIdFile)) {
                 return Files.readString(lastIdFile).trim();
             }
         } catch (IOException e) {
+            e.printStackTrace();
         }
         return "all";
     }

90-95: Empty catch block hides persistence failures.

If saving the last message ID fails, subsequent restarts will reprocess old messages without any indication that something went wrong.

♻️ Suggested improvement
     private void saveLastId(String id) {
         try {
             Files.writeString(lastIdFile, id);
         } catch (IOException e) {
+            e.printStackTrace();
         }
     }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b044060 and 75ad1cc.

📒 Files selected for processing (1)
  • src/main/java/com/example/NtfyConnectionImpl.java
🔇 Additional comments (2)
src/main/java/com/example/NtfyConnectionImpl.java (2)

19-25: LGTM on class structure and fields.

The fields are appropriately scoped as final and private. The HttpClient is correctly instantiated as a single instance for reuse.


27-34: Good constructor design for testability.

The overloaded constructor accepting hostName directly enables easy testing without requiring environment configuration.

Comment on lines +44 to +51
try {

http.send(httpRequest, HttpResponse.BodyHandlers.discarding());
return true;
} catch (IOException | InterruptedException e) {
e.printStackTrace();
return false;}
}
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

Restore the thread interrupt status when catching InterruptedException.

When InterruptedException is caught, the thread's interrupt flag is cleared. Failing to restore it breaks the thread's interruption contract and can prevent proper shutdown behavior.

🔧 Proposed fix
     try {
         http.send(httpRequest, HttpResponse.BodyHandlers.discarding());
         return true;
     } catch (IOException | InterruptedException e) {
+        if (e instanceof InterruptedException) {
+            Thread.currentThread().interrupt();
+        }
         e.printStackTrace();
         return false;
     }
📝 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
try {
http.send(httpRequest, HttpResponse.BodyHandlers.discarding());
return true;
} catch (IOException | InterruptedException e) {
e.printStackTrace();
return false;}
}
try {
http.send(httpRequest, HttpResponse.BodyHandlers.discarding());
return true;
} catch (IOException | InterruptedException e) {
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
e.printStackTrace();
return false;
}
🤖 Prompt for AI Agents
In @src/main/java/com/example/NtfyConnectionImpl.java around lines 44 - 51, In
the catch block of the send routine in NtfyConnectionImpl (the try around
http.send and the catch catching IOException | InterruptedException e), restore
the thread interrupt status when e is an InterruptedException by calling
Thread.currentThread().interrupt(); and replace the e.printStackTrace() with
proper logging (or at minimum log the exception) before returning false so the
interruption contract is preserved and the error is recorded.

Comment on lines +97 to +116
@Override
public boolean sendFile(File file) {
try {
byte[] fileBytes = Files.readAllBytes(file.toPath());

HttpRequest httpRequest = HttpRequest.newBuilder()
.uri(URI.create(hostName + "/mytopic"))
.header("Attachment", file.getName())
.POST(HttpRequest.BodyPublishers.ofByteArray(fileBytes))
.build();

http.sendAsync(httpRequest, HttpResponse.BodyHandlers.discarding());
return true;

} catch (IOException e) {
System.out.println("Error sending file");
e.printStackTrace();
return false;
}
}
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

sendFile() returns success before the async operation completes.

The method uses sendAsync() but returns true immediately without waiting for the response. The caller has no way to know if the file was actually sent successfully.

🔧 Proposed fix - wait for completion
     @Override
     public boolean sendFile(File file) {
         try {
             byte[] fileBytes = Files.readAllBytes(file.toPath());
 
             HttpRequest httpRequest = HttpRequest.newBuilder()
                     .uri(URI.create(hostName + "/mytopic"))
                     .header("Attachment", file.getName())
                     .POST(HttpRequest.BodyPublishers.ofByteArray(fileBytes))
                     .build();
 
-            http.sendAsync(httpRequest, HttpResponse.BodyHandlers.discarding());
+            http.send(httpRequest, HttpResponse.BodyHandlers.discarding());
             return true;
 
-        } catch (IOException e) {
+        } catch (IOException | InterruptedException e) {
+            if (e instanceof InterruptedException) {
+                Thread.currentThread().interrupt();
+            }
             System.out.println("Error sending file");
             e.printStackTrace();
             return false;
         }
     }

Alternatively, if async behavior is intended, change the return type to CompletableFuture<Boolean> so callers can handle completion properly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants