From 509aa71d9fd704f2a15ed9221b295729a08116e6 Mon Sep 17 00:00:00 2001 From: Mathis Ginkel Date: Tue, 7 Apr 2026 22:08:16 +0200 Subject: [PATCH] Add: Implement Commands for Chat Protocol --- build.gradle | 2 +- .../casono/client/chat/ChatController.java | 86 +++++++++---- .../cs108/casono/client/chat/ChatModel.java | 61 +++++---- .../cs108/casono/client/chat/Message.java | 60 +++++++-- .../casono/client/network/ChatClient.java | 13 +- .../casono/client/network/ClientService.java | 32 +++-- .../casono/client/network/CoreClient.java | 1 - .../client/ui/chatui/ChatBoxController.java | 63 +++++++++ .../client/ui/chatui/ChatViewController.java | 120 +++++++----------- .../client/ui/chatui/GlobalChatView.java | 73 ----------- .../client/ui/chatui/LobbyChatView.java | 72 ----------- .../client/ui/chatui/WhisperChatView.java | 3 - .../client/ui/lobbyui/Casinomainui.java | 4 +- .../dbis/cs108/casono/server/ServerApp.java | 35 ++++- .../check_nick/CheckUsernameParser.java | 2 +- .../check_nick/CheckUsernameResponse.java | 4 +- .../check_nick/UsernameAvailability.java | 2 +- .../GetMessageCountHandler.java | 40 ++++++ .../GetMessageCountParser.java | 13 ++ .../GetMessageCountRequest.java | 10 ++ .../GetMessageCountResponse.java | 13 ++ .../GetNextMessageHandler.java | 41 ++++++ .../GetNextMessageParser.java | 13 ++ .../GetNextMessageRequest.java | 10 ++ .../GetNextMessageResponse.java | 14 ++ .../send_message/SendMessageHandler.java | 30 +++++ .../send_message/SendMessageParser.java | 13 ++ .../send_message/SendMessageRequest.java | 19 +++ .../server/domain/message/MessageManager.java | 4 +- .../cs108/casono/server/domain/user/User.java | 11 +- .../protocol/response/SuccessResponse.java | 4 +- .../components/chatui/chatbox.fxml | 56 +++----- .../chatui/{globalchat.fxml => chattab.fxml} | 24 ++-- .../components/chatui/lobbychat.fxml | 39 ------ .../components/chatui/whisperchat.fxml | 37 ------ .../dbis/cs108/casono/client/chat/Chat.java | 9 ++ .../casono/client/chat/ChatApplication.java | 48 +++++++ .../cs108/casono/client/chat/ChatTest.java | 2 +- 38 files changed, 644 insertions(+), 439 deletions(-) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java delete mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/GlobalChatView.java delete mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/LobbyChatView.java delete mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/WhisperChatView.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/get_message_count/GetMessageCountHandler.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/get_message_count/GetMessageCountParser.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/get_message_count/GetMessageCountRequest.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/get_message_count/GetMessageCountResponse.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/get_next_message/GetNextMessageHandler.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/get_next_message/GetNextMessageParser.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/get_next_message/GetNextMessageRequest.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/get_next_message/GetNextMessageResponse.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/send_message/SendMessageHandler.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/send_message/SendMessageParser.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/send_message/SendMessageRequest.java rename src/main/resources/ui-structure/components/chatui/{globalchat.fxml => chattab.fxml} (52%) delete mode 100644 src/main/resources/ui-structure/components/chatui/lobbychat.fxml delete mode 100644 src/main/resources/ui-structure/components/chatui/whisperchat.fxml create mode 100644 src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/Chat.java create mode 100644 src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatApplication.java diff --git a/build.gradle b/build.gradle index 4360c82..6f82563 100644 --- a/build.gradle +++ b/build.gradle @@ -32,7 +32,7 @@ dependencies { // Source: https://mvnrepository.com/artifact/org.apache.logging.log4j implementation("org.apache.logging.log4j:log4j-api:2.25.3") runtimeOnly("org.apache.logging.log4j:log4j-core:2.25.3") - + implementation("org.jspecify:jspecify:1.0.0") // Source: https://mvnrepository.com/artifact/org.fusesource.jansi/jansi runtimeOnly("org.fusesource.jansi:jansi:2.4.2") diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java index c3a809f..a3861a5 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java @@ -2,8 +2,15 @@ package ch.unibas.dmi.dbis.cs108.casono.client.chat; import ch.unibas.dmi.dbis.cs108.casono.client.network.ChatClient; import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService; -import java.util.ArrayList; +import ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui.ChatBoxController; +import org.jspecify.annotations.Nullable; + +import java.util.Timer; +import java.util.TimerTask; +import java.util.Map; import java.util.List; +import java.util.LinkedHashMap; + /** * responsible for the transferring of messages from the server to the ChatModel or from the @@ -12,49 +19,84 @@ import java.util.List; public class ChatController { private final String username; - private final ClientService clientService; - private final ArrayList chatModelArrayList; private final ChatClient chatClient; + public ChatBoxController getChatBoxController() { + return chatBoxController; + } + + private final ChatBoxController chatBoxController; + private int lobbyId = -1; + private final Timer timer; + + + public record ChatKey(ChatType type, @Nullable String targetUser){ + public ChatKey(ChatType type){ + this(type, null); + } + } + + public Map getChatModelMap() { + return chatModelMap; + } + + private Map chatModelMap; + + private static final long REFRESH_TIME = 1000; + public ChatController(String username, ClientService clientService) { this.username = username; - this.clientService = clientService; - chatModelArrayList = new ArrayList<>(); - chatModelArrayList.add(new ChatModel(ChatType.GLOBAL, username)); chatClient = new ChatClient(clientService); + chatModelMap = new LinkedHashMap<>(); + this.chatBoxController = new ChatBoxController(username, this); + this.timer = new Timer(); + timer.schedule( + new TimerTask() { + @Override + public void run() { + clientService.ping(); + receiveMessage(); + } + }, + 0, + REFRESH_TIME); } - public ChatModel getChatModel(int index) { - return chatModelArrayList.get(index); - } - - public void createLobbyChat(int lobbyId, ChatType chatType) { - ChatModel chatModel = new ChatModel(chatType, username, lobbyId); - chatModelArrayList.add(chatModel); + public void setLobbyChat(int lobbyId) { + this.lobbyId = lobbyId; + ChatModel lobbyChatModel = new ChatModel(ChatType.LOBBY, username, lobbyId, null); + chatModelMap.put(new ChatKey(ChatType.LOBBY), lobbyChatModel); } /** method to get all messages from the server */ - public Boolean receiveMessage() { + public void receiveMessage() { List newMessages = chatClient.getMessages(); if (!newMessages.isEmpty()) { for (Message msg : newMessages) { switch (msg.getMessageType()) { case ChatType.GLOBAL: - chatModelArrayList.get(0).addMessage(msg); + chatModelMap.get(new ChatKey(ChatType.GLOBAL)).addMessage(msg); case ChatType.LOBBY: - ChatModel chatModel = chatModelArrayList.get(1); - if (chatModel != null && chatModel.lobbyId == msg.lobbyId) { - chatModel.addMessage(msg); + if (msg.lobbyId == lobbyId) { + chatModelMap.computeIfAbsent(new ChatKey(ChatType.LOBBY), + (_key) -> new ChatModel(ChatType.LOBBY, username, msg.lobbyId, null)).addMessage(msg); } case ChatType.WHISPER: // TODO: Check, if target person is user and if yes, iterate through all - // whisper chats. + if (msg.target.equals(username)) { + if (chatModelMap.containsKey(new ChatKey(ChatType.WHISPER, msg.sender))) { + chatModelMap.get(new ChatKey(ChatType.WHISPER, msg.sender)).addMessage(msg); + } else { + ChatModel value = new ChatModel(ChatType.WHISPER, username, -1, msg.sender); + chatModelMap.put(new ChatKey(ChatType.WHISPER, msg.sender), value); + chatBoxController.addWhisperChat(msg.sender, value); + } + } + + } } - return true; - } else { - return false; } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatModel.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatModel.java index 6fd4177..894d957 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatModel.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatModel.java @@ -1,5 +1,9 @@ package ch.unibas.dmi.dbis.cs108.casono.client.chat; +import ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui.ChatViewController; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleIntegerProperty; + import java.util.ArrayList; /** @@ -11,33 +15,30 @@ public class ChatModel { public ArrayList messages; - private ChatType chattype; + private final ChatType chattype; - public String username; + /** + * The person currently using this client + */ + public final String username; - public int count; + /** + * The person to send the message to + * If the chat is a whisper chat + */ + private final String target; + + private final IntegerProperty count; public int lobbyId; - /** - * Creates a new ChatModel, given a username of the client - * - * @param chattype - * @param username - */ - public ChatModel(ChatType chattype, String username) { + public ChatModel(ChatType chattype, String username, int lobbyId, String target) { this.messages = new ArrayList(); this.chattype = chattype; this.username = username; - this.count = 0; - } - - public ChatModel(ChatType chattype, String username, int lobbyId) { - this.messages = new ArrayList(); - this.chattype = chattype; - this.username = username; - this.count = 0; + this.count = new SimpleIntegerProperty(0); this.lobbyId = lobbyId; + this.target = target; } public ChatType getChattype() { @@ -49,9 +50,9 @@ public class ChatModel { * ChatModel */ public synchronized String viewNextMessage() { - count--; + count.subtract(1); Message msg = messages.getLast(); - return String.format("[%s] %s: %s", msg.timestamp, msg.user, msg.getMessage()); + return String.format("[%s] %s: %s", msg.timestamp, msg.sender, msg.getMessage()); } /** @@ -61,13 +62,21 @@ public class ChatModel { */ public synchronized void addMessage(Message msg) { messages.add(msg); - count++; + count.add(1); } - /** method to send all current messages to the ChatViewController, if needed */ - public void addCompleteChat() { - for (int i = 0; i < this.messages.size(); i++) { - Message msg = this.messages.get(i); - } + + public void addListener(ChatViewController chatViewController) { + count.addListener( + (_count, _p, n) -> + { + if(n.intValue() > 0) { + chatViewController.showMessage(); + } + }); + } + + public String getTarget() { + return target; } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/Message.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/Message.java index 60cb568..7869271 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/Message.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/Message.java @@ -1,7 +1,11 @@ package ch.unibas.dmi.dbis.cs108.casono.client.chat; +import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.RequestParameter; +import org.jspecify.annotations.NonNull; + import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -12,7 +16,7 @@ import java.util.regex.Pattern; public class Message { private final ChatType type; private final String message; - public String user; + public String sender; public String timestamp; public int lobbyId = 0; public String target = null; @@ -22,20 +26,20 @@ public class Message { * * @param type - Either global, local or whisper * @param lobbyId - lobby id, or null, if the typChatType - * @param user - username + * @param sender - username * @param target - username of the target user, for whisper chat * @param message */ public Message( ChatType type, int lobbyId, - String user, + String sender, String target, String timestamp, String message) { this.type = type; this.lobbyId = lobbyId; - this.user = user; + this.sender = sender; this.target = target; this.timestamp = timestamp; this.message = message; @@ -47,14 +51,14 @@ public class Message { * * @param type - Either global, local or whisper * @param lobbyId - lobby id, or null, if the type is global - * @param user - username + * @param sender - username * @param target - username of the target user, for whisper chat * @param message */ - public Message(ChatType type, int lobbyId, String user, String target, String message) { + public Message(ChatType type, int lobbyId, String sender, String target, String message) { this.type = type; this.lobbyId = lobbyId; - this.user = user; + this.sender = sender; this.target = target; this.message = message; LocalDateTime now = LocalDateTime.now(); @@ -77,10 +81,10 @@ public class Message { */ public String toArgsString() { return String.format( - "TYPE=%s GAME=%d USER=%s TARGET=%s TIME=%s TEXT=%s", + "TYPE=%s GAME=%d USER=%s TARGET=%s TIME=%s TEXT='%s'", this.type.toString(), this.lobbyId, - this.user, + this.sender, this.target, this.timestamp, this.message); @@ -91,7 +95,7 @@ public class Message { Pattern.compile( "TYPE=(?\\w+) " + "GAME=(?\\w+) " + "USER=(?\\w+) " + "TARGET=(?\\w+) " + - "TIME=(?