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
26 changes: 24 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,28 @@
<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>
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock</artifactId>
<version>4.0.0-beta.15</version>
<scope>test</scope>
</dependency>
Comment on lines +58 to +63
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

Consider using a stable WireMock version.

The project uses WireMock version 4.0.0-beta.15, which is a beta release and may have stability or compatibility issues.


🌐 Web query:

What is the latest stable version of WireMock?

💡 Result:

The latest stable WireMock release is 3.13.1 (released June 13, 2025). [1][2]

Sources:
[1] WireMock blog — "WireMock 3.13.1 Released" (June 13, 2025).
[2] GitHub release — wiremock/wiremock tag 3.13.1.


Use WireMock 3.13.1, the latest stable release.

The project currently uses WireMock 4.0.0-beta.15, a beta release that may have stability or compatibility issues. The latest stable version is 3.13.1, which should be preferred for production dependencies.

🤖 Prompt for AI Agents
In pom.xml around lines 58 to 63, replace the WireMock dependency version
4.0.0-beta.15 with the stable release 3.13.1; keep the groupId, artifactId and
<scope>test</scope> unchanged so tests continue to use WireMock but now
reference the stable 3.13.1 version.

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
Expand All @@ -55,7 +77,7 @@
<configuration>
<mainClass>com.example.HelloFX</mainClass>
<options>
<option>--enable-native-access=javafx.graphics</option>
<option>--enable-native-access=javafx.graphics</option>
</options>
<launcher>javafx</launcher>
<stripDebug>true</stripDebug>
Expand All @@ -65,4 +87,4 @@
</plugin>
</plugins>
</build>
</project>
</project>
71 changes: 60 additions & 11 deletions src/main/java/com/example/HelloController.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,71 @@
package com.example;

import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.*;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;

/**
* 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());
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

Tight coupling makes testing difficult.

The HelloModel is instantiated with a hardcoded NtfyConnectionImpl, which prevents dependency injection and makes unit testing challenging. Consider accepting the model (or connection) as a constructor parameter or through a setter method.

Apply this diff to enable dependency injection:

+    private HelloModel model;
+
+    public HelloController() {
+        this(new HelloModel(new NtfyConnectionImpl()));
+    }
+
+    // Package-private constructor for testing
+    HelloController(HelloModel model) {
+        this.model = model;
+    }

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

Alternatively, use a factory pattern or dependency injection framework.

📝 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
private final HelloModel model = new HelloModel(new NtfyConnectionImpl());
private HelloModel model;
public HelloController() {
this(new HelloModel(new NtfyConnectionImpl()));
}
// Package-private constructor for testing
HelloController(HelloModel model) {
this.model = model;
}
🤖 Prompt for AI Agents
In src/main/java/com/example/HelloController.java around line 15: the controller
currently instantiates HelloModel with a hardcoded new NtfyConnectionImpl(),
causing tight coupling that prevents DI and unit testing; change the class to
accept a HelloModel (or at minimum a NtfyConnection) via constructor injection
(add a constructor taking HelloModel or NtfyConnection and assign it to the
final field) and remove the direct new expression so tests can pass a mock;
optionally provide a no-arg constructor that delegates to the default
NtfyConnectionImpl for production.


@FXML
private Label messageLabel;
@FXML private Label messageLabel;
@FXML private ListView<NtfyMessageDto> messageView;
@FXML private TextArea messageInput;

private final DateTimeFormatter timeFormatter =
DateTimeFormatter.ofPattern("HH:mm:ss").withZone(ZoneId.systemDefault());

@FXML
private void initialize() {
if (messageLabel != null) {
messageLabel.setText(model.getGreeting());
}
//visa välkomstmeddelande
messageLabel.setText(model.getGreeting());

messageView.setItems(model.getMessages());

messageInput.textProperty().bindBidirectional(model.messageToSendProperty());

//formatera meddelanden i ListView
messageView.setCellFactory(lv -> new ListCell<>() {
@Override
protected void updateItem(NtfyMessageDto msg, boolean empty) {
super.updateItem(msg, empty);
if (empty || msg == null) {
setText(null);
} else {
setText("[" + timeFormatter.format(Instant.ofEpochMilli(msg.time())) + "] " + msg.message());
}
}
});

// Scrolla automatiskt till senaste meddelandet
model.getMessages().addListener((javafx.collections.ListChangeListener<NtfyMessageDto>) change -> {
while (change.next()) {
if (change.wasAdded()) {
Platform.runLater(() -> {
int size = messageView.getItems().size();
if (size > 0) {
messageView.scrollTo(size - 1);
}
});
}
}
});
}

@FXML
private void sendMessage(ActionEvent event) {
//skicka asynkront – HelloModel hanterar rensning och callback
model.sendMessageAsync(success -> {
if (!success) {
Platform.runLater(() -> {
Alert alert = new Alert(Alert.AlertType.ERROR, "Kunde inte skicka meddelandet.");
alert.show();
});
}
});
}
}
}
2 changes: 1 addition & 1 deletion src/main/java/com/example/HelloFX.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class HelloFX extends Application {
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);
Scene scene = new Scene(root, 1280, 480);
stage.setTitle("Hello MVC");
stage.setScene(scene);
stage.show();
Expand Down
83 changes: 71 additions & 12 deletions src/main/java/com/example/HelloModel.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,74 @@
package com.example;

/**
* Model layer: encapsulates application data and business logic.
*/
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.util.function.Consumer;

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 final NtfyConnection connection;
private final ObservableList<NtfyMessageDto> messages = FXCollections.observableArrayList();
private final StringProperty messageToSend = new SimpleStringProperty("");

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

private void startReceiving() {
connection.receive(incoming -> {
if (incoming == null || incoming.message() == null || incoming.message().isBlank()) {
return;
}
runOnFx(() -> messages.add(incoming));
});
}

public void sendMessageAsync(Consumer<Boolean> callback) {
String msg = messageToSend.get();
if (msg == null || msg.isBlank()) {
callback.accept(false);
return;
}

try {
connection.send(msg, success -> {
if (success) {
runOnFx(() -> {
if (msg.equals(messageToSend.get())) {
messageToSend.set("");
}
});
callback.accept(true);
} else {
callback.accept(false);
}
});
} catch (Exception e) {
// FÅNGA ALLA EXCEPTIONS HÄR!
System.err.println("Exception during send: " + e.getMessage());
callback.accept(false);
}
}

private static void runOnFx(Runnable task) {
try {
if (Platform.isFxApplicationThread()) {
task.run();
} else {
Platform.runLater(task);
}
} catch (Exception e) {
task.run(); // fallback i tester
}
}

public ObservableList<NtfyMessageDto> getMessages() { return messages; }
public String getMessageToSend() { return messageToSend.get(); }
public StringProperty messageToSendProperty() { return messageToSend; }
public void setMessageToSend(String v) { messageToSend.set(v); }
public String getGreeting() { return "Welcome to ChatApp"; }
}
19 changes: 19 additions & 0 deletions src/main/java/com/example/ManyParameters.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.example;

public class ManyParameters {

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

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

Empty constructor body - incomplete implementation.

The constructor accepts five parameters but doesn't store or use them. Since the class declares no fields, constructed instances will have no state. This makes the builder pattern ineffective, as ManyParametersBuilder.createManyParameters() produces empty objects.

Add 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 8, the
constructor accepts parameters but has an empty body and the class declares no
fields; 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 the corresponding field inside the constructor; also
add simple getters for each field (or make fields non-final with setters if
mutability is required) and update the builder
(ManyParametersBuilder.createManyParameters()) to populate these new fields when
constructing instances.



static void main() {
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() signature.

This method cannot serve as a Java application entry point because it's package-private and lacks the String[] args parameter. If it's intended as an entry point, use the standard signature. If it's just a demo method, consider renaming it to avoid confusion.

Apply this diff to fix the signature for a proper entry point:

-    static void main() {
+    public static void main(String[] args) {
📝 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() {
public static void main(String[] args) {
🤖 Prompt for AI Agents
In src/main/java/com/example/ManyParameters.java around line 11, the main method
is package-private and missing the args parameter so it cannot be used as a Java
entry point; change the signature to the standard public static void
main(String[] args) so the JVM can locate and invoke it (if this method was
intended only as a demo, alternatively rename it instead of making it public).

ManyParametersBuilder builder = new ManyParametersBuilder();
builder
.setComputerName("localhost") //Fluent API
.setTimeout(10)
.setSize(0)
.createManyParameters();
Comment on lines +12 to +17
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

Unused object creation.

The ManyParameters instance created by the builder is immediately discarded. Either assign it to a variable for further use or remove this demonstration code if it serves no purpose.

Apply this diff to capture the result:

-        ManyParametersBuilder builder = new ManyParametersBuilder();
-        builder
+        ManyParameters params = new ManyParametersBuilder()
                 .setComputerName("localhost")   //Fluent API
                 .setTimeout(10)
                 .setSize(0)
                 .createManyParameters();
+        // Use params...
📝 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
ManyParametersBuilder builder = new ManyParametersBuilder();
builder
.setComputerName("localhost") //Fluent API
.setTimeout(10)
.setSize(0)
.createManyParameters();
ManyParameters params = new ManyParametersBuilder()
.setComputerName("localhost") //Fluent API
.setTimeout(10)
.setSize(0)
.createManyParameters();
// Use params...
🤖 Prompt for AI Agents
In src/main/java/com/example/ManyParameters.java around lines 12 to 17, the
builder call creates a ManyParameters instance that is immediately discarded;
either assign the returned object to a variable for later use (e.g.,
ManyParameters params = builder...createManyParameters();) or remove the
builder/demo code if it has no purpose; update subsequent code to use the
assigned variable where appropriate or delete the unused lines to avoid dead
code.

}
}
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;
Comment on lines +4 to +8
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 initialization.

Only data is explicitly initialized to null, while computerName and method are implicitly null. For consistency and clarity, either explicitly initialize all reference types or rely on implicit initialization for all.

Apply this diff for consistency:

     private String computerName;
     private int timeout = 0;
     private String method;
     private int size = 0;
-    private byte[] data = null;
+    private byte[] data;
📝 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
private String computerName;
private int timeout = 0;
private String method;
private int size = 0;
private byte[] data = null;
private String computerName;
private int timeout = 0;
private String method;
private int size = 0;
private byte[] data;
🤖 Prompt for AI Agents
In src/main/java/com/example/ManyParametersBuilder.java around lines 4 to 8, the
field initializations are inconsistent: data is explicitly set to null while
computerName and method are left implicit; make them consistent by explicitly
initializing reference fields to null (e.g., change the declarations for
computerName and method to "private String computerName = null;" and "private
String method = null;") so all reference types use the same explicit null
initialization.


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);
}
}
12 changes: 12 additions & 0 deletions src/main/java/com/example/NtfyConnection.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.example;

import java.util.function.Consumer;

interface NtfyConnection {
void send(String message, Consumer<Boolean> callback);
void receive(Consumer<NtfyMessageDto> handler);
}

//public boolean send(String message);

//public void receive(Consumer<NtfyMessageDto> messageHandler);
87 changes: 87 additions & 0 deletions src/main/java/com/example/NtfyConnectionImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.example;

import io.github.cdimascio.dotenv.Dotenv;
import tools.jackson.databind.ObjectMapper;
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

Fix ObjectMapper import package

tools.jackson.databind.ObjectMapper does not exist, so this file will not compile. Please correct the import to the standard Jackson package.

-import tools.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectMapper;
📝 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
import tools.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectMapper;
🤖 Prompt for AI Agents
In src/main/java/com/example/NtfyConnectionImpl.java around line 4, the import
uses a non-existent package "tools.jackson.databind.ObjectMapper"; replace it
with the correct Jackson import "com.fasterxml.jackson.databind.ObjectMapper"
(and remove any leftover incorrect imports), then re-run compilation to verify
the class resolves correctly.


import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.Objects;
import java.util.function.Consumer;

public class NtfyConnectionImpl implements NtfyConnection {

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

public NtfyConnectionImpl() {
Dotenv dotenv = Dotenv.load();
hostName = Objects.requireNonNull(dotenv.get("HOST_NAME"));
this.http = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
}

public NtfyConnectionImpl(String hostName) {
this.hostName = hostName;
this.http = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build();
}

@Override
public void send(String message, Consumer<Boolean> callback) {
HttpRequest request = HttpRequest.newBuilder()
.POST(HttpRequest.BodyPublishers.ofString(message))
.uri(URI.create(hostName + "/mytopic"))
.header("Cache", "no")
.timeout(Duration.ofSeconds(10)) //request timeout
.build();

http.sendAsync(request, HttpResponse.BodyHandlers.discarding())
.thenAccept(response -> {
boolean success = response.statusCode() >= 200 && response.statusCode() < 300;
callback.accept(success);
})
.exceptionally(throwable -> {
System.err.println("Error sending message: " + throwable.getMessage());
callback.accept(false);
return null;
});
}

@Override
public void receive(Consumer<NtfyMessageDto> messageHandler) {
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(URI.create(hostName + "/mytopic/json"))
.timeout(Duration.ofSeconds(30)) //timeout för receive
.build();

http.sendAsync(request, HttpResponse.BodyHandlers.ofLines())
.thenAccept(response -> {
try {
response.body()
.map(line -> {
try {
return mapper.readValue(line, NtfyMessageDto.class);
} catch (Exception e) {
System.err.println("Failed to parse message: " + line);
return null;
}
})
.filter(Objects::nonNull)
.forEach(messageHandler);
} catch (Exception e) {
System.err.println("Stream processing error: " + e.getMessage());
}
})
.exceptionally(ex -> {
System.err.println("Error receiving messages: " + ex.getMessage());
return null;
});
}
}
Loading
Loading