Skip to content
Open
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
25 changes: 25 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>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.2</version>
</dependency>
<dependency>
<groupId>io.github.cdimascio</groupId>
<artifactId>java-dotenv</artifactId>
<version>5.2.2</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 All @@ -63,6 +79,15 @@
<noManPages>true</noManPages>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>25</source>
<target>25</target>
<compilerArgs>--enable-preview</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
</project>
60 changes: 54 additions & 6 deletions src/main/java/com/example/HelloController.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,70 @@
package com.example;

import javafx.collections.ListChangeListener;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.input.KeyCode;
import javafx.stage.FileChooser;

import java.io.File;

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

private final HelloModel model = new HelloModel();
@FXML private TextArea inputField;
@FXML private Button sendButton;
@FXML private TextArea chatArea;
@FXML private Button sendFileButton;


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

@FXML
private Label messageLabel;

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

model.getMessages().addListener((ListChangeListener<NtfyMessageDto>) change ->{
while(change.next()) {
if (change.wasAdded()) {
for (var msg : change.getAddedSubList()) {
chatArea.appendText(msg.message() + "\n");
}
}
}
});

sendFileButton.setOnAction(e -> attachFile());
sendButton.setOnAction(e -> sendMessage());

inputField.setOnKeyPressed(event -> {
if (event.getCode() == KeyCode.ENTER && !event.isShiftDown()) {
sendMessage();
event.consume();
}
});
}
@FXML
private void sendMessage() {
String msg = inputField.getText().trim();
if (msg.isEmpty()) return;

chatArea.appendText("Du: " + msg + "\n");
model.setMessageToSend(msg);
model.sendMessage();
inputField.clear();
}
@FXML
private void attachFile() {
FileChooser chooser = new FileChooser();
chooser.setTitle("Välj fil att bifoga");

File file = chooser.showOpenDialog(sendFileButton.getScene().getWindow());
if (file != null) {
model.sendFile(file);
chatArea.appendText("Du skickade fil: " + file.getName() + "\n");
}
Comment on lines +60 to 68
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 feedback when file send fails.

model.sendFile(file) can fail silently (returns false or throws). The user sees "Du skickade fil:" regardless of outcome. Consider checking the result and providing appropriate feedback.

     @FXML
     private void attachFile() {
         FileChooser chooser = new FileChooser();
         chooser.setTitle("Välj fil att bifoga");

         File file = chooser.showOpenDialog(sendFileButton.getScene().getWindow());
         if (file != null) {
-            model.sendFile(file);
-            chatArea.appendText("Du skickade fil: " + file.getName() + "\n");
+            boolean success = model.sendFile(file);
+            if (success) {
+                chatArea.appendText("Du skickade fil: " + file.getName() + "\n");
+            } else {
+                chatArea.appendText("Kunde inte skicka fil: " + file.getName() + "\n");
+            }
         }
     }

Note: This requires changing HelloModel.sendFile() to return boolean.

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

🤖 Prompt for AI Agents
In src/main/java/com/example/HelloController.java around lines 60 to 68, the
controller currently calls model.sendFile(file) but always appends "Du skickade
fil:" regardless of success; change the call to check the result and only append
success text when sendFile returns true, otherwise append an error message (and
catch exceptions). Update HelloModel.sendFile(...) to return boolean indicating
success/failure (and avoid throwing silently), have the controller handle false
return and any caught exceptions by appending a failure message to chatArea (or
showing an alert) with brief context and the file name.

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

@Override
public void start(Stage stage) throws Exception {
System.getenv("HOST_NAME");
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");
stage.setTitle("JavaFX Chat App");
stage.setScene(scene);
stage.show();
}

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

}
71 changes: 67 additions & 4 deletions src/main/java/com/example/HelloModel.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,78 @@
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;
import java.io.*;
import java.net.URI;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.UUID;

/**
* Model layer: encapsulates application data and business logic.
*/
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 StringProperty messageToSend = new SimpleStringProperty("");
private final ObservableList<NtfyMessageDto> messages = FXCollections.observableArrayList();
private final String clientId = UUID.randomUUID().toString();

//Konstuktor för prod
public HelloModel() {
this.connection = new NtfyConnectionImpl();
receiveMessage();
}

//Konstuktor för test
public HelloModel(NtfyConnection connection) {
this.connection = connection;
receiveMessage();
}

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

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

public void sendMessage() {
String text = messageToSend.get().trim();
if (text.isEmpty()) return;

connection.send(text);

messageToSend.set("");
}

public void sendFile(File file) {
try {
connection.sendFile(file.toPath());
} catch (IOException e) {
e.printStackTrace();
}
}
Comment on lines +56 to +62
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

sendFile provides no feedback on failure.

The method catches IOException and prints a stack trace but returns void, leaving the caller unaware of success or failure. This makes it difficult to provide user feedback in the controller.

-    public void sendFile(File file) {
+    public boolean sendFile(File file) {
         try {
-            connection.sendFile(file.toPath());
+            return connection.sendFile(file.toPath());
         } catch (IOException e) {
             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
public void sendFile(File file) {
try {
connection.sendFile(file.toPath());
} catch (IOException e) {
e.printStackTrace();
}
}
public boolean sendFile(File file) {
try {
return connection.sendFile(file.toPath());
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
🤖 Prompt for AI Agents
In src/main/java/com/example/HelloModel.java around lines 56-62, the sendFile
method currently swallows IOException and returns void so callers get no
success/failure signal; change the method to propagate the error instead: update
the signature to declare throws IOException, remove the try/catch (or rethrow
the caught exception), and let callers (controllers) handle the exception and
provide user feedback; update any call sites to handle or declare the
IOException accordingly.


public void receiveMessage() {
connection.receive(m ->
Platform.runLater(() -> messages.add(m))
);
}
}









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.io.FileNotFoundException;
import java.nio.file.Path;
import java.util.function.Consumer;

public interface NtfyConnection {
boolean send (String message);

void receive (Consumer<NtfyMessageDto> messageHandler);

boolean sendFile(Path filePath) throws FileNotFoundException;
}
95 changes: 95 additions & 0 deletions src/main/java/com/example/NtfyConnectionImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package com.example;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.cdimascio.dotenv.Dotenv;

import java.io.File;
import java.io.FileNotFoundException;
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.nio.file.Files;
import java.nio.file.Path;
import java.util.UUID;
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();

public NtfyConnectionImpl() {
Dotenv dotenv = Dotenv.load();
String host = dotenv.get("HOST_NAME");

if (host == null || host.isEmpty()) {
throw new IllegalStateException("HOST_NAME has to be defined");
}
this.hostName = host;
}

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

@Override
public boolean send (String message) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(hostName + "/mytopic"))
.POST(HttpRequest.BodyPublishers.ofString(message))
.build();

try {
http.send(request, HttpResponse.BodyHandlers.discarding());
return true;

} catch (IOException | InterruptedException e) {
System.out.println("Error sending message!");
return false;
}
Comment on lines +49 to +52
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 interrupt flag when catching InterruptedException.

Catching InterruptedException without restoring the interrupt status can cause issues with thread pool shutdown and cancellation. The same applies to sendFile().

         } catch (IOException | InterruptedException e) {
+            if (e instanceof InterruptedException) {
+                Thread.currentThread().interrupt();
+            }
             System.out.println("Error sending message!");
             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
} catch (IOException | InterruptedException e) {
System.out.println("Error sending message!");
return false;
}
} catch (IOException | InterruptedException e) {
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
System.out.println("Error sending message!");
return false;
}
🤖 Prompt for AI Agents
In src/main/java/com/example/NtfyConnectionImpl.java around lines 49 to 52, the
catch block groups IOException and InterruptedException and prints a message
without restoring the interrupt status; split the exceptions so
InterruptedException is caught separately, call
Thread.currentThread().interrupt() to restore the interrupt flag, and rethrow or
return appropriately (while keeping IOException handling as before); apply the
same change in sendFile() so InterruptedException is handled separately and the
thread interrupt flag is restored.

}

@Override
public boolean sendFile(Path filePath) throws FileNotFoundException {
if (!Files.exists(filePath)) {
throw new FileNotFoundException("File does not exist: " + filePath);
}

HttpRequest httpRequest = HttpRequest.newBuilder()
.uri(URI.create(hostName + "/mytopic"))
.header("Content-type", "application/octet-stream")
.POST(HttpRequest.BodyPublishers.ofFile(filePath))
.build();

try {
http.send(httpRequest, HttpResponse.BodyHandlers.discarding());
return true;
} catch (IOException | InterruptedException e) {
System.out.println("Error sending file: " + e.getMessage());
return false;
}
}

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

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(handler));
}
}
10 changes: 10 additions & 0 deletions src/main/java/com/example/NtfyMessageDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
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) {
public record Attachment(String name, String type, long size, String url) {
}
}

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

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

private Singelton() {}

public static Singelton getInstance() {
return instance;
}
}
5 changes: 4 additions & 1 deletion src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
module hellofx {
requires javafx.controls;
requires javafx.fxml;
requires java.net.http;
requires com.fasterxml.jackson.databind;
requires java.dotenv;

opens com.example to javafx.fxml;
opens com.example to javafx.fxml, com.fasterxml.jackson.databind;
exports com.example;
}
27 changes: 19 additions & 8 deletions src/main/resources/com/example/hello-view.fxml
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
<?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.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<VBox spacing="10" style="-fx-background-color: GRAY;" xmlns="http://javafx.com/javafx/17.0.12" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.HelloController">

<padding>
<Insets bottom="10" left="10" right="10" top="10" />
</padding>

<TextArea fx:id="chatArea" editable="false" wrapText="true" prefHeight="400" />

<HBox spacing="5">
<TextArea fx:id="inputField" prefRowCount="2" promptText="Skriv meddelande..." style="-fx-background-color: WHITE;" wrapText="true" HBox.hgrow="ALWAYS" />
<Button fx:id="sendButton" text="Skicka" />
<Button fx:id="sendFileButton" text="Bifoga fil"/>
</HBox>
</VBox>
Loading
Loading