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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
target/
/.idea/
.env
package-lock.json
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>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>io.github.cdimascio</groupId>
<artifactId>dotenv-java</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock</artifactId>
<version>4.0.0-beta.15</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
70 changes: 67 additions & 3 deletions src/main/java/com/example/HelloController.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,86 @@
package com.example;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.geometry.Insets;
import javafx.scene.control.*;
import javafx.scene.layout.HBox;

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

private final HelloModel model = new HelloModel();
private final HelloModel model = new HelloModel(new NtfyConnectionImpl());
public ListView<NtfyMessageDto> messageView;

@FXML
private Label messageLabel;

@FXML
private TextField messageField;

@FXML
private void initialize() {
if (messageLabel != null) {
messageLabel.setText(model.getGreeting());
}
messageView.setItems(model.getMessages());

messageView.setCellFactory(list -> new ListCell<>() {
private final Label messageLabel = new Label();
private final HBox bubble = new HBox(messageLabel);
{
bubble.setPadding(new Insets(5, 10, 5, 10));
bubble.setMaxWidth(200);
messageLabel.setWrapText(true);
bubble.getStyleClass().add("chat-bubble");
}

@Override
protected void updateItem(NtfyMessageDto item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setGraphic(null);
} else {
// Format tid + text
java.time.LocalTime time = java.time.Instant.ofEpochSecond(item.time())
.atZone(java.time.ZoneId.systemDefault())
.toLocalTime();
String formattedTime = time.format(java.time.format.DateTimeFormatter.ofPattern("HH:mm"));

messageLabel.setText(formattedTime + "\n" + item.message());
setGraphic(bubble);
}
}
});

model.messageToSendProperty().bind(messageField.textProperty());

Comment on lines +57 to +58
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

Fix binding to avoid IllegalStateException.
Line 57 binds the model's messageToSend property to the TextField, turning it into a read-only binding. Any later call to HelloModel.setMessageToSend(...) will now throw RuntimeException: A bound value cannot be set once the view is initialized. Use a bidirectional binding so both the control and model can update the value safely.

-        model.messageToSendProperty().bind(messageField.textProperty());
+        messageField.textProperty().bindBidirectional(model.messageToSendProperty());
📝 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
model.messageToSendProperty().bind(messageField.textProperty());
messageField.textProperty().bindBidirectional(model.messageToSendProperty());
🤖 Prompt for AI Agents
In src/main/java/com/example/HelloController.java around lines 57-58, the code
currently does a unidirectional bind:
model.messageToSendProperty().bind(messageField.textProperty()); which makes the
model property read-only and causes IllegalStateException when
HelloModel.setMessageToSend(...) is called later; replace this with a
bidirectional binding by removing the bind(...) call and using
bindBidirectional(...) between model.messageToSendProperty() and
messageField.textProperty() so both view and model can update the value safely.

}

public void sendMessage(ActionEvent actionEvent) {
String message = messageField.getText();
if(message == null || message.isBlank()){
showTemporaryAlert("You must write something before sending!");
return;
}

model.sendMessage();
messageField.clear();
}

private void showTemporaryAlert(String alertMessage) {
Alert alert = new Alert(Alert.AlertType.WARNING);
alert.setHeaderText(null);
alert.setContentText(alertMessage);
alert.initOwner(messageField.getScene().getWindow());

alert.show();

new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {}
}).start();
}
}
4 changes: 2 additions & 2 deletions src/main/java/com/example/HelloFX.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
package com.example;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
Expand All @@ -13,7 +12,8 @@ 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.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
stage.setTitle("Chatt Client");
stage.setScene(scene);
stage.show();
}
Expand Down
46 changes: 43 additions & 3 deletions src/main/java/com/example/HelloModel.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,55 @@
package com.example;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;


/**
* Model layer: encapsulates application data and business logic.
*/
public class HelloModel {

private final NtfyConnection connection;

private final ObservableList<NtfyMessageDto> messages = FXCollections.observableArrayList();
private final StringProperty messageToSend = new SimpleStringProperty();

public HelloModel(NtfyConnection connection) {
this.connection = connection;
receiveMessage();
}

public ObservableList<NtfyMessageDto> getMessages() {
return messages;
}

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

public StringProperty messageToSendProperty() {
return messageToSend;
}

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

/**
* 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 + ".";
return "Chat Client by Adam";
}

public void sendMessage() {
connection.send(messageToSend.get());

}

public void receiveMessage() {
connection.receive(m->Platform.runLater(()->messages.add(m)));
}
}
17 changes: 17 additions & 0 deletions src/main/java/com/example/ManyParameters.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.example;

public class ManyParameters {

public ManyParameters(String computerName, int timeout, String method, int size, byte[] data){

}
Comment on lines +5 to +7
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

Constructor body is empty—parameters are not stored.

The constructor accepts five parameters but doesn't assign them to any fields or perform any operations. This means instances of ManyParameters won't retain the provided values.

If this class is meant to store these parameters, add corresponding fields and assignments:

 public class ManyParameters {
+    private final String computerName;
+    private final int timeout;
+    private final String method;
+    private final int size;
+    private final byte[] data;
+
     public ManyParameters(String computerName, int timeout, String method, int size, byte[] data){
-
+        this.computerName = computerName;
+        this.timeout = timeout;
+        this.method = method;
+        this.size = size;
+        this.data = data;
     }

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

🤖 Prompt for AI Agents
In src/main/java/com/example/ManyParameters.java around lines 5 to 7, the
constructor accepts five parameters but has an empty body so none of the values
are stored; add private final fields for computerName (String), timeout (int),
method (String), size (int) and data (byte[]) to the class and assign each
constructor parameter to its corresponding field (defensive-copy the byte[] into
a new array and store that) so the instance retains the provided values and
remains immutable.


static void main(){
ManyParametersBuilder builder = new ManyParametersBuilder();
builder
.setComputerName("localhost") // Fluent API
.setTimeout(10)
.setSize(0)
.createManyParameters();
}
Comment on lines +9 to +16
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

Non-standard main method signature.

The main method lacks the required String[] args parameter and is package-private. This method cannot serve as a Java application entry point.

Apply this diff to make it a valid entry point:

-    static void main(){
+    public static void main(String[] args){
         ManyParametersBuilder builder = new ManyParametersBuilder();
📝 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
static void main(){
ManyParametersBuilder builder = new ManyParametersBuilder();
builder
.setComputerName("localhost") // Fluent API
.setTimeout(10)
.setSize(0)
.createManyParameters();
}
public static void main(String[] args){
ManyParametersBuilder builder = new ManyParametersBuilder();
builder
.setComputerName("localhost") // Fluent API
.setTimeout(10)
.setSize(0)
.createManyParameters();
}
🤖 Prompt for AI Agents
In src/main/java/com/example/ManyParameters.java around lines 9 to 16, the main
method is declared as package-private and takes no parameters, so it cannot be
used as a Java application entry point; change its signature to public static
void main(String[] args) and keep the existing body so the JVM can invoke it;
ensure any references to args are added only if needed and adjust method
visibility to public.

}
38 changes: 38 additions & 0 deletions src/main/java/com/example/ManyParametersBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.example;

public class ManyParametersBuilder {
private String computerName;
private int timeout = 0;
private String method;
private int size = 0;
private byte[] data = null;

public ManyParametersBuilder setComputerName(String computerName) {
this.computerName = computerName;
return this;
}

public ManyParametersBuilder setTimeout(int timeout) {
this.timeout = timeout;
return this;
}

public ManyParametersBuilder setMethod(String method) {
this.method = method;
return this;
}

public ManyParametersBuilder setSize(int size) {
this.size = size;
return this;
}

public ManyParametersBuilder setData(byte[] data) {
this.data = data;
return this;
}

public ManyParameters createManyParameters() {
return new ManyParameters(computerName, timeout, method, size, data);
}
}
9 changes: 9 additions & 0 deletions src/main/java/com/example/NtfyConnection.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example;

import java.util.function.Consumer;

public interface NtfyConnection {
public boolean send(String message);

public void receive(Consumer<NtfyMessageDto> messageHandler);
}
62 changes: 62 additions & 0 deletions src/main/java/com/example/NtfyConnectionImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.example;
import io.github.cdimascio.dotenv.Dotenv;
import tools.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Objects;
import java.util.function.Consumer;

public class NtfyConnectionImpl implements NtfyConnection {

private final HttpClient http = HttpClient.newHttpClient();
private final String hostName;
private final ObjectMapper mapper = new ObjectMapper();

NtfyConnectionImpl(){
Dotenv dotenv = Dotenv.load();
hostName = Objects.requireNonNull(dotenv.get("HOST_NAME"));
}

public NtfyConnectionImpl(String hostName){
this.hostName = hostName;
}

@Override
public boolean send(String message) {
HttpRequest httpRequest = HttpRequest.newBuilder()
.POST(HttpRequest.BodyPublishers.ofString(message))
.uri(URI.create(hostName + "/mytopic"))
.build();
try {
// TODO: handle long blocking send requests to not freeze the JavaFX thread
// 1. Use thread send message?
// 2. Use async?
var response = http.send(httpRequest, HttpResponse.BodyHandlers.ofString());
return true;
} catch (IOException e) {
Comment on lines +34 to +39
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

Return false when the server rejects the message.
Line 38 returns true for any response that doesn’t throw, so 4xx/5xx replies are misreported as success and the UI will clear the message even though the server discarded it. Please inspect the status code and only report success on 2xx responses.

-            var response = http.send(httpRequest, HttpResponse.BodyHandlers.ofString());
-            return true;
+            var response = http.send(httpRequest, HttpResponse.BodyHandlers.ofString());
+            int status = response.statusCode();
+            return status >= 200 && status < 300;
🤖 Prompt for AI Agents
In src/main/java/com/example/NtfyConnectionImpl.java around lines 34 to 39, the
method currently returns true for any HTTP response that doesn't throw an
exception, which misreports 4xx/5xx replies as success; change the logic to
inspect the HttpResponse status code after http.send(...) and only return true
for 2xx responses (e.g., statusCode >= 200 && statusCode < 300), otherwise log
the non-2xx status and return false so the UI does not clear the message on
server rejection.

System.out.println("Error sending message");
} catch (InterruptedException e) {
System.out.println("Interrupted sending message");
}
return false;
}

@Override
public void receive(Consumer<NtfyMessageDto> messageHandler) {
HttpRequest httpRequest = HttpRequest.newBuilder()
.GET()
.uri(URI.create(hostName + "/mytopic/json?since=wBuD2KGEaAe0"))
.build();

http.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofLines())
.thenAccept(response -> response.body()
.map(s->
mapper.readValue(s, NtfyMessageDto.class))
.filter(message -> message.event().equals("message"))
.peek(System.out::println)
.forEach(messageHandler));
Comment on lines +55 to +60
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

Handle checked exceptions inside the stream pipeline.
Line 57 calls mapper.readValue, which declares throws IOException; placing it directly in the map lambda produces a compile-time error (Unhandled exception type IOException). Wrap the call and rethrow as an unchecked exception (and add import java.io.UncheckedIOException;) so the code compiles and surfaces parsing failures.

-                        .map(s->
-                                mapper.readValue(s, NtfyMessageDto.class))
+                        .map(s -> {
+                            try {
+                                return mapper.readValue(s, NtfyMessageDto.class);
+                            } catch (IOException e) {
+                                throw new UncheckedIOException("Failed to parse Ntfy message", e);
+                            }
+                        })
📝 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
.thenAccept(response -> response.body()
.map(s->
mapper.readValue(s, NtfyMessageDto.class))
.filter(message -> message.event().equals("message"))
.peek(System.out::println)
.forEach(messageHandler));
.thenAccept(response -> response.body()
.map(s -> {
try {
return mapper.readValue(s, NtfyMessageDto.class);
} catch (IOException e) {
throw new UncheckedIOException("Failed to parse Ntfy message", e);
}
})
.filter(message -> message.event().equals("message"))
.peek(System.out::println)
.forEach(messageHandler));
🤖 Prompt for AI Agents
In src/main/java/com/example/NtfyConnectionImpl.java around lines 55 to 60, the
stream lambda calls mapper.readValue which throws IOException and causes a
compile error; catch IOException inside the map lambda and rethrow it wrapped in
an UncheckedIOException, and add import java.io.UncheckedIOException; so the
stream compiles and parsing failures surface as unchecked exceptions.

}
}
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 id, long time, String event, String topic, String message) {}
13 changes: 13 additions & 0 deletions src/main/java/com/example/Singelton.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.example;

public class Singelton {
private final static Singelton instance = new Singelton();
Comment on lines +3 to +4
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

Fix the class name typo: "Singelton" should be "Singleton".

The class name contains a spelling error that affects the class name, filename, and all type references throughout the codebase. This impacts code professionalism and maintainability.

Apply this diff to correct the spelling:

-public class Singelton {
-    private final static Singelton instance = new Singelton();
+public class Singleton {
+    private final static Singleton instance = new Singleton();

Also rename the file from Singelton.java to Singleton.java.

📝 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 class Singelton {
private final static Singelton instance = new Singelton();
public class Singleton {
private final static Singleton instance = new Singleton();
🤖 Prompt for AI Agents
In src/main/java/com/example/Singelton.java around lines 3-4, the class name is
misspelled as "Singelton"; rename the class to "Singleton" and update the
filename to Singleton.java, then update all references across the codebase
(imports, variable types, constructors, factory methods, tests, and any build
configs) to use the corrected name; ensure the constructor and any static
INSTANCE variable or accessor keep the same behavior but use the new identifier,
run a compile to catch remaining references, and update package-level Javadoc or
comments if they mention the old name.


private Singelton() {

}

public static Singelton getInstance() {
return instance;
}
}
Comment on lines +3 to +13
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 the purpose and usage of this class.

This singleton class appears to be unused and unrelated to the PR objectives (adding message input functionality). The class has no state or behavior, and according to the PR summary, no other files reference it.

Run the following script to verify if this class is referenced anywhere in the codebase:

If the class is indeed unused, consider removing it to keep the codebase clean and aligned with YAGNI principles. If it's intended for future use, please add a comment explaining its purpose or defer its addition until it's actually needed.


🏁 Script executed:

#!/bin/bash
# Description: Search for any references to Singelton class in the codebase

# Search for class usage (accounting for the typo)
rg -n --type=java -C3 '\bSingelton\b'

Length of output: 784


Remove this unused class.

The verification confirms that Singelton is completely unused—it only references itself and has zero external dependencies in the codebase. This is dead code unrelated to the PR objectives and should be removed.

Additionally, note the typo in the class name: "Singelton" should be "Singleton".

🤖 Prompt for AI Agents
In src/main/java/com/example/Singelton.java lines 3-13: this class is dead code
and should be removed; delete the entire Singelton.java file from the codebase
(or move to archive) to eliminate the unused singleton, and if a correct
singleton implementation is needed elsewhere, create a properly named Singleton
class with correct spelling and intended usage in a separate change instead of
keeping this unused file.

4 changes: 4 additions & 0 deletions src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
module hellofx {
requires javafx.controls;
requires javafx.fxml;
requires io.github.cdimascio.dotenv.java;
requires java.net.http;
requires tools.jackson.databind;
requires javafx.graphics;

opens com.example to javafx.fxml;
exports com.example;
Expand Down
29 changes: 21 additions & 8 deletions src/main/resources/com/example/hello-view.fxml
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.control.Label?>

<StackPane xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="com.example.HelloController">
<children>
<Label fx:id="messageLabel" text="Hello, JavaFX!" />
</children>
</StackPane>

<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<?import javafx.geometry.Insets?>
<VBox alignment="CENTER" stylesheets="@style.css" xmlns="http://javafx.com/javafx/17.0.12" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.HelloController">

<Label fx:id="messageLabel" text="Adams Chat Client" />
<ListView fx:id="messageView" />


<HBox alignment="CENTER_RIGHT" spacing="5" prefHeight="40" maxWidth="600">
<VBox.margin>
<Insets top="10" left="10" right="10" bottom="10" />
</VBox.margin>

<TextField fx:id="messageField" prefHeight="40" promptText="Write something here.." HBox.hgrow="ALWAYS" />
<Button onAction="#sendMessage" prefHeight="40" prefWidth="80" text="Send" />
</HBox>

</VBox>
Loading
Loading