From e65553076094791ac3dff4c11e4eb493a6fa4e81 Mon Sep 17 00:00:00 2001 From: Jona Walpert Date: Sat, 11 Apr 2026 16:48:58 +0200 Subject: [PATCH 01/10] Feat: Add addPlayer command on sever side Rafs #92 --- .../commands/add_player/AddPlayerHandler.java | 33 +++++++++++++++++++ .../commands/add_player/AddPlayerParser.java | 20 +++++++++++ .../commands/add_player/AddPlayerRequest.java | 24 ++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/add_player/AddPlayerHandler.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/add_player/AddPlayerParser.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/add_player/AddPlayerRequest.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/add_player/AddPlayerHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/add_player/AddPlayerHandler.java new file mode 100644 index 0000000..25a93c8 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/add_player/AddPlayerHandler.java @@ -0,0 +1,33 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.add_player; + +import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.User; +import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserRegistry; +import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.ErrorResponse; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.OkResponse; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher; +import java.util.Optional; + +/** Handler for the ADD_PLAYER command. */ +public class AddPlayerHandler extends CommandHandler { + private final UserRegistry userRegistry; + + public AddPlayerHandler(UserRegistry userRegistry, ResponseDispatcher responseDispatcher) { + super(responseDispatcher); + this.userRegistry = userRegistry; + } + + @Override + public void execute(AddPlayerRequest request) { + Optional created = + userRegistry.registerIfAvailable( + request.getName(), request.getContext().sessionId()); + if (created.isEmpty()) { + responseDispatcher.dispatch( + new ErrorResponse(request.getContext(), "NAME_TAKEN", "Name already taken")); + return; + } + + responseDispatcher.dispatch(new OkResponse(request.getContext())); + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/add_player/AddPlayerParser.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/add_player/AddPlayerParser.java new file mode 100644 index 0000000..6e869e6 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/add_player/AddPlayerParser.java @@ -0,0 +1,20 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.add_player; + +import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParser; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.PrimitiveRequest; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.accessor.RequestParameterAccessor; + +/** Parser for the ADD_PLAYER command. */ +public class AddPlayerParser implements CommandParser { + @Override + public AddPlayerRequest parse(PrimitiveRequest primitiveRequest) { + RequestParameterAccessor accessor = + new RequestParameterAccessor(primitiveRequest.parameters()); + + String name = accessor.require("NAME"); + + int chips = accessor.require("CHIPS", Integer::parseInt); + + return new AddPlayerRequest(primitiveRequest.context(), name, chips); + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/add_player/AddPlayerRequest.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/add_player/AddPlayerRequest.java new file mode 100644 index 0000000..2e55ee7 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/add_player/AddPlayerRequest.java @@ -0,0 +1,24 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.add_player; + +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext; + +/** Request data for the ADD_PLAYER command. */ +public class AddPlayerRequest extends Request { + private final String name; + private final int chips; + + public AddPlayerRequest(RequestContext context, String name, int chips) { + super(context); + this.name = name; + this.chips = chips; + } + + public String getName() { + return name; + } + + public int getChips() { + return chips; + } +} From 2ab27ac3eda33a38bc7eb724f5faf00da8ed8c21 Mon Sep 17 00:00:00 2001 From: Jona Walpert Date: Sat, 11 Apr 2026 17:41:45 +0200 Subject: [PATCH 02/10] Feat: Add lobby class and Manager with conenction to Game engine. The gameengine will be satrte when 4 players joined the lobby. This is made possible by the callback-driven-Listener Refs #73 --- .../casono/server/domain/lobby/Lobby.java | 131 ++++++++++++++++ .../domain/lobby/LobbyEventListener.java | 7 + .../casono/server/domain/lobby/LobbyId.java | 8 + .../server/domain/lobby/LobbyManager.java | 144 ++++++++++++++++++ 4 files changed, 290 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/Lobby.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/LobbyEventListener.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/LobbyId.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/LobbyManager.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/Lobby.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/Lobby.java new file mode 100644 index 0000000..7653685 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/Lobby.java @@ -0,0 +1,131 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby; + +import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.GameController; +import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.engine.GameEngine; +import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.engine.RoundManager; +import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.engine.TurnManager; +import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player.PlayerId; +import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.rules.RuleEngine; +import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state.GameState; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** Represents a single lobby: id, name, players and an optional GameController. */ +public class Lobby { + private static final Logger LOGGER = Logger.getLogger(Lobby.class.getName()); + + public enum AddResult { + NOT_ADDED, + ADDED, + ADDED_AND_STARTED + } + + private final LobbyId id; + private final String name; + private final List playerNames = new CopyOnWriteArrayList<>(); + private volatile GameController gameController; + + public Lobby(LobbyId id, String name) { + this.id = id; + this.name = name; + } + + public LobbyId getId() { + return id; + } + + public String getName() { + return name; + } + + public List getPlayerNames() { + return List.copyOf(playerNames); + } + + /** + * Try to add a player to this lobby. + * + * @return true if added, false if already present or full + */ + public boolean addPlayer(String playerName, int maxPlayers) { + if (playerName == null) { + return false; + } + synchronized (playerNames) { + if (playerNames.contains(playerName)) { + return false; + } + if (playerNames.size() >= maxPlayers) { + return false; + } + playerNames.add(playerName); + return true; + } + } + + public boolean removePlayer(String playerName) { + return playerNames.remove(playerName); + } + + public void initGame(GameController controller) { + this.gameController = controller; + } + + public GameController getGameController() { + return gameController; + } + + /** + * Atomically add a player and, if the lobby reached {@code autoStartPlayers} and no game exists + * yet, create and start a game. The operation is synchronized on the internal player list to + * avoid races when multiple joins happen concurrently. + * + * @return {@link AddResult} indicating whether the player was added and if a game was started + */ + public AddResult addPlayerAndMaybeStart( + String playerName, int maxPlayers, int autoStartPlayers, int defaultStartChips) { + if (playerName == null) { + return AddResult.NOT_ADDED; + } + synchronized (playerNames) { + if (playerNames.contains(playerName)) { + return AddResult.NOT_ADDED; + } + if (playerNames.size() >= maxPlayers) { + return AddResult.NOT_ADDED; + } + playerNames.add(playerName); + + // If threshold reached and no game running, create and start game here + if (gameController == null && playerNames.size() == autoStartPlayers) { + try { + GameState state = new GameState(); + GameEngine engine = + new GameEngine( + state, + new RuleEngine(new ArrayList<>()), + new RoundManager(), + new TurnManager()); + GameController game = new GameController(engine); + + for (String p : playerNames) { + game.addPlayer(PlayerId.of(p), defaultStartChips); + } + + game.startGame(); + this.gameController = game; + LOGGER.info(() -> "Auto-started game in lobby " + id.value()); + return AddResult.ADDED_AND_STARTED; + } catch (RuntimeException e) { + LOGGER.log(Level.WARNING, "Auto-start failed for lobby " + id.value(), e); + return AddResult.ADDED; + } + } + + return AddResult.ADDED; + } + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/LobbyEventListener.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/LobbyEventListener.java new file mode 100644 index 0000000..d6fee3a --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/LobbyEventListener.java @@ -0,0 +1,7 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby; + +/** Listener interface for lobby lifecycle events. */ +public interface LobbyEventListener { + /** Called when a game is automatically started in the given lobby. */ + void onGameStarted(LobbyId lobbyId); +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/LobbyId.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/LobbyId.java new file mode 100644 index 0000000..457aea0 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/LobbyId.java @@ -0,0 +1,8 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby; + +/** Typesafe wrapper for lobby IDs (1-8). */ +public record LobbyId(int value) { + public static LobbyId of(int value) { + return new LobbyId(value); + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/LobbyManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/LobbyManager.java new file mode 100644 index 0000000..2179e8a --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/LobbyManager.java @@ -0,0 +1,144 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby; + +import ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby.Lobby.AddResult; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Logger; + +/** Manages dynamic creation of up to 8 lobbies and maps players to lobbies. */ +public class LobbyManager { + private static final int MAX_LOBBIES = 8; + private static final int DEFAULT_MAX_PLAYERS = 4; + private static final int AUTO_START_PLAYERS = 4; + private static final int DEFAULT_START_CHIPS = 20000; + + private final Map activeLobbies = new ConcurrentHashMap<>(); + private final Map playerToLobby = new ConcurrentHashMap<>(); + private final List listeners = new CopyOnWriteArrayList<>(); + private final int maxPlayersPerLobby; + private static final Logger LOGGER = Logger.getLogger(LobbyManager.class.getName()); + + public LobbyManager() { + this(DEFAULT_MAX_PLAYERS); + } + + public LobbyManager(int maxPlayersPerLobby) { + this.maxPlayersPerLobby = maxPlayersPerLobby; + } + + /** Create a new lobby with an automatic id (1..8). Returns null if none available. */ + public synchronized LobbyId createNewLobby(String name) { + if (activeLobbies.size() >= MAX_LOBBIES) { + return null; + } + for (int i = 1; i <= MAX_LOBBIES; i++) { + LobbyId id = LobbyId.of(i); + if (!activeLobbies.containsKey(id)) { + Lobby lobby = new Lobby(id, name == null ? ("Room " + i) : name); + activeLobbies.put(id, lobby); + return id; + } + } + return null; + } + + public Lobby getLobby(LobbyId id) { + if (id == null) { + return null; + } + return activeLobbies.get(id); + } + + public Lobby getLobbyByUsername(String username) { + LobbyId id = playerToLobby.get(username); + if (id == null) { + return null; + } + return activeLobbies.get(id); + } + + public boolean addPlayerToLobby(String username, LobbyId lobbyId) { + Lobby lobby = getLobby(lobbyId); + if (lobby == null) { + return false; + } + AddResult result = + lobby.addPlayerAndMaybeStart( + username, maxPlayersPerLobby, AUTO_START_PLAYERS, DEFAULT_START_CHIPS); + if (result != AddResult.NOT_ADDED) { + playerToLobby.put(username, lobbyId); + if (result == AddResult.ADDED_AND_STARTED) { + LOGGER.info( + () -> + "Lobby " + + lobbyId.value() + + " reached " + + AUTO_START_PLAYERS + + " players; game started."); + notifyGameStarted(lobbyId); + } + return true; + } + return false; + } + + public void addListener(LobbyEventListener listener) { + listeners.add(listener); + } + + public void removeListener(LobbyEventListener listener) { + listeners.remove(listener); + } + + private void notifyGameStarted(LobbyId lobbyId) { + for (LobbyEventListener l : listeners) { + try { + l.onGameStarted(lobbyId); + } catch (RuntimeException e) { + LOGGER.warning( + () -> + "Listener threw while handling game-start for lobby " + + lobbyId.value()); + } + } + } + + public boolean removePlayer(String username) { + LobbyId id = playerToLobby.remove(username); + if (id == null) { + return false; + } + Lobby lobby = activeLobbies.get(id); + if (lobby == null) { + return false; + } + boolean removed = lobby.removePlayer(username); + // If lobby becomes empty, remove it + if (lobby.getPlayerNames().isEmpty()) { + activeLobbies.remove(id); + } + return removed; + } + + public Collection getAllLobbies() { + return activeLobbies.values(); + } + + /** + * Apply the given action to every player username in the lobby identified by {@code lobbyId}. + * This is a small helper that keeps iteration logic centralized and avoids leaking internal + * collections to callers. + */ + public void broadcast(LobbyId lobbyId, java.util.function.Consumer action) { + Lobby lobby = getLobby(lobbyId); + if (lobby == null) { + return; + } + for (String username : lobby.getPlayerNames()) { + action.accept(username); + } + } +} From af9daecf4600a110e0856574c9c7ea9d6be2330f Mon Sep 17 00:00:00 2001 From: Jona Walpert Date: Sat, 11 Apr 2026 18:03:18 +0200 Subject: [PATCH 03/10] Docs: Add documentation for Join_lobby command --- .../networking/commands/protocol-document.md | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/documents/docs/networking/commands/protocol-document.md b/documents/docs/networking/commands/protocol-document.md index 340dc8e..329e669 100644 --- a/documents/docs/networking/commands/protocol-document.md +++ b/documents/docs/networking/commands/protocol-document.md @@ -453,4 +453,64 @@ GET_NEXT_MESSAGE +OK TYPE=GLOBAL GAME=-1 USER=player1 TARGET=null TIME=9:30 TEXT="Guten Tag" END +``` + +### Example Response (success) + +``` ++OK +END +``` + +## JOIN_LOBBY command + +The `JOIN_LOBBY` command requests the server to add the currently logged-in user to the lobby with the given identifier. The server resolves the username from the session; clients must only provide the `ID` parameter. + +### Required pre-execution checks + +- [`UserLoggedInCheck`](#userloggedincheck) + +### Request Parameters + +| Parameter Name | Type | Optional | Description | +| :------------- | :--- | :------: | :---------- | +| `ID` | `int` | no | Numeric id of the target lobby | + +### Implementation notes + +- Parser: `JoinLobbyParser` — reads the `ID` parameter and builds `JoinLobbyRequest`. +- Handler: `JoinLobbyHandler` — resolves the username from `UserRegistry` using the session id, attempts to add the user to the lobby via `LobbyManager.addPlayerToLobby(...)` and returns appropriate responses. + +### Success Response + +No additional response fields. The server replies with a simple `+OK` on success. + +### Error Response + +| Code | Description | +| :--- | :---------- | +| `USER_NOT_LOGGED_IN` | No user associated with the session (pre-execution check failed) | +| `LOBBY_NOT_FOUND` | The specified lobby id does not exist | +| `LOBBY_FULL_OR_ALREADY_IN` | Lobby is full or the user is already in the lobby | + +### Example Request + +``` +JOIN_LOBBY ID=1 +``` + +### Example Response (success) + +``` ++OK +END +``` + +### Example Response (error) + +``` +-ERR + CODE=LOBBY_NOT_FOUND + MESSAGE=Lobby not found +END ``` \ No newline at end of file From 73dd00f37d6ee600a68b6f71b77ce01efd5d4c2f Mon Sep 17 00:00:00 2001 From: Jona Walpert Date: Sat, 11 Apr 2026 18:29:21 +0200 Subject: [PATCH 04/10] Feat: Add JoinLobby Command to server side This command adds the possibility to join a created lobby --- .../lobby/join_lobby/JoinLobbyHandler.java | 63 +++++++++++++++++++ .../lobby/join_lobby/JoinLobbyParser.java | 15 +++++ .../lobby/join_lobby/JoinLobbyRequest.java | 17 +++++ 3 files changed, 95 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyHandler.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyParser.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyRequest.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyHandler.java new file mode 100644 index 0000000..9a2b43b --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyHandler.java @@ -0,0 +1,63 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.join_lobby; + +import ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby.LobbyId; +import ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby.LobbyManager; +import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.User; +import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserRegistry; +import ch.unibas.dmi.dbis.cs108.casono.server.app.checks.UserLoggedInCheck; +import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.ErrorResponse; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.OkResponse; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher; + +public class JoinLobbyHandler extends CommandHandler { + private final LobbyManager lobbyManager; + private final UserRegistry userRegistry; + + public JoinLobbyHandler( + ResponseDispatcher responseDispatcher, + LobbyManager lobbyManager, + UserRegistry userRegistry) { + super(responseDispatcher); + this.lobbyManager = lobbyManager; + this.userRegistry = userRegistry; + addCheck(new UserLoggedInCheck(userRegistry)); + } + + @Override + public void execute(JoinLobbyRequest request) { + LobbyId lid = LobbyId.of(request.getId()); + var lobby = lobbyManager.getLobby(lid); + if (lobby == null) { + responseDispatcher.dispatch( + new ErrorResponse(request.getContext(), "LOBBY_NOT_FOUND", "Lobby not found")); + return; + } + + var maybeUser = userRegistry.getBySessionId(request.getContext().sessionId()); + if (maybeUser.isEmpty()) { + // UserLoggedInCheck should normally prevent this; keep safe fallback + responseDispatcher.dispatch( + new ErrorResponse( + request.getContext(), + "USER_NOT_LOGGED_IN", + "No user associated with session")); + return; + } + + User user = maybeUser.get(); + String username = user.getName(); + + boolean ok = lobbyManager.addPlayerToLobby(username, lid); + if (!ok) { + responseDispatcher.dispatch( + new ErrorResponse( + request.getContext(), + "LOBBY_FULL_OR_ALREADY_IN", + "Lobby full or user already in lobby")); + return; + } + + responseDispatcher.dispatch(new OkResponse(request.getContext())); + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyParser.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyParser.java new file mode 100644 index 0000000..facb1a4 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyParser.java @@ -0,0 +1,15 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.join_lobby; + +import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParser; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.PrimitiveRequest; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.accessor.RequestParameterAccessor; + +public class JoinLobbyParser implements CommandParser { + @Override + public JoinLobbyRequest parse(PrimitiveRequest primitiveRequest) { + RequestParameterAccessor accessor = + new RequestParameterAccessor(primitiveRequest.parameters()); + int id = accessor.require("ID", Integer::parseInt); + return new JoinLobbyRequest(primitiveRequest.context(), id); + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyRequest.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyRequest.java new file mode 100644 index 0000000..8c455f2 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyRequest.java @@ -0,0 +1,17 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.join_lobby; + +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext; + +public class JoinLobbyRequest extends Request { + private final int id; + + public JoinLobbyRequest(RequestContext context, int id) { + super(context); + this.id = id; + } + + public int getId() { + return id; + } +} From 2c6ecdc0f9c5b4adb58b87350f0a3b3654bbca1c Mon Sep 17 00:00:00 2001 From: Jona Walpert Date: Sat, 11 Apr 2026 19:20:04 +0200 Subject: [PATCH 05/10] Docs: Add javadoc for JoinLobby command --- .../lobby/join_lobby/JoinLobbyHandler.java | 25 ++++++++++++++++++- .../lobby/join_lobby/JoinLobbyParser.java | 22 ++++++++++++++-- .../lobby/join_lobby/JoinLobbyRequest.java | 18 +++++++++++++ 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyHandler.java index 9a2b43b..33ca37e 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyHandler.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyHandler.java @@ -1,19 +1,37 @@ package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.join_lobby; +import ch.unibas.dmi.dbis.cs108.casono.server.app.checks.UserLoggedInCheck; import ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby.LobbyId; import ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby.LobbyManager; import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.User; import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserRegistry; -import ch.unibas.dmi.dbis.cs108.casono.server.app.checks.UserLoggedInCheck; import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler; import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.ErrorResponse; import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.OkResponse; import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher; +/** + * Handler for the `JOIN_LOBBY` command. + * + *

+ * Resolves the username from the session (requires {@link UserLoggedInCheck}), + * looks up the target lobby and attempts to add the user. On success an + * `+OK` response is dispatched; on failure an appropriate {@link ErrorResponse} + * with one of the error codes `LOBBY_NOT_FOUND`, `USER_NOT_LOGGED_IN` or + * `LOBBY_FULL_OR_ALREADY_IN` is returned. + */ public class JoinLobbyHandler extends CommandHandler { private final LobbyManager lobbyManager; private final UserRegistry userRegistry; + /** + * Create a new {@link JoinLobbyHandler}. + * + * @param responseDispatcher dispatcher used to send responses back to the + * client + * @param lobbyManager manager providing lobby state and operations + * @param userRegistry registry to resolve session -> user mappings + */ public JoinLobbyHandler( ResponseDispatcher responseDispatcher, LobbyManager lobbyManager, @@ -24,6 +42,11 @@ public class JoinLobbyHandler extends CommandHandler { addCheck(new UserLoggedInCheck(userRegistry)); } + /** + * Execute the join-lobby request. + * + * @param request the parsed {@link JoinLobbyRequest} + */ @Override public void execute(JoinLobbyRequest request) { LobbyId lid = LobbyId.of(request.getId()); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyParser.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyParser.java index facb1a4..c7cb217 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyParser.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyParser.java @@ -4,11 +4,29 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandPar import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.PrimitiveRequest; import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.accessor.RequestParameterAccessor; +/** + * Parser for the `JOIN_LOBBY` command. + * + *

+ * Expected request parameters: + *

    + *
  • `ID` (int) — numeric lobby identifier (required) + *
+ * + *

+ * The parser builds a {@link JoinLobbyRequest} containing the parsed lobby id + * and the original request context. + */ public class JoinLobbyParser implements CommandParser { + /** + * Parse the incoming primitive request into a {@link JoinLobbyRequest}. + * + * @param primitiveRequest the raw primitive request + * @return a {@link JoinLobbyRequest} with the parsed lobby id and context + */ @Override public JoinLobbyRequest parse(PrimitiveRequest primitiveRequest) { - RequestParameterAccessor accessor = - new RequestParameterAccessor(primitiveRequest.parameters()); + RequestParameterAccessor accessor = new RequestParameterAccessor(primitiveRequest.parameters()); int id = accessor.require("ID", Integer::parseInt); return new JoinLobbyRequest(primitiveRequest.context(), id); } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyRequest.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyRequest.java index 8c455f2..f667a66 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyRequest.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyRequest.java @@ -3,14 +3,32 @@ package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.join_lobby; import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request; import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext; +/** + * Request data for the `JOIN_LOBBY` command. + * + *

+ * Contains the lobby id the client wants to join and inherits the + * {@link Request} contextual information. + */ public class JoinLobbyRequest extends Request { private final int id; + /** + * Create a new {@link JoinLobbyRequest}. + * + * @param context the request context + * @param id numeric lobby id to join + */ public JoinLobbyRequest(RequestContext context, int id) { super(context); this.id = id; } + /** + * Returns the lobby id requested by the client. + * + * @return numeric lobby id + */ public int getId() { return id; } From 7788bbc5d3d70158331a7b37393907303758b770 Mon Sep 17 00:00:00 2001 From: Jona Walpert Date: Sat, 11 Apr 2026 19:28:17 +0200 Subject: [PATCH 06/10] Style: Spotless apllied to JoinLobby command --- .../dbis/cs108/casono/server/ServerApp.java | 181 ++++++++++-------- .../lobby/join_lobby/JoinLobbyHandler.java | 17 +- .../lobby/join_lobby/JoinLobbyParser.java | 14 +- .../lobby/join_lobby/JoinLobbyRequest.java | 7 +- 4 files changed, 120 insertions(+), 99 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java index 7062252..e859056 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java @@ -24,6 +24,7 @@ import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.ping.PingRequest; import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.send_message.SendMessageHandler; import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.send_message.SendMessageParser; import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.send_message.SendMessageRequest; +import ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby.LobbyManager; import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserCleanupJob; import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserRegistry; import ch.unibas.dmi.dbis.cs108.casono.server.network.NetworkManager; @@ -44,97 +45,121 @@ import org.apache.logging.log4j.Logger; /** Application class for starting the server. */ public class ServerApp { - private static final int USER_CLEANUP_JOB_DELAY = 0; - private static final int USER_CLEANUP_JOB_PERIOD = 10; - private static final int USER_CLEANUP_JOB_RECONNECT_THRESHOLD = 10; - private static final int SESSION_DISCONNECT_JOB_DELAY = 0; - private static final int SESSION_DISCONNECT_JOB_PERIOD = 2; - private static final int SESSION_DISCONNECT_JOB_TIMEOUT = 5; + private static final int USER_CLEANUP_JOB_DELAY = 0; + private static final int USER_CLEANUP_JOB_PERIOD = 10; + private static final int USER_CLEANUP_JOB_RECONNECT_THRESHOLD = 10; + private static final int SESSION_DISCONNECT_JOB_DELAY = 0; + private static final int SESSION_DISCONNECT_JOB_PERIOD = 2; + private static final int SESSION_DISCONNECT_JOB_TIMEOUT = 5; - public static void start(String arg) { - int port = Integer.parseInt(arg); + public static void start(String arg) { + int port = Integer.parseInt(arg); - Logger logger = LogManager.getLogger(ServerApp.class); - logger.info("Starting server at port {}", port); + Logger logger = LogManager.getLogger(ServerApp.class); + logger.info("Starting server at port {}", port); - EventBus eventBus = new EventBus(); - CommandParserDispatcher dispatcher = new CommandParserDispatcher(); - SessionManager sessionManager = new SessionManager(eventBus, dispatcher); - ResponseDispatcher responseDispatcher = new ResponseDispatcher(sessionManager); - CommandHandlerExecutor handlerExecutor = new CommandHandlerExecutor(responseDispatcher); - CommandRouter router = new CommandRouter(handlerExecutor); + EventBus eventBus = new EventBus(); + CommandParserDispatcher dispatcher = new CommandParserDispatcher(); + SessionManager sessionManager = new SessionManager(eventBus, dispatcher); + ResponseDispatcher responseDispatcher = new ResponseDispatcher(sessionManager); + CommandHandlerExecutor handlerExecutor = new CommandHandlerExecutor(responseDispatcher); + CommandRouter router = new CommandRouter(handlerExecutor); - eventBus.subscribe(DisconnectEvent.class, event -> sessionManager.onDisconnect(event)); + eventBus.subscribe(DisconnectEvent.class, event -> sessionManager.onDisconnect(event)); - UserRegistry userRegistry = new UserRegistry(); - eventBus.subscribe( - DisconnectEvent.class, event -> userRegistry.onDisconnect(event.sessionId())); - ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); - scheduler.scheduleAtFixedRate( - new UserCleanupJob( - userRegistry, Duration.ofSeconds(USER_CLEANUP_JOB_RECONNECT_THRESHOLD)), - USER_CLEANUP_JOB_DELAY, - USER_CLEANUP_JOB_PERIOD, - TimeUnit.SECONDS); - scheduler.scheduleAtFixedRate( - new SessionDisconnectJob( - sessionManager, - eventBus, - Duration.ofSeconds(SESSION_DISCONNECT_JOB_TIMEOUT)), - SESSION_DISCONNECT_JOB_DELAY, - SESSION_DISCONNECT_JOB_PERIOD, - TimeUnit.SECONDS); + UserRegistry userRegistry = new UserRegistry(); + eventBus.subscribe( + DisconnectEvent.class, event -> userRegistry.onDisconnect(event.sessionId())); + ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + scheduler.scheduleAtFixedRate( + new UserCleanupJob( + userRegistry, Duration.ofSeconds(USER_CLEANUP_JOB_RECONNECT_THRESHOLD)), + USER_CLEANUP_JOB_DELAY, + USER_CLEANUP_JOB_PERIOD, + TimeUnit.SECONDS); + scheduler.scheduleAtFixedRate( + new SessionDisconnectJob( + sessionManager, + eventBus, + Duration.ofSeconds(SESSION_DISCONNECT_JOB_TIMEOUT)), + SESSION_DISCONNECT_JOB_DELAY, + SESSION_DISCONNECT_JOB_PERIOD, + TimeUnit.SECONDS); - registerCommands(dispatcher, router, responseDispatcher, userRegistry); + LobbyManager lobbyManager = new LobbyManager(); + registerCommands(dispatcher, router, responseDispatcher, userRegistry, lobbyManager); - NetworkManager networkManager = new NetworkManager(port, sessionManager, router); - networkManager.start(); - } + NetworkManager networkManager = new NetworkManager(port, sessionManager, router); + networkManager.start(); + } - /** - * Registers command parsers and handlers. - * - * @param parserDispatcher the dispatcher responsible for parsing incoming commands - * @param commandRouter the router that dispatches parsed commands to appropriate handlers - * @param responseDispatcher the dispatcher responsible for sending responses back to clients - */ - private static void registerCommands( - CommandParserDispatcher parserDispatcher, - CommandRouter commandRouter, - ResponseDispatcher responseDispatcher, - UserRegistry userRegistry) { - parserDispatcher.register("PING", new PingParser()); - commandRouter.register(PingRequest.class, new PingHandler(responseDispatcher)); + /** + * Registers command parsers and handlers. + * + * @param parserDispatcher the dispatcher responsible for parsing incoming + * commands + * @param commandRouter the router that dispatches parsed commands to + * appropriate handlers + * @param responseDispatcher the dispatcher responsible for sending responses + * back to clients + */ + private static void registerCommands( + CommandParserDispatcher parserDispatcher, + CommandRouter commandRouter, + ResponseDispatcher responseDispatcher, + UserRegistry userRegistry, + LobbyManager lobbyManager) { + parserDispatcher.register("PING", new PingParser()); + commandRouter.register(PingRequest.class, new PingHandler(responseDispatcher)); - parserDispatcher.register("CHECK_USERNAME", new CheckUsernameParser()); - commandRouter.register( - CheckUsernameRequest.class, - new CheckUsernameHandler(responseDispatcher, userRegistry)); + parserDispatcher.register("CHECK_USERNAME", new CheckUsernameParser()); + commandRouter.register( + CheckUsernameRequest.class, + new CheckUsernameHandler(responseDispatcher, userRegistry)); - parserDispatcher.register("LOGIN", new LoginParser()); - commandRouter.register( - LoginRequest.class, new LoginHandler(responseDispatcher, userRegistry)); + parserDispatcher.register("LOGIN", new LoginParser()); + commandRouter.register( + LoginRequest.class, new LoginHandler(responseDispatcher, userRegistry)); - parserDispatcher.register("LOGOUT", new LogoutParser()); - commandRouter.register( - LogoutRequest.class, new LogoutHandler(responseDispatcher, userRegistry)); + parserDispatcher.register("LOGOUT", new LogoutParser()); + commandRouter.register( + LogoutRequest.class, new LogoutHandler(responseDispatcher, userRegistry)); - parserDispatcher.register("SEND_MESSAGE", new SendMessageParser()); - commandRouter.register( - SendMessageRequest.class, new SendMessageHandler(responseDispatcher, userRegistry)); + parserDispatcher.register("SEND_MESSAGE", new SendMessageParser()); + commandRouter.register( + SendMessageRequest.class, new SendMessageHandler(responseDispatcher, userRegistry)); - parserDispatcher.register("GET_MESSAGE_COUNT", new GetMessageCountParser()); - commandRouter.register( - GetMessageCountRequest.class, - new GetMessageCountHandler(responseDispatcher, userRegistry)); + parserDispatcher.register("GET_MESSAGE_COUNT", new GetMessageCountParser()); + commandRouter.register( + GetMessageCountRequest.class, + new GetMessageCountHandler(responseDispatcher, userRegistry)); - parserDispatcher.register("GET_NEXT_MESSAGE", new GetNextMessageParser()); - commandRouter.register( - GetNextMessageRequest.class, - new GetNextMessageHandler(responseDispatcher, userRegistry)); + parserDispatcher.register("GET_NEXT_MESSAGE", new GetNextMessageParser()); + commandRouter.register( + GetNextMessageRequest.class, + new GetNextMessageHandler(responseDispatcher, userRegistry)); - parserDispatcher.register("LIST_USERS", new ListUsersParser()); - commandRouter.register( - ListUsersRequest.class, new ListUsersHandler(responseDispatcher, userRegistry)); - } + parserDispatcher.register("LIST_USERS", new ListUsersParser()); + commandRouter.register( + ListUsersRequest.class, new ListUsersHandler(responseDispatcher, userRegistry)); + + // Lobby commands + parserDispatcher.register( + "JOIN_LOBBY", + new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.join_lobby.JoinLobbyParser()); + commandRouter.register( + ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.join_lobby.JoinLobbyRequest.class, + new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.join_lobby.JoinLobbyHandler( + responseDispatcher, lobbyManager, userRegistry)); + + + // Game state: allow client to request game snapshot for their lobby + parserDispatcher.register( + "GET_GAME_STATE", + new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.get_game_state.GetGameStateParser()); + commandRouter.register( + ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.get_game_state.GetGameStateRequest.class, + new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.get_game_state.GetGameStateHandler( + responseDispatcher, lobbyManager, userRegistry)); + } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyHandler.java index 33ca37e..579c281 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyHandler.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyHandler.java @@ -13,12 +13,10 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatch /** * Handler for the `JOIN_LOBBY` command. * - *

- * Resolves the username from the session (requires {@link UserLoggedInCheck}), - * looks up the target lobby and attempts to add the user. On success an - * `+OK` response is dispatched; on failure an appropriate {@link ErrorResponse} - * with one of the error codes `LOBBY_NOT_FOUND`, `USER_NOT_LOGGED_IN` or - * `LOBBY_FULL_OR_ALREADY_IN` is returned. + *

Resolves the username from the session (requires {@link UserLoggedInCheck}), looks up the + * target lobby and attempts to add the user. On success an `+OK` response is dispatched; on failure + * an appropriate {@link ErrorResponse} with one of the error codes `LOBBY_NOT_FOUND`, + * `USER_NOT_LOGGED_IN` or `LOBBY_FULL_OR_ALREADY_IN` is returned. */ public class JoinLobbyHandler extends CommandHandler { private final LobbyManager lobbyManager; @@ -27,10 +25,9 @@ public class JoinLobbyHandler extends CommandHandler { /** * Create a new {@link JoinLobbyHandler}. * - * @param responseDispatcher dispatcher used to send responses back to the - * client - * @param lobbyManager manager providing lobby state and operations - * @param userRegistry registry to resolve session -> user mappings + * @param responseDispatcher dispatcher used to send responses back to the client + * @param lobbyManager manager providing lobby state and operations + * @param userRegistry registry to resolve session -> user mappings */ public JoinLobbyHandler( ResponseDispatcher responseDispatcher, diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyParser.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyParser.java index c7cb217..6f7b892 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyParser.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyParser.java @@ -7,15 +7,14 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.accessor. /** * Parser for the `JOIN_LOBBY` command. * - *

- * Expected request parameters: + *

Expected request parameters: + * *

    - *
  • `ID` (int) — numeric lobby identifier (required) + *
  • `ID` (int) — numeric lobby identifier (required) *
* - *

- * The parser builds a {@link JoinLobbyRequest} containing the parsed lobby id - * and the original request context. + *

The parser builds a {@link JoinLobbyRequest} containing the parsed lobby id and the original + * request context. */ public class JoinLobbyParser implements CommandParser { /** @@ -26,7 +25,8 @@ public class JoinLobbyParser implements CommandParser { */ @Override public JoinLobbyRequest parse(PrimitiveRequest primitiveRequest) { - RequestParameterAccessor accessor = new RequestParameterAccessor(primitiveRequest.parameters()); + RequestParameterAccessor accessor = + new RequestParameterAccessor(primitiveRequest.parameters()); int id = accessor.require("ID", Integer::parseInt); return new JoinLobbyRequest(primitiveRequest.context(), id); } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyRequest.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyRequest.java index f667a66..4402707 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyRequest.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyRequest.java @@ -6,9 +6,8 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestCo /** * Request data for the `JOIN_LOBBY` command. * - *

- * Contains the lobby id the client wants to join and inherits the - * {@link Request} contextual information. + *

Contains the lobby id the client wants to join and inherits the {@link Request} contextual + * information. */ public class JoinLobbyRequest extends Request { private final int id; @@ -17,7 +16,7 @@ public class JoinLobbyRequest extends Request { * Create a new {@link JoinLobbyRequest}. * * @param context the request context - * @param id numeric lobby id to join + * @param id numeric lobby id to join */ public JoinLobbyRequest(RequestContext context, int id) { super(context); From de0d0f93f84033e20e211faebd8e4e04ef3472d9 Mon Sep 17 00:00:00 2001 From: Jona Walpert Date: Sat, 11 Apr 2026 19:30:54 +0200 Subject: [PATCH 07/10] Docs: Applied Spotless to JoinLobby command --- .../lobby/join_lobby/JoinLobbyHandler.java | 17 +++++++---------- .../lobby/join_lobby/JoinLobbyParser.java | 14 +++++++------- .../lobby/join_lobby/JoinLobbyRequest.java | 7 +++---- 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyHandler.java index 33ca37e..579c281 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyHandler.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyHandler.java @@ -13,12 +13,10 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatch /** * Handler for the `JOIN_LOBBY` command. * - *

- * Resolves the username from the session (requires {@link UserLoggedInCheck}), - * looks up the target lobby and attempts to add the user. On success an - * `+OK` response is dispatched; on failure an appropriate {@link ErrorResponse} - * with one of the error codes `LOBBY_NOT_FOUND`, `USER_NOT_LOGGED_IN` or - * `LOBBY_FULL_OR_ALREADY_IN` is returned. + *

Resolves the username from the session (requires {@link UserLoggedInCheck}), looks up the + * target lobby and attempts to add the user. On success an `+OK` response is dispatched; on failure + * an appropriate {@link ErrorResponse} with one of the error codes `LOBBY_NOT_FOUND`, + * `USER_NOT_LOGGED_IN` or `LOBBY_FULL_OR_ALREADY_IN` is returned. */ public class JoinLobbyHandler extends CommandHandler { private final LobbyManager lobbyManager; @@ -27,10 +25,9 @@ public class JoinLobbyHandler extends CommandHandler { /** * Create a new {@link JoinLobbyHandler}. * - * @param responseDispatcher dispatcher used to send responses back to the - * client - * @param lobbyManager manager providing lobby state and operations - * @param userRegistry registry to resolve session -> user mappings + * @param responseDispatcher dispatcher used to send responses back to the client + * @param lobbyManager manager providing lobby state and operations + * @param userRegistry registry to resolve session -> user mappings */ public JoinLobbyHandler( ResponseDispatcher responseDispatcher, diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyParser.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyParser.java index c7cb217..6f7b892 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyParser.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyParser.java @@ -7,15 +7,14 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.accessor. /** * Parser for the `JOIN_LOBBY` command. * - *

- * Expected request parameters: + *

Expected request parameters: + * *

    - *
  • `ID` (int) — numeric lobby identifier (required) + *
  • `ID` (int) — numeric lobby identifier (required) *
* - *

- * The parser builds a {@link JoinLobbyRequest} containing the parsed lobby id - * and the original request context. + *

The parser builds a {@link JoinLobbyRequest} containing the parsed lobby id and the original + * request context. */ public class JoinLobbyParser implements CommandParser { /** @@ -26,7 +25,8 @@ public class JoinLobbyParser implements CommandParser { */ @Override public JoinLobbyRequest parse(PrimitiveRequest primitiveRequest) { - RequestParameterAccessor accessor = new RequestParameterAccessor(primitiveRequest.parameters()); + RequestParameterAccessor accessor = + new RequestParameterAccessor(primitiveRequest.parameters()); int id = accessor.require("ID", Integer::parseInt); return new JoinLobbyRequest(primitiveRequest.context(), id); } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyRequest.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyRequest.java index f667a66..4402707 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyRequest.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/join_lobby/JoinLobbyRequest.java @@ -6,9 +6,8 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestCo /** * Request data for the `JOIN_LOBBY` command. * - *

- * Contains the lobby id the client wants to join and inherits the - * {@link Request} contextual information. + *

Contains the lobby id the client wants to join and inherits the {@link Request} contextual + * information. */ public class JoinLobbyRequest extends Request { private final int id; @@ -17,7 +16,7 @@ public class JoinLobbyRequest extends Request { * Create a new {@link JoinLobbyRequest}. * * @param context the request context - * @param id numeric lobby id to join + * @param id numeric lobby id to join */ public JoinLobbyRequest(RequestContext context, int id) { super(context); From 203004fc42dc30972fd8cdee33166939ced97342 Mon Sep 17 00:00:00 2001 From: Jona Walpert Date: Sat, 11 Apr 2026 19:31:33 +0200 Subject: [PATCH 08/10] Add: register JoinLobby command --- .../dbis/cs108/casono/server/ServerApp.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java index 7062252..e77d457 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java @@ -24,6 +24,7 @@ import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.ping.PingRequest; import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.send_message.SendMessageHandler; import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.send_message.SendMessageParser; import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.send_message.SendMessageRequest; +import ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby.LobbyManager; import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserCleanupJob; import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserRegistry; import ch.unibas.dmi.dbis.cs108.casono.server.network.NetworkManager; @@ -85,7 +86,8 @@ public class ServerApp { SESSION_DISCONNECT_JOB_PERIOD, TimeUnit.SECONDS); - registerCommands(dispatcher, router, responseDispatcher, userRegistry); + LobbyManager lobbyManager = new LobbyManager(); + registerCommands(dispatcher, router, responseDispatcher, userRegistry, lobbyManager); NetworkManager networkManager = new NetworkManager(port, sessionManager, router); networkManager.start(); @@ -102,7 +104,8 @@ public class ServerApp { CommandParserDispatcher parserDispatcher, CommandRouter commandRouter, ResponseDispatcher responseDispatcher, - UserRegistry userRegistry) { + UserRegistry userRegistry, + LobbyManager lobbyManager) { parserDispatcher.register("PING", new PingParser()); commandRouter.register(PingRequest.class, new PingHandler(responseDispatcher)); @@ -136,5 +139,16 @@ public class ServerApp { parserDispatcher.register("LIST_USERS", new ListUsersParser()); commandRouter.register( ListUsersRequest.class, new ListUsersHandler(responseDispatcher, userRegistry)); + + // Lobby commands + parserDispatcher.register( + "JOIN_LOBBY", + new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.join_lobby + .JoinLobbyParser()); + commandRouter.register( + ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.join_lobby + .JoinLobbyRequest.class, + new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.join_lobby + .JoinLobbyHandler(responseDispatcher, lobbyManager, userRegistry)); } } From 15d1ea4edd19f5b18d95e50feed0f3212e68314a Mon Sep 17 00:00:00 2001 From: Jona Walpert Date: Sat, 11 Apr 2026 19:45:55 +0200 Subject: [PATCH 09/10] Add: Register JoinLobby command --- .../dbis/cs108/casono/server/ServerApp.java | 52 ++++++++++++------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java index fd074ba..2795fb9 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java @@ -86,26 +86,31 @@ public class ServerApp { SESSION_DISCONNECT_JOB_PERIOD, TimeUnit.SECONDS); - registerCommands(dispatcher, router, responseDispatcher, userRegistry); + LobbyManager lobbyManager = new LobbyManager(); + registerCommands(dispatcher, router, responseDispatcher, userRegistry, lobbyManager); NetworkManager networkManager = new NetworkManager(port, sessionManager, router); networkManager.start(); } - /** - * Registers command parsers and handlers. - * - * @param parserDispatcher the dispatcher responsible for parsing incoming commands - * @param commandRouter the router that dispatches parsed commands to appropriate handlers - * @param responseDispatcher the dispatcher responsible for sending responses back to clients - */ - private static void registerCommands( - CommandParserDispatcher parserDispatcher, - CommandRouter commandRouter, - ResponseDispatcher responseDispatcher, - UserRegistry userRegistry) { - parserDispatcher.register("PING", new PingParser()); - commandRouter.register(PingRequest.class, new PingHandler(responseDispatcher)); + /** + * Registers command parsers and handlers. + * + * @param parserDispatcher the dispatcher responsible for parsing incoming + * commands + * @param commandRouter the router that dispatches parsed commands to + * appropriate handlers + * @param responseDispatcher the dispatcher responsible for sending responses + * back to clients + */ + private static void registerCommands( + CommandParserDispatcher parserDispatcher, + CommandRouter commandRouter, + ResponseDispatcher responseDispatcher, + UserRegistry userRegistry, + LobbyManager lobbyManager) { + parserDispatcher.register("PING", new PingParser()); + commandRouter.register(PingRequest.class, new PingHandler(responseDispatcher)); parserDispatcher.register("CHECK_USERNAME", new CheckUsernameParser()); commandRouter.register( @@ -134,8 +139,17 @@ public class ServerApp { GetNextMessageRequest.class, new GetNextMessageHandler(responseDispatcher, userRegistry)); - parserDispatcher.register("LIST_USERS", new ListUsersParser()); - commandRouter.register( - ListUsersRequest.class, new ListUsersHandler(responseDispatcher, userRegistry)); - } + parserDispatcher.register("LIST_USERS", new ListUsersParser()); + commandRouter.register( + ListUsersRequest.class, new ListUsersHandler(responseDispatcher, userRegistry)); + + // JOIN_LOBBY registration + parserDispatcher.register( + "JOIN_LOBBY", + new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.join_lobby.JoinLobbyParser()); + commandRouter.register( + ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.join_lobby.JoinLobbyRequest.class, + (ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler) new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.join_lobby.JoinLobbyHandler( + responseDispatcher, lobbyManager, userRegistry)); + } } From 240bba9bf51c40c483360fe28392a47ca2c5c78d Mon Sep 17 00:00:00 2001 From: Jona Walpert Date: Sat, 11 Apr 2026 19:46:48 +0200 Subject: [PATCH 10/10] Style: apply Spotless for Serverapp --- .../dbis/cs108/casono/server/ServerApp.java | 184 +++++++++--------- 1 file changed, 93 insertions(+), 91 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java index 2795fb9..881705a 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java @@ -45,111 +45,113 @@ import org.apache.logging.log4j.Logger; /** Application class for starting the server. */ public class ServerApp { - private static final int USER_CLEANUP_JOB_DELAY = 0; - private static final int USER_CLEANUP_JOB_PERIOD = 10; - private static final int USER_CLEANUP_JOB_RECONNECT_THRESHOLD = 10; - private static final int SESSION_DISCONNECT_JOB_DELAY = 0; - private static final int SESSION_DISCONNECT_JOB_PERIOD = 2; - private static final int SESSION_DISCONNECT_JOB_TIMEOUT = 5; + private static final int USER_CLEANUP_JOB_DELAY = 0; + private static final int USER_CLEANUP_JOB_PERIOD = 10; + private static final int USER_CLEANUP_JOB_RECONNECT_THRESHOLD = 10; + private static final int SESSION_DISCONNECT_JOB_DELAY = 0; + private static final int SESSION_DISCONNECT_JOB_PERIOD = 2; + private static final int SESSION_DISCONNECT_JOB_TIMEOUT = 5; - public static void start(String arg) { - int port = Integer.parseInt(arg); + public static void start(String arg) { + int port = Integer.parseInt(arg); - Logger logger = LogManager.getLogger(ServerApp.class); - logger.info("Starting server at port {}", port); + Logger logger = LogManager.getLogger(ServerApp.class); + logger.info("Starting server at port {}", port); - EventBus eventBus = new EventBus(); - CommandParserDispatcher dispatcher = new CommandParserDispatcher(); - SessionManager sessionManager = new SessionManager(eventBus, dispatcher); - ResponseDispatcher responseDispatcher = new ResponseDispatcher(sessionManager); - CommandHandlerExecutor handlerExecutor = new CommandHandlerExecutor(responseDispatcher); - CommandRouter router = new CommandRouter(handlerExecutor); + EventBus eventBus = new EventBus(); + CommandParserDispatcher dispatcher = new CommandParserDispatcher(); + SessionManager sessionManager = new SessionManager(eventBus, dispatcher); + ResponseDispatcher responseDispatcher = new ResponseDispatcher(sessionManager); + CommandHandlerExecutor handlerExecutor = new CommandHandlerExecutor(responseDispatcher); + CommandRouter router = new CommandRouter(handlerExecutor); - eventBus.subscribe(DisconnectEvent.class, event -> sessionManager.onDisconnect(event)); + eventBus.subscribe(DisconnectEvent.class, event -> sessionManager.onDisconnect(event)); - UserRegistry userRegistry = new UserRegistry(); - eventBus.subscribe( - DisconnectEvent.class, event -> userRegistry.onDisconnect(event.sessionId())); - ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); - scheduler.scheduleAtFixedRate( - new UserCleanupJob( - userRegistry, Duration.ofSeconds(USER_CLEANUP_JOB_RECONNECT_THRESHOLD)), - USER_CLEANUP_JOB_DELAY, - USER_CLEANUP_JOB_PERIOD, - TimeUnit.SECONDS); - scheduler.scheduleAtFixedRate( - new SessionDisconnectJob( - sessionManager, - eventBus, - Duration.ofSeconds(SESSION_DISCONNECT_JOB_TIMEOUT)), - SESSION_DISCONNECT_JOB_DELAY, - SESSION_DISCONNECT_JOB_PERIOD, - TimeUnit.SECONDS); + UserRegistry userRegistry = new UserRegistry(); + eventBus.subscribe( + DisconnectEvent.class, event -> userRegistry.onDisconnect(event.sessionId())); + ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + scheduler.scheduleAtFixedRate( + new UserCleanupJob( + userRegistry, Duration.ofSeconds(USER_CLEANUP_JOB_RECONNECT_THRESHOLD)), + USER_CLEANUP_JOB_DELAY, + USER_CLEANUP_JOB_PERIOD, + TimeUnit.SECONDS); + scheduler.scheduleAtFixedRate( + new SessionDisconnectJob( + sessionManager, + eventBus, + Duration.ofSeconds(SESSION_DISCONNECT_JOB_TIMEOUT)), + SESSION_DISCONNECT_JOB_DELAY, + SESSION_DISCONNECT_JOB_PERIOD, + TimeUnit.SECONDS); - LobbyManager lobbyManager = new LobbyManager(); - registerCommands(dispatcher, router, responseDispatcher, userRegistry, lobbyManager); + LobbyManager lobbyManager = new LobbyManager(); + registerCommands(dispatcher, router, responseDispatcher, userRegistry, lobbyManager); - NetworkManager networkManager = new NetworkManager(port, sessionManager, router); - networkManager.start(); - } + NetworkManager networkManager = new NetworkManager(port, sessionManager, router); + networkManager.start(); + } - /** - * Registers command parsers and handlers. - * - * @param parserDispatcher the dispatcher responsible for parsing incoming - * commands - * @param commandRouter the router that dispatches parsed commands to - * appropriate handlers - * @param responseDispatcher the dispatcher responsible for sending responses - * back to clients - */ - private static void registerCommands( - CommandParserDispatcher parserDispatcher, - CommandRouter commandRouter, - ResponseDispatcher responseDispatcher, - UserRegistry userRegistry, - LobbyManager lobbyManager) { - parserDispatcher.register("PING", new PingParser()); - commandRouter.register(PingRequest.class, new PingHandler(responseDispatcher)); + /** + * Registers command parsers and handlers. + * + * @param parserDispatcher the dispatcher responsible for parsing incoming commands + * @param commandRouter the router that dispatches parsed commands to appropriate handlers + * @param responseDispatcher the dispatcher responsible for sending responses back to clients + */ + private static void registerCommands( + CommandParserDispatcher parserDispatcher, + CommandRouter commandRouter, + ResponseDispatcher responseDispatcher, + UserRegistry userRegistry, + LobbyManager lobbyManager) { + parserDispatcher.register("PING", new PingParser()); + commandRouter.register(PingRequest.class, new PingHandler(responseDispatcher)); - parserDispatcher.register("CHECK_USERNAME", new CheckUsernameParser()); - commandRouter.register( - CheckUsernameRequest.class, - new CheckUsernameHandler(responseDispatcher, userRegistry)); + parserDispatcher.register("CHECK_USERNAME", new CheckUsernameParser()); + commandRouter.register( + CheckUsernameRequest.class, + new CheckUsernameHandler(responseDispatcher, userRegistry)); - parserDispatcher.register("LOGIN", new LoginParser()); - commandRouter.register( - LoginRequest.class, new LoginHandler(responseDispatcher, userRegistry)); + parserDispatcher.register("LOGIN", new LoginParser()); + commandRouter.register( + LoginRequest.class, new LoginHandler(responseDispatcher, userRegistry)); - parserDispatcher.register("LOGOUT", new LogoutParser()); - commandRouter.register( - LogoutRequest.class, new LogoutHandler(responseDispatcher, userRegistry)); + parserDispatcher.register("LOGOUT", new LogoutParser()); + commandRouter.register( + LogoutRequest.class, new LogoutHandler(responseDispatcher, userRegistry)); - parserDispatcher.register("SEND_MESSAGE", new SendMessageParser()); - commandRouter.register( - SendMessageRequest.class, new SendMessageHandler(responseDispatcher, userRegistry)); + parserDispatcher.register("SEND_MESSAGE", new SendMessageParser()); + commandRouter.register( + SendMessageRequest.class, new SendMessageHandler(responseDispatcher, userRegistry)); - parserDispatcher.register("GET_MESSAGE_COUNT", new GetMessageCountParser()); - commandRouter.register( - GetMessageCountRequest.class, - new GetMessageCountHandler(responseDispatcher, userRegistry)); + parserDispatcher.register("GET_MESSAGE_COUNT", new GetMessageCountParser()); + commandRouter.register( + GetMessageCountRequest.class, + new GetMessageCountHandler(responseDispatcher, userRegistry)); - parserDispatcher.register("GET_NEXT_MESSAGE", new GetNextMessageParser()); - commandRouter.register( - GetNextMessageRequest.class, - new GetNextMessageHandler(responseDispatcher, userRegistry)); + parserDispatcher.register("GET_NEXT_MESSAGE", new GetNextMessageParser()); + commandRouter.register( + GetNextMessageRequest.class, + new GetNextMessageHandler(responseDispatcher, userRegistry)); - parserDispatcher.register("LIST_USERS", new ListUsersParser()); - commandRouter.register( - ListUsersRequest.class, new ListUsersHandler(responseDispatcher, userRegistry)); + parserDispatcher.register("LIST_USERS", new ListUsersParser()); + commandRouter.register( + ListUsersRequest.class, new ListUsersHandler(responseDispatcher, userRegistry)); - // JOIN_LOBBY registration - parserDispatcher.register( - "JOIN_LOBBY", - new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.join_lobby.JoinLobbyParser()); - commandRouter.register( - ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.join_lobby.JoinLobbyRequest.class, - (ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler) new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.join_lobby.JoinLobbyHandler( - responseDispatcher, lobbyManager, userRegistry)); - } + // JOIN_LOBBY registration + parserDispatcher.register( + "JOIN_LOBBY", + new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.join_lobby + .JoinLobbyParser()); + commandRouter.register( + ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.join_lobby + .JoinLobbyRequest.class, + (ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler< + ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.join_lobby + .JoinLobbyRequest>) + new ch.unibas.dmi.dbis.cs108.casono.server.app.commands.lobby.join_lobby + .JoinLobbyHandler(responseDispatcher, lobbyManager, userRegistry)); + } }