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
8 changes: 8 additions & 0 deletions javafx.iml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="AdditionalModuleElements">
<content url="file://$MODULE_DIR$" dumb="true">
<sourceFolder url="file://$MODULE_DIR$/src/test" isTestSource="true" />
</content>
</component>
</module>
24 changes: 23 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>com.example</groupId>
Expand All @@ -15,26 +16,32 @@
<assertj.core.version>3.27.6</assertj.core.version>
<mockito.version>5.20.0</mockito.version>
<javafx.version>25</javafx.version>
<httpclient.version>5.3.1</httpclient.version>
</properties>

<dependencies>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj.core.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
Expand All @@ -45,7 +52,21 @@
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
</dependency>

<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20240303</version>
</dependency>

<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>${httpclient.version}</version>
</dependency>
Comment on lines +56 to +66
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

🧩 Analysis chain

Verify dependency versions and security advisories.

Since the project uses fixed versions for org.json (20240303, from March 2024) and httpclient5 (5.3.1), please verify these are current and check for any security advisories.

Run the following script to check for the latest versions and security advisories:


🏁 Script executed:

#!/bin/bash
# Check latest versions from Maven Central
echo "=== Checking org.json:json latest version ==="
curl -s "https://search.maven.org/solrsearch/select?q=g:org.json+AND+a:json&rows=1&wt=json" | jq -r '.response.docs[0].latestVersion'

echo "=== Checking httpclient5 latest version ==="
curl -s "https://search.maven.org/solrsearch/select?q=g:org.apache.httpcomponents.client5+AND+a:httpclient5&rows=1&wt=json" | jq -r '.response.docs[0].latestVersion'

echo "=== Checking for security advisories ==="
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: MAVEN, package: "org.json:json") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
      vulnerableVersionRange
      firstPatchedVersion {
        identifier
      }
    }
  }
}' 

gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: MAVEN, package: "org.apache.httpcomponents.client5:httpclient5") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
      vulnerableVersionRange
      firstPatchedVersion {
        identifier
      }
    }
  }
}'

Length of output: 1094


Update outdated dependencies in pom.xml.

HttpClient 5.5.x is stable and production ready, and actively maintained, while the current version (5.3.1) is from an older release series. Latest stable httpclient5 is 5.5.1.

org.json latest version is 20250517 (May 2025), but the pom.xml uses 20240303 (March 2024)—17 months behind. Both current versions lack recent security patches and improvements.

Update:

  • org.json:json from 20240303 to 20250517
  • httpclient5 from 5.3.1 to 5.5.1
🤖 Prompt for AI Agents
In pom.xml around lines 56 to 66, the project is using outdated dependency
versions: org.json:json is set to 20240303 and httpclient5 is resolved to
${httpclient.version} which currently points to 5.3.1; update org.json:json to
version 20250517 and update httpclient5 to 5.5.1 (either by changing the
explicit <version> for the httpclient5 dependency to 5.5.1 or by updating the
httpclient.version property in the pom.xml to 5.5.1) so the POM uses
org.json:json:20250517 and org.apache.httpcomponents.client5:httpclient5:5.5.1.


</dependencies>

<build>
<plugins>
<plugin>
Expand All @@ -55,7 +76,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 +86,5 @@
</plugin>
</plugins>
</build>

</project>
34 changes: 34 additions & 0 deletions src/main/java/com/example/ChatMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.example;

public class ChatMessage {
private final String id;
private final String username;
private final String message;
private final String timestamp;
private final String fileName;
private final String fileUrl;
private final String mimeType;

public ChatMessage(String id, String username, String message, String timestamp,
String fileName, String fileUrl, String mimeType) {
this.id = id;
this.username = username;
this.message = message;
this.timestamp = timestamp;
this.fileName = fileName;
this.fileUrl = fileUrl;
this.mimeType = mimeType;
}

public ChatMessage(String id, String username, String message, String timestamp) {
this(id, username, message, timestamp, null, null, null);
}

public String getId() { return id; }
public String getUsername() { return username; }
public String getMessage() { return message; }
public String getTimestamp() { return timestamp; }
public String getFileName() { return fileName; }
public String getFileUrl() { return fileUrl; }
public String getMimeType() { return mimeType; }
}
32 changes: 32 additions & 0 deletions src/main/java/com/example/EnvLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.example;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class EnvLoader {

private static Map<String, String> env = new HashMap<>();

static {
try (BufferedReader reader = new BufferedReader(new FileReader(".env"))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty() || line.startsWith("#")) continue;
String[] parts = line.split("=", 2);
if (parts.length == 2) {
env.put(parts[0].trim(), parts[1].trim());
}
}
} catch (IOException e) {
System.err.println("Warning: .env file not found or could not be read");
}
}

public static String get(String key) {
return env.get(key);
}
}
118 changes: 108 additions & 10 deletions src/main/java/com/example/HelloController.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,120 @@
package com.example;

import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.fxml.Initializable;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import javafx.stage.FileChooser;

/**
* Controller layer: mediates between the view (FXML) and the model.
*/
public class HelloController {
import java.io.File;
import java.net.URL;
import java.time.format.DateTimeFormatter;
import java.util.ResourceBundle;
import java.util.function.Predicate;

public class HelloController implements Initializable {

@FXML private ListView<ChatMessage> chatList;
@FXML private TextField inputField;
@FXML private TextField usernameField;
@FXML private CheckBox hideMyMessagesCheck;

private final HelloModel model = new HelloModel();
private final ObservableList<ChatMessage> masterList = FXCollections.observableArrayList();
private FilteredList<ChatMessage> filteredList;

private String getCurrentUsername() {
String u = usernameField.getText();
if (u == null) return "Anonymous";
u = u.trim();
return u.isEmpty() ? "Anonymous" : u;
}

@Override
public void initialize(URL url, ResourceBundle rb) {
filteredList = new FilteredList<>(masterList, msg -> true);
chatList.setItems(filteredList);

chatList.setCellFactory(list -> new ListCell<>() {
@Override
protected void updateItem(ChatMessage msg, boolean empty) {
super.updateItem(msg, empty);
if (empty || msg == null) { setGraphic(null); return; }

Text user = new Text(msg.getUsername());
user.setStyle("-fx-font-weight: bold;");
Text time = new Text(" (" + msg.getTimestamp() + ")\n");
time.setStyle("-fx-fill: gray; -fx-font-size: 12px;");

if (msg.getFileName() != null && msg.getFileUrl() != null) {
if (msg.getMimeType() != null && msg.getMimeType().startsWith("image/")) {
try {
ImageView imageView = new ImageView(new Image(msg.getFileUrl(), true));
imageView.setFitWidth(200);
imageView.setPreserveRatio(true);
Text messageText = new Text(msg.getMessage() + "\n");
messageText.setStyle("-fx-font-size: 14px;");
setGraphic(new TextFlow(user, time, messageText, imageView));
} catch (Exception e) { e.printStackTrace(); }
} else {
Hyperlink link = new Hyperlink(msg.getFileName());
final String fileUrl = msg.getFileUrl();
link.setOnAction(ev -> HelloFX.hostServices().showDocument(fileUrl));
Text messageText = new Text(msg.getMessage() + "\n");
messageText.setStyle("-fx-font-size: 14px;");
setGraphic(new TextFlow(user, time, messageText, link));
}
} else {
Text text = new Text(msg.getMessage());
text.setStyle("-fx-font-size: 14px;");
setGraphic(new TextFlow(user, time, text));
}
}
});

hideMyMessagesCheck.selectedProperty().addListener((obs, oldVal, newVal) -> updateFilterPredicate());
usernameField.textProperty().addListener((obs, oldVal, newVal) -> updateFilterPredicate());

model.loadHistory(msg -> Platform.runLater(() -> masterList.add(msg)));
model.listenForMessages(msg -> Platform.runLater(() -> masterList.add(msg)));
}

private void updateFilterPredicate() {
final String current = getCurrentUsername();
final boolean hideMine = hideMyMessagesCheck.isSelected();
Predicate<ChatMessage> pred = msg -> !hideMine || !current.equals(msg.getUsername());
filteredList.setPredicate(pred);
}

@FXML
private Label messageLabel;
private void onSend() {
String user = getCurrentUsername();
String msg = inputField.getText().trim();
if (msg.isEmpty()) return;
inputField.clear();

new Thread(() -> {
try { model.sendMessage(user, msg); }
catch (Exception e) { e.printStackTrace(); }
}).start();
}

@FXML
private void initialize() {
if (messageLabel != null) {
messageLabel.setText(model.getGreeting());
}
private void onAttachFile() {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Select a file to send");
File file = fileChooser.showOpenDialog(chatList.getScene().getWindow());
if (file != null) new Thread(() -> model.sendFile(getCurrentUsername(), file)).start();
}
}
21 changes: 14 additions & 7 deletions src/main/java/com/example/HelloFX.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
package com.example;

import javafx.application.Application;
import javafx.application.HostServices;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class HelloFX extends Application {
public static HostServices hostServices;

public static HostServices hostServices() {
return hostServices;
}

@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");
hostServices = getHostServices();
FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/example/hello-view.fxml"));
Parent root = loader.load();
Scene scene = new Scene(root, 720, 520);
scene.getStylesheets().add(getClass().getResource("/css/style.css").toExternalForm());
stage.setTitle("JavaFX NTFY Chat — mats_notiser");
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

Remove hardcoded username from window title.

The window title includes a hardcoded username "mats_notiser", which appears to be developer-specific. Since the UI includes a usernameField, consider either:

  1. Using a generic title without a username
  2. Dynamically updating the title when the username changes

Example for a generic title:

-        stage.setTitle("JavaFX NTFY Chat — mats_notiser");
+        stage.setTitle("JavaFX NTFY Chat");
📝 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
stage.setTitle("JavaFX NTFY Chat — mats_notiser");
stage.setTitle("JavaFX NTFY Chat");
🤖 Prompt for AI Agents
In src/main/java/com/example/HelloFX.java around line 24, remove the hardcoded
username in the window title ("mats_notiser") and either set a generic title
(e.g., "JavaFX NTFY Chat") or make the title dynamic by updating/binding it to
the usernameField so it reflects the entered username; implement this by
replacing the literal title string with a generic one or by adding a
listener/binding on usernameField.textProperty() to update stage.setTitle(...)
when the username changes (use a fallback when the field is empty).

stage.setScene(scene);
stage.show();
}

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

}
}
Loading
Loading