Skip to content
Merged
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.
16 changes: 16 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,22 @@
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>io.github.cdimascio</groupId>
<artifactId>dotenv-java</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>3.0.1</version>
</dependency>
Comment on lines +53 to +57
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Incorrect Jackson group ID.

The group ID tools.jackson.core appears incorrect. The standard Jackson library uses com.fasterxml.jackson.core as the group ID.

Apply this diff to correct the group ID:

         <dependency>
-            <groupId>tools.jackson.core</groupId>
+            <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-databind</artifactId>
-            <version>3.0.1</version>
+            <version>2.18.2</version>
         </dependency>

Note: Jackson 3.x is still in development. Version 2.18.2 is the latest stable release from the 2.x series.

📝 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
<dependency>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.18.2</version>
</dependency>
🤖 Prompt for AI Agents
In pom.xml around lines 53 to 57, the Jackson dependency uses the wrong groupId;
change the groupId from tools.jackson.core to com.fasterxml.jackson.core and
update the version to a stable 2.x release (e.g., 2.18.2). Ensure the dependency
block reads with the corrected groupId and the recommended version to avoid
using the incorrect artifact coordinates or an unstable 3.x snapshot.

<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock</artifactId>
<version>4.0.0-beta.15</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
81 changes: 79 additions & 2 deletions src/main/java/com/example/HelloController.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,99 @@
package com.example;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;

import java.io.IOException;

/**
* Controller layer: mediates between the view (FXML) and the model.
* Handles user interaction, initializes UI bindings, and forwards actions
* (sending messages, switching topics) to the model.
*/
public class HelloController {

private final HelloModel model = new HelloModel();
/** The application model injected by the framework or calling code. */
private final HelloModel model;

/**
* Default constructor used by the JavaFX runtime.
* Creates a model with a production {@link NtfyConnectionImpl}.
*/
public HelloController() {
this(new HelloModel(new NtfyConnectionImpl()));
}

/**
* Constructor primarily intended for testing or dependency injection.
*
* @param model the model instance this controller should use
*/
public HelloController(HelloModel model) {
this.model = model;
}

@FXML
public ListView<NtfyMessageDto> messageView;

@FXML
private Label messageLabel;

@FXML
private Label statusLabel;

@FXML
private TextField inputField;

@FXML
private TextField topicField;

/**
* Called automatically by JavaFX after FXML fields are injected.
* Sets up UI bindings, listens for connection status changes,
* and triggers the initial topic connection.
*/
@FXML
private void initialize() {
if (messageLabel != null) {
messageLabel.setText(model.getGreeting());
}

// Bind input fields to model state
inputField.textProperty().bindBidirectional(model.messageToSendProperty());
topicField.textProperty().bindBidirectional(model.topicProperty());
messageView.setItems(model.getMessages());

// Update connection status indicator
model.connectedProperty().addListener((obs, wasConnected, isConnected) -> {
statusLabel.setText(isConnected ? "🟢 Connected" : "🔴 Disconnected");
});

model.connectToTopic();
}

/**
* Handles clicking the "Send" button.
* Sends the message via the model and clears the input field.
*/
public void sendMessage(ActionEvent actionEvent) {
if (!inputField.getText().trim().isEmpty()) {
try {
model.sendMessage();
inputField.clear();
} catch (IOException e) {
System.err.println("Failed to send message: " + e.getMessage());
}
Comment on lines +86 to +88
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Show user-visible error feedback instead of System.err.

In a GUI application, users cannot see System.err output. When message sending fails, the user receives no feedback.

Replace console logging with a JavaFX Alert:

             } catch (IOException e) {
-                System.err.println("Failed to send message: " + e.getMessage());
+                var alert = new Alert(Alert.AlertType.ERROR);
+                alert.setTitle("Send Failed");
+                alert.setHeaderText("Failed to send message");
+                alert.setContentText(e.getMessage());
+                alert.showAndWait();
             }

Don't forget to add the import:

import javafx.scene.control.Alert;
🤖 Prompt for AI Agents
In src/main/java/com/example/HelloController.java around lines 58 to 60, replace
the System.err.println call inside the catch(IOException) with user-visible
JavaFX feedback: create and show a javafx.scene.control.Alert of type ERROR with
a clear title, header and the exception message; ensure the Alert is constructed
and shown on the JavaFX Application Thread (wrap in Platform.runLater if the
catch may run off the UI thread) and add the import for
javafx.scene.control.Alert (and javafx.application.Platform if using
Platform.runLater).

}
}

/**
* Handles clicking the "Connect" button.
* Reconnects the model to the current topic.
*/
public void connectToTopic(ActionEvent actionEvent) {
model.connectToTopic();
}
}
}
15 changes: 12 additions & 3 deletions src/main/java/com/example/HelloFX.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,29 @@
import javafx.scene.Scene;
import javafx.stage.Stage;

/**
* JavaFX application entry point.
* Loads the FXML view and displays the main window.
*/
public class HelloFX extends Application {

/**
* Initializes and shows the primary JavaFX stage.
*/
@Override
public void start(Stage stage) throws Exception {
FXMLLoader fxmlLoader = new FXMLLoader(HelloFX.class.getResource("hello-view.fxml"));
Parent root = fxmlLoader.load();
Scene scene = new Scene(root, 640, 480);
stage.setTitle("Hello MVC");
Scene scene = new Scene(root, 740, 480);
stage.setTitle("JavaFX Chat App \uD83D\uDCAC");
stage.setScene(scene);
stage.show();
}

/**
* Launches the JavaFX application.
*/
public static void main(String[] args) {
launch();
}

}
149 changes: 148 additions & 1 deletion src/main/java/com/example/HelloModel.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,87 @@
package com.example;

import javafx.application.Platform;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

import java.io.IOException;

/**
* Model layer: encapsulates application data and business logic.
* Manages the active topic subscription, incoming messages, and
* message sending through an {@link NtfyConnection}.
*/
public class HelloModel {

/** Underlying connection for sending and receiving messages. */
private final NtfyConnection connection;
/** Observable list of received messages for UI binding. */
private final ObservableList<NtfyMessageDto> messages = FXCollections.observableArrayList();
/** Text the user intends to send. */
private final StringProperty messageToSend = new SimpleStringProperty();
/** Currently selected topic. */
private final StringProperty topic = new SimpleStringProperty("mytopic");
/** Handle for the active subscription, if any. */
private NtfyConnection.Subscription currentSubscription;
/** Indicates whether the model is currently connected to a topic. */
private final ReadOnlyBooleanWrapper connected = new ReadOnlyBooleanWrapper(false);

/**
* Creates a new model using the provided {@link NtfyConnection}.
*
* @param connection the message connection backend
*/
public HelloModel(NtfyConnection connection) {
this.connection = connection;
}

/** @return observable list of received messages */
public ObservableList<NtfyMessageDto> getMessages() {
return messages;
}

public StringProperty messageToSendProperty() {
return messageToSend;
}

public String getMessageToSend() {
return messageToSend.get();
}

public void setMessageToSend(String message) {
messageToSend.set(message);
}

public String getTopic() {
return topic.get();
}

public StringProperty topicProperty() {
return topic;
}

public void setTopic(String topic) {
this.topic.set(topic);
}

/**
* Read-only property indicating whether a subscription is active.
*/
public ReadOnlyBooleanProperty connectedProperty() {
return connected.getReadOnlyProperty();
}

/**
* @return true if a subscription is active and open
*/
public boolean isConnected() {
return connected.get();
}

/**
* Returns a greeting based on the current Java and JavaFX versions.
*/
Expand All @@ -12,4 +90,73 @@ public String getGreeting() {
String javafxVersion = System.getProperty("javafx.version");
return "Hello, JavaFX " + javafxVersion + ", running on Java " + javaVersion + ".";
}
}

/**
* Sends the value of {@link #messageToSend} to the current topic.
*
* @throws IOException if sending through the connection fails
*/
public void sendMessage() throws IOException {
connection.send(topic.get(), messageToSend.get());
messageToSend.set("");
}
Comment on lines +94 to +102
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Verify that the controller handles IOException from sendMessage.

The exception-based error handling is good: on success, the input is cleared (line 101); on failure, the exception prevents clearing so the user can retry. However, ensure the controller catches and handles the IOException appropriately (e.g., displaying an error message to the user).

Run this script to check how the controller handles exceptions from sendMessage():


🏁 Script executed:

#!/bin/bash
# Description: Verify HelloController handles IOException from sendMessage

# Search for sendMessage() calls and surrounding error handling
rg -n -A 10 -B 3 '\.sendMessage\s*\(' --type=java

Length of output: 3620


Error handling exists but lacks user-facing feedback.

The controller properly catches IOException from sendMessage() (HelloController line 86), and the input is correctly preserved on failure. However, error reporting is limited to stderr (System.err.println at line 87). Consider displaying the error to the user via an alert dialog or status message so they're aware the send failed, rather than only logging to stderr.

🤖 Prompt for AI Agents
In src/main/java/com/example/HelloModel.java around lines 94 to 102, the
sendMessage method is fine but the app currently only logs send failures to
stderr; update the controller catch block (where IOException is caught at
HelloController line ~86) to present a user-facing error (e.g., a dialog or
status bar update) showing a concise message plus exception.getMessage(), while
still preserving the input and keeping the existing stderr log for diagnostics;
if using Swing use JOptionPane.showMessageDialog, if using JavaFX use
Platform.runLater with an Alert, and ensure the UI-call is made on the UI
thread.


/**
* Connects to the current topic by creating a new subscription.
* Any previous subscription is closed first.
* Old messages are preserved if subscription creation fails.
* Incoming messages are added to {@link #messages} on the JavaFX thread.
*/
public void connectToTopic() {
disconnect();

// Make a backup of current messages in case subscription fails
var oldMessages = FXCollections.observableArrayList(messages);
// Clear messages for the new topic
messages.clear();

try {
// Start receiving new messages asynchronously
currentSubscription = connection.receive(topic.get(),
m -> runOnFx(() -> messages.add(m)));
// Mark as connected
connected.set(true);
} catch (Exception e) {
// Restore old messages if connection failed
messages.setAll(oldMessages);
connected.set(false);
System.err.println("Failed to connect to topic: " + e.getMessage());
}
}


/**
* Stops the active subscription, if one exists, and updates connection state.
*/
public void disconnect() {
if (currentSubscription != null) {
try {
if (currentSubscription.isOpen()) {
currentSubscription.close();
}
} catch (IOException e) {
System.err.println("Error closing subscription: " + e.getMessage());
}
currentSubscription = null;
connected.set(false);
}
}

/**
* Ensures that the given task runs on the JavaFX thread.
* Falls back to direct execution if JavaFX is not initialized (e.g. in tests).
*/
private static void runOnFx(Runnable task) {
try {
if (Platform.isFxApplicationThread()) task.run();
else Platform.runLater(task);
} catch (IllegalStateException notInitialized) {
task.run();
}
}
}
55 changes: 55 additions & 0 deletions src/main/java/com/example/NtfyConnection.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.example;

import java.io.Closeable;
import java.io.IOException;
import java.util.function.Consumer;

/**
* Represents a connection to a Ntfy-compatible notification service.
* Implementations of this interface provide basic operations for:
* * Sending messages to a specific topic
* * Subscribing to incoming messages from a topic
*/
public interface NtfyConnection {

/**
* Sends a message to the given topic.
*
* @param topic the topic to publish to (must not be null or blank)
* @param message the message content to send
* @throws IOException if the message cannot be delivered due to
* network errors or underlying I/O issues
*/
void send(String topic, String message) throws IOException;

/**
* Subscribes to a topic and receives messages asynchronously.
*
* @param topic the topic to subscribe to
* @param messageHandler callback invoked for every received message on a background thread
* @return a {@link Subscription} that controls the active message stream
*/
Subscription receive(String topic, Consumer<NtfyMessageDto> messageHandler);

/**
* Controls an active topic subscription.
* Encapsulates the logic to stop an active message stream.
*/
interface Subscription extends Closeable {

/**
* Closes this subscription and stops receiving messages.
*
* @throws IOException if closing fails
*/
@Override
void close() throws IOException;

/**
* Checks whether the subscription is still active.
*
* @return true if active, false otherwise
*/
boolean isOpen();
}
}
Loading
Loading