From 1260371d40a75ea7e6108d8e7c3683d85e746ee1 Mon Sep 17 00:00:00 2001
From: Felipe <139709626+FelipeKobra@users.noreply.github.com>
Date: Tue, 14 Jan 2025 00:45:22 -0300
Subject: [PATCH] - Fix showing terminal buffered messages a lot of times on
closing the server - No more Exceptions when exiting of the application via
`CTRL + C` - Use GraalVM instead of Launch4j for creating Windows native
images. Launch4j made necessary for the user to have JRE - Add JLine in
bundles for better compatibility with the native image build - Change
Deprecated JLine Jansi to JNI Version
---
.idea/artifacts/ChatClient_jar.xml | 2 +-
.idea/artifacts/ChatServer_jar.xml | 2 +-
.idea/inspectionProfiles/Project_Default.xml | 10 ++
CHANGELOG.MD | 13 ++
.../{CLIENT => artifacts/client}/MANIFEST.MF | 0
.../{SERVER => artifacts/server}/MANIFEST.MF | 0
README.md | 25 +++-
TODO.md | 7 +-
pom.xml | 127 +++++++++---------
src/main/org/gladiator/app/client/Client.java | 23 +---
.../gladiator/app/client/ClientConfig.java | 7 +-
.../org/gladiator/app/client/ClientMain.java | 1 +
src/main/org/gladiator/app/server/Server.java | 33 ++---
.../server/config/ServerConfigFactory.java | 3 +-
src/main/org/gladiator/app/util/Chat.java | 8 +-
.../native-image/reachability-metadata.json | 7 +
.../native-image/resource-config.json | 9 ++
17 files changed, 163 insertions(+), 114 deletions(-)
create mode 100644 .idea/inspectionProfiles/Project_Default.xml
rename META-INF/{CLIENT => artifacts/client}/MANIFEST.MF (100%)
rename META-INF/{SERVER => artifacts/server}/MANIFEST.MF (100%)
create mode 100644 src/main/resources/META-INF/native-image/reachability-metadata.json
create mode 100644 src/main/resources/META-INF/native-image/resource-config.json
diff --git a/.idea/artifacts/ChatClient_jar.xml b/.idea/artifacts/ChatClient_jar.xml
index e7ec94f..3ad88d1 100644
--- a/.idea/artifacts/ChatClient_jar.xml
+++ b/.idea/artifacts/ChatClient_jar.xml
@@ -3,7 +3,7 @@
$PROJECT_DIR$/out/artifacts/ChatClient_jar
-
+
diff --git a/.idea/artifacts/ChatServer_jar.xml b/.idea/artifacts/ChatServer_jar.xml
index 11ce362..de42a92 100644
--- a/.idea/artifacts/ChatServer_jar.xml
+++ b/.idea/artifacts/ChatServer_jar.xml
@@ -3,7 +3,7 @@
$PROJECT_DIR$/out/artifacts/ChatServer_jar
-
+
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..58ad3a5
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CHANGELOG.MD b/CHANGELOG.MD
index dc0cd53..441eb19 100644
--- a/CHANGELOG.MD
+++ b/CHANGELOG.MD
@@ -1,5 +1,18 @@
# Change Log
+## [0.0.3](https://github.com/FelipeKobra/JMessenger/tree/v0.0.3) - To Release
+
+### Enhancements
+
+- Use GraalVM instead of Launch4j for creating Windows native images. Launch4j made necessary for the user to have JRE
+- Added JLine in bundles for better compatibility with the native image build
+- Change Deprecated JLine Jansi to JNI Version
+
+### Bug Fixes
+
+- Fixed showing terminal buffered messages a lot of times on closing the server
+- No more Exceptions when exiting of the application via `CTRL + C`
+
## [0.0.2](https://github.com/FelipeKobra/JMessenger/tree/v0.0.2) - January 12, 2025
### Enhancements
diff --git a/META-INF/CLIENT/MANIFEST.MF b/META-INF/artifacts/client/MANIFEST.MF
similarity index 100%
rename from META-INF/CLIENT/MANIFEST.MF
rename to META-INF/artifacts/client/MANIFEST.MF
diff --git a/META-INF/SERVER/MANIFEST.MF b/META-INF/artifacts/server/MANIFEST.MF
similarity index 100%
rename from META-INF/SERVER/MANIFEST.MF
rename to META-INF/artifacts/server/MANIFEST.MF
diff --git a/README.md b/README.md
index 73dac15..0548a0c 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,15 @@
![command](src/main/resources/images/command.png)
JMessenger
-**JMessenger** is a terminal-based chat application that allows you to communicate seamlessly with others over a
-network. It supports both client and server functionalities, making it easy to set up and use.
+**JMessenger** is a terminal-based chat application that allows you to communicate seamlessly with others over
+the internet. It supports both client and server functionalities, making it easy to set up and use.
## Table of Contents
- [Notes](#notes)
- [Installation](#installation)
- [Usage](#usage)
+ - [UTF-8 Support](#utf-8-support)
- [Windows Executable](#windows-executable)
- [JAR](#jar)
- [Building](#building)
@@ -34,6 +35,24 @@ network. It supports both client and server functionalities, making it easy to s
## Usage
+### UTF-8 Support
+
+For being able to see emojis and different symbols on your terminal, you need to check it's encoding. For enabling
+the UTF-8 Charset on the current terminal instance check some tips
+
+- Powershell
+
+```powershell
+$OutputEncoding = [Console]::InputEncoding = [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding
+```
+
+- Bash
+
+```bash
+export LANG=en_US.UTF-8
+export LC_ALL=en_US.UTF-8
+```
+
### Windows Executable
Download the executable (`.exe`) from the [Releases](https://github.com/FelipeKobra/JavaTerminalChat/releases) section.
@@ -100,6 +119,8 @@ To build the artifacts, run this command in the root folder:
## Requirements for Developers
- JDK 21 or higher
+- GraalVM JDK 21 or Higher
+- Powershell
## Contributing
diff --git a/TODO.md b/TODO.md
index 4ef4e21..8f8e0f1 100644
--- a/TODO.md
+++ b/TODO.md
@@ -4,10 +4,6 @@
- [ ] Create communication with cryptography
-## Configuration
-
-- [ ] Add a file for server config
-
## QOL
- [ ] Add colors to the terminal
@@ -16,6 +12,9 @@
## Basic
- [ ] Notify clients when someone connects/disconnects
+- [ ] Better warning when server not found on client instead of throwing an exception
+- [ ] Make user terminal automatically support UTF8 Characters
+- [x] Disable Debug on native-image
## Administration
diff --git a/pom.xml b/pom.xml
index 411641b..6e28e9b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,12 +16,15 @@
org.gladiator
-
+
21
21
UTF-8
${project.build.directory}/artifacts
+
+ 3.28.0
+ 0.10.4
@@ -88,79 +91,51 @@
-
-
+
+
- com.akathist.maven.plugins.launch4j
- launch4j-maven-plugin
- 2.5.2
+ org.graalvm.buildtools
+ native-maven-plugin
+ ${native.maven.plugin.version}
+ false
+
+ target/artifacts
+ true
+ false
+ false
+ false
+ true
+
+ -J-Dfile.encoding=UTF-8
+
+
-
- executable-server-main
- package
+ build-native-server
- launch4j
+ compile-no-fork
+ package
- src/main/resources/images/server.ico
- console
- ${artifacts.output.directory}/Server.exe
- ${artifacts.output.directory}/Server.jar
- ${project.artifactId}
-
- %JAVA_HOME%;%PATH%
- false
- false
-
-
- 0.${project.version}
- ${project.version}
- Server executable for ${project.artifactId}
- ${project.groupId}
- 0.${project.version}
- ${project.version
- ${project.artifactId} Server
- ${project.artifactId} Server
- Server.exe
-
+ Server
+ app.server.ServerMain
-
- executable-client-main
- package
+ build-native-client
- launch4j
+ compile-no-fork
+ package
- src/main/resources/images/client.ico
- console
- ${artifacts.output.directory}/Client.exe
- ${artifacts.output.directory}/Client.jar
- ${project.artifactId}
-
- %JAVA_HOME%;%PATH%
- false
- false
-
-
- 0.${project.version}
- ${project.version}
- Client executable for ${project.artifactId}
- ${project.groupId}
- 0.${project.version}
- ${project.version
- ${project.artifactId} Client
- ${project.artifactId} Client
- Client.exe
-
+ Client
+ app.client.ClientMain
-
+
org.codehaus.mojo
exec-maven-plugin
@@ -203,17 +178,41 @@
1.0.2
-
+
- org.fusesource.jansi
- jansi
- 2.4.1
+ org.jline
+ jline-terminal
+ ${jline.bundle.version}
+
+
+
+ org.jline
+ jline-terminal-jni
+ ${jline.bundle.version}
+
+
+
+ org.jline
+ jline-native
+ ${jline.bundle.version}
+
+
+
+ org.jline
+ jline-console
+ ${jline.bundle.version}
+
+
+
+ org.jline
+ jline-style
+ ${jline.bundle.version}
-
+
org.jline
- jline
- 3.28.0
+ jline-reader
+ ${jline.bundle.version}
diff --git a/src/main/org/gladiator/app/client/Client.java b/src/main/org/gladiator/app/client/Client.java
index f2873ee..6707224 100644
--- a/src/main/org/gladiator/app/client/Client.java
+++ b/src/main/org/gladiator/app/client/Client.java
@@ -63,7 +63,7 @@ public static Logger getLogger() {
public void run() {
final AsyncSocketIO socketIO = AsyncSocketIO.getAsyncSocketIO(socket, executor);
- final var reader = new BufferedReader(new InputStreamReader(socketIO.getInputStream()));
+ final var reader = new BufferedReader(new InputStreamReader(socketIO.getInputStream(), StandardCharsets.UTF_8));
final var writer = new PrintWriter(socketIO.getOutputStream(), true, StandardCharsets.UTF_8);
String serverName = exchangeNames(writer, reader);
@@ -79,10 +79,7 @@ private CompletableFuture receiveMessages(BufferedReader reader) {
return CompletableFuture.runAsync(() -> {
try {
reader.lines()
- .map(msg -> {
- msg = new String(msg.getBytes(), StandardCharsets.UTF_8);
- return ConnectionMessage.fromRawString(msg);
- })
+ .map(ConnectionMessage::fromRawString)
.forEach(msg -> {
chat.showConnectionMessage(msg);
chat.showUserPrompt();
@@ -102,18 +99,13 @@ private CompletableFuture sendMessages(PrintWriter writer) {
try {
while ((line = SingletonTerminal.TERMINAL.getLineReader().readLine(chat.userPrompt() + " ")) != null) {
if (line.equals("quit")) {
- throw new UserInterruptException("User typed `quit`");
+ break;
}
String msg = ConnectionMessage.toRawString(config.getName(), line);
writer.println(msg);
}
} catch (EndOfFileException | UserInterruptException e) {
- LOGGER.debug("User stopped the console reading, probably by pressing CTRL + C");
- try {
- socket.close();
- } catch (IOException ex) {
- LOGGER.error("Error during socket closing after shutdown of sending message: {}", e, e);
- }
+ LOGGER.debug("User stopped the console reading, probably by pressing CTRL + C", e);
} finally {
executor.shutdownNow();
}
@@ -150,12 +142,11 @@ private void reconnectPrompt() {
try {
String choice = SingletonTerminal.TERMINAL.getLineReader().readLine("Connect to another server? y/N: ");
if (!choice.equalsIgnoreCase("Y")) {
- chat.prettyPrint("Chat Ended");
- isRunning.set(false);
+ throw new UserInterruptException("Reconnection choice not made");
}
-
} catch (UserInterruptException | EndOfFileException e) {
- LOGGER.debug("Reconnection choice not made: {}", e.getMessage());
+ isRunning.set(false);
+ chat.prettyPrint("Chat Ended");
}
}
diff --git a/src/main/org/gladiator/app/client/ClientConfig.java b/src/main/org/gladiator/app/client/ClientConfig.java
index 27be84e..3e2aac5 100644
--- a/src/main/org/gladiator/app/client/ClientConfig.java
+++ b/src/main/org/gladiator/app/client/ClientConfig.java
@@ -3,7 +3,6 @@
import app.util.SingletonTerminal;
import environment.Port;
import org.apache.commons.lang3.Validate;
-import org.jline.reader.UserInterruptException;
public class ClientConfig {
private final String name;
@@ -24,7 +23,7 @@ public static ClientConfig createClientConfig() {
return new ClientConfig(clientName, serverAddress, serverPort);
}
- private static String receiveName() throws UserInterruptException {
+ private static String receiveName() {
String name = "";
while (name.isBlank()) {
name = SingletonTerminal.TERMINAL.getLineReader().readLine("Choose your name: ");
@@ -32,11 +31,11 @@ private static String receiveName() throws UserInterruptException {
return name;
}
- private static String receiveAddress() throws UserInterruptException {
+ private static String receiveAddress() {
return SingletonTerminal.TERMINAL.getLineReader().readLine("Type the Server IP (default = localhost): ");
}
- private static int receivePort() throws UserInterruptException {
+ private static int receivePort() {
int serverPort;
try {
String serverPortString = SingletonTerminal.TERMINAL.getLineReader().readLine("Type the server port (default = " + Port.PORT_DEFAULT + "): ");
diff --git a/src/main/org/gladiator/app/client/ClientMain.java b/src/main/org/gladiator/app/client/ClientMain.java
index 3b16dfe..817dbf7 100644
--- a/src/main/org/gladiator/app/client/ClientMain.java
+++ b/src/main/org/gladiator/app/client/ClientMain.java
@@ -13,6 +13,7 @@ public static void main(final String... args) {
try (Client client = Client.createClient(isRunning)) {
client.run();
} catch (UserInterruptException e) {
+ isRunning.set(false);
Server.getLogger().debug("User didn't finished typing during a line read, probably by pressing CTRL+C", e);
}
}
diff --git a/src/main/org/gladiator/app/server/Server.java b/src/main/org/gladiator/app/server/Server.java
index 139c83f..e09fff8 100644
--- a/src/main/org/gladiator/app/server/Server.java
+++ b/src/main/org/gladiator/app/server/Server.java
@@ -26,21 +26,22 @@
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
public final class Server implements AutoCloseable {
private static final Logger LOGGER = LoggerFactory.getLogger(Server.class.getName());
+ private final List clientConnections = new CopyOnWriteArrayList<>();
+ private final AtomicBoolean isClosingManually = new AtomicBoolean(false);
- private final List clientConnections;
private final ServerConfig serverConfig;
private final ServerSocket serverSocket;
private final ExecutorService executor;
private final Chat chat;
private final PortMapper portMapper;
- private Server(List clientConnections, ServerConfig serverConfig,
- ServerSocket serverSocket, ExecutorService executor, Chat chat, PortMapper portMapper) {
- this.clientConnections = clientConnections;
+ private Server(ServerConfig serverConfig, ServerSocket serverSocket,
+ ExecutorService executor, Chat chat, PortMapper portMapper) {
this.serverConfig = serverConfig;
this.serverSocket = serverSocket;
this.executor = executor;
@@ -55,7 +56,6 @@ public static Logger getLogger() {
public static Server createServer() throws UserInterruptException {
Server server = null;
try {
- List clientConnections = new CopyOnWriteArrayList<>();
ServerConfig serverConfig = new ServerConfigFactory().create();
ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(serverConfig.port());
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
@@ -64,7 +64,7 @@ public static Server createServer() throws UserInterruptException {
PortMapper portMapper = PortMapper.createDefault();
portMapper.openPort(serverConfig.port());
- server = new Server(clientConnections, serverConfig, serverSocket, executor, chat, portMapper);
+ server = new Server(serverConfig, serverSocket, executor, chat, portMapper);
} catch (BindException e) {
LOGGER.error("Address already in use, check if you have another server opened in the same port");
} catch (IOException e) {
@@ -91,7 +91,7 @@ private CompletableFuture listenToConnections() {
final AsyncSocketIO asyncSocketIO = AsyncSocketIO.getAsyncSocketIO(clientSocket, executor);
- final var reader = new BufferedReader(new InputStreamReader(asyncSocketIO.getInputStream()));
+ final var reader = new BufferedReader(new InputStreamReader(asyncSocketIO.getInputStream(), StandardCharsets.UTF_8));
final var writer = new PrintWriter(asyncSocketIO.getOutputStream(), true, StandardCharsets.UTF_8);
String clientName = exchangeNames(writer, reader);
@@ -113,7 +113,7 @@ private CompletableFuture listenToConnections() {
private CompletableFuture broadcastToConnections() {
LOGGER.info("Broadcasting to connections...");
-
+
System.out.println("Type `quit` to exit");
return CompletableFuture.runAsync(() -> {
String line;
@@ -145,10 +145,7 @@ private void receiveMessages(BufferedReader reader, Connection clientConnection)
executor.execute(() -> {
try {
reader.lines()
- .map(msg -> {
- msg = new String(msg.getBytes(), StandardCharsets.UTF_8);
- return ConnectionMessage.fromRawString(msg);
- })
+ .map(ConnectionMessage::fromRawString)
.forEach(msg -> {
chat.showConnectionMessage(msg);
chat.showBufferedMessage(SingletonTerminal.TERMINAL.getLineReader().getBuffer().toString());
@@ -162,10 +159,12 @@ private void receiveMessages(BufferedReader reader, Connection clientConnection)
} catch (UncheckedIOException ignored) {
LOGGER.debug("Connection with {} ended abruptly", clientConnection.name());
} finally {
- LOGGER.info("User Disconnected: {}", clientConnection.name());
- chat.showBufferedMessage(SingletonTerminal.TERMINAL.getLineReader().getBuffer().toString());
- clientConnection.close();
- clientConnections.remove(clientConnection);
+ if (!isClosingManually.get()) {
+ LOGGER.info("User Disconnected: {}", clientConnection.name());
+ chat.showBufferedMessage(SingletonTerminal.TERMINAL.getLineReader().getBuffer().toString());
+ clientConnection.close();
+ clientConnections.remove(clientConnection);
+ }
}
});
}
@@ -203,6 +202,8 @@ private String exchangeNames(PrintWriter writer, BufferedReader reader) {
@Override
public void close() throws IOException {
LOGGER.info("Closing all connections...");
+
+ isClosingManually.set(true);
final var futures = clientConnections.stream().map(connection ->
CompletableFuture.runAsync(connection::close)).toArray(CompletableFuture[]::new);
CompletableFuture.allOf(futures).join();
diff --git a/src/main/org/gladiator/app/server/config/ServerConfigFactory.java b/src/main/org/gladiator/app/server/config/ServerConfigFactory.java
index aad2949..e1908bd 100644
--- a/src/main/org/gladiator/app/server/config/ServerConfigFactory.java
+++ b/src/main/org/gladiator/app/server/config/ServerConfigFactory.java
@@ -5,12 +5,11 @@
import environment.Port;
import org.apache.commons.lang3.Validate;
import org.jline.reader.LineReader;
-import org.jline.reader.UserInterruptException;
public class ServerConfigFactory {
- public ServerConfig create() throws UserInterruptException {
+ public ServerConfig create() {
ServerConfig serverConfig;
String isCustom;
diff --git a/src/main/org/gladiator/app/util/Chat.java b/src/main/org/gladiator/app/util/Chat.java
index 88796d9..aca6de1 100644
--- a/src/main/org/gladiator/app/util/Chat.java
+++ b/src/main/org/gladiator/app/util/Chat.java
@@ -9,10 +9,6 @@ public Chat(String userPrompt) {
this.userPrompt = userPrompt;
}
- public void cleanLine() {
- System.out.print("\r\033[K");
- }
-
public void prettyPrint(String str) {
String division = "=".repeat(str.length());
System.out.println("\n" + division);
@@ -20,6 +16,10 @@ public void prettyPrint(String str) {
System.out.println(division + "\n");
}
+ public void cleanLine() {
+ System.out.print("\r\033[K");
+ }
+
public void showUserPrompt() {
System.out.print(userPrompt + " ");
}
diff --git a/src/main/resources/META-INF/native-image/reachability-metadata.json b/src/main/resources/META-INF/native-image/reachability-metadata.json
new file mode 100644
index 0000000..52eaf38
--- /dev/null
+++ b/src/main/resources/META-INF/native-image/reachability-metadata.json
@@ -0,0 +1,7 @@
+{
+ "resources": [
+ {
+ "glob": "**/logback.xml"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/main/resources/META-INF/native-image/resource-config.json b/src/main/resources/META-INF/native-image/resource-config.json
new file mode 100644
index 0000000..fb3f8d2
--- /dev/null
+++ b/src/main/resources/META-INF/native-image/resource-config.json
@@ -0,0 +1,9 @@
+{
+ "resources": {
+ "includes": [
+ {
+ "pattern": "logback\\.xml"
+ }
+ ]
+ }
+}
\ No newline at end of file