From e6b5cac53caceb870c26b8db268a1699333f0a85 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Thu, 5 Mar 2026 13:28:04 +0100 Subject: [PATCH 01/61] Add: SessionId class to uniquely identify session in SessionRegistry --- .../casono/common/network/SessionId.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/common/network/SessionId.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/common/network/SessionId.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/common/network/SessionId.java new file mode 100644 index 0000000..a7cafa4 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/common/network/SessionId.java @@ -0,0 +1,22 @@ +package ch.unibas.dmi.dbis.cs108.casono.common.network; + +import java.util.UUID; + +public class SessionId { + /** + * The session id is used to identify a unique client connection in the SessionRegistry + */ + private final UUID value; + + public SessionId() { + this.value = UUID.randomUUID(); + } + + public SessionId(UUID value) { + this.value = value; + } + + public UUID value() { + return value; + } +} From ff38c9d6ef8fce0bedafaba6e2489bd6484c9e64 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Thu, 5 Mar 2026 13:28:04 +0100 Subject: [PATCH 02/61] Add: SessionId class to uniquely identify session in SessionRegistry --- .../unibas/dmi/dbis/cs108/casono/common/network/SessionId.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/common/network/SessionId.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/common/network/SessionId.java index a7cafa4..fded15a 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/common/network/SessionId.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/common/network/SessionId.java @@ -4,7 +4,7 @@ import java.util.UUID; public class SessionId { /** - * The session id is used to identify a unique client connection in the SessionRegistry + * The SessionId is used to identify a unique client connection in the SessionRegistry */ private final UUID value; From 66925bc66541c5db0275380ccd42da8efb5e864f Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Thu, 5 Mar 2026 13:46:10 +0100 Subject: [PATCH 03/61] Add: Session interface all sessions need to conform to --- .../dbis/cs108/casono/common/network/Session.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/common/network/Session.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/common/network/Session.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/common/network/Session.java new file mode 100644 index 0000000..f4ac359 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/common/network/Session.java @@ -0,0 +1,13 @@ +package ch.unibas.dmi.dbis.cs108.casono.common.network; + +public interface Session { + /** + * Outlines an interface all session implementations have to conform to + */ + + SessionId id(); + + void send(byte[] payload); + + void close(); +} \ No newline at end of file From 91a513e2148e533ea5c77dbf56b5f2b7d99676c9 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Mon, 9 Mar 2026 15:30:50 +0100 Subject: [PATCH 04/61] Add: Readme explaining the purpose, format and links to existing blog posts --- documents/docs/blog/README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 documents/docs/blog/README.md diff --git a/documents/docs/blog/README.md b/documents/docs/blog/README.md new file mode 100644 index 0000000..aa92b15 --- /dev/null +++ b/documents/docs/blog/README.md @@ -0,0 +1,28 @@ +# Blog + +## Purpose +This folder contains a collection of blog posts documenting the development process of our application. These blogs serve two key purposes: + +1. **For External Audiences** + + The blogs provide insights into the internal program flow and the interaction between components without requiring readers to dive into the source code. They offer a mixed-level overview of how different parts of the system work together. + +2. **For Our Team** + + The blogs serve as a record of our decision-making process. By documenting what decisions were made and why, we can track the evolution of our design choices and understand the reasoning behind them. This is invaluable for maintaining consistency across the team. + +But keep in mind that these blogs **do not replace** the detailed source code documentation. + +## Format +All blog posts follow the naming convention: `_.md` + +## Blog Posts + +### General +*(No posts yet)* + +### Client +*(No posts yet)* + +### Server +*(No posts yet)* From 9c291e4e3bcdd2cf08cac8cac4180cd15aad818e Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Mon, 9 Mar 2026 15:37:26 +0100 Subject: [PATCH 05/61] Revert "Add: Readme explaining the purpose, format and links to existing blog posts" This reverts commit 91a513e2148e533ea5c77dbf56b5f2b7d99676c9. --- documents/docs/blog/README.md | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 documents/docs/blog/README.md diff --git a/documents/docs/blog/README.md b/documents/docs/blog/README.md deleted file mode 100644 index aa92b15..0000000 --- a/documents/docs/blog/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# Blog - -## Purpose -This folder contains a collection of blog posts documenting the development process of our application. These blogs serve two key purposes: - -1. **For External Audiences** - - The blogs provide insights into the internal program flow and the interaction between components without requiring readers to dive into the source code. They offer a mixed-level overview of how different parts of the system work together. - -2. **For Our Team** - - The blogs serve as a record of our decision-making process. By documenting what decisions were made and why, we can track the evolution of our design choices and understand the reasoning behind them. This is invaluable for maintaining consistency across the team. - -But keep in mind that these blogs **do not replace** the detailed source code documentation. - -## Format -All blog posts follow the naming convention: `<YYYY-MM-DD>_<Title>.md` - -## Blog Posts - -### General -*(No posts yet)* - -### Client -*(No posts yet)* - -### Server -*(No posts yet)* From 22990fa35e3b8c37e77e005b9de8e8f6811a3682 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Wed, 11 Mar 2026 11:37:04 +0100 Subject: [PATCH 06/61] Add: Document outlining the core components and roles in the server-side networking --- .../docs/networking/server-architecture.md | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 documents/docs/networking/server-architecture.md diff --git a/documents/docs/networking/server-architecture.md b/documents/docs/networking/server-architecture.md new file mode 100644 index 0000000..93ad908 --- /dev/null +++ b/documents/docs/networking/server-architecture.md @@ -0,0 +1,81 @@ +# Server-Side Networking — How It All Fits Together + +This doc is meant to give a solid mixed-level understanding of how our server-side networking works. No deep dives — just a clear picture of what's happening under the hood when a client connects and sends a command. + +> **Note:** This is v1. Annotations and Reflection-based dispatching are on the roadmap but not covered here yet. + +> **Disclamer:** This document has been written by Claude. I modified certain parts and verified its contents for correctness. + +## Our Protocol at a Glance + +Our protocol is inspired by **POP3** - a classic, text-based protocol that communicates over a raw TCP connection. The idea is simple: the client sends a command as a plain-text string, and the server responds with either a success or an error. + +A typical exchange looks something like this: + +``` +Client → GET_DELTA SINCE=42 +Server → +OK + PLAYER_FOLDED playerId=3 + POT 240 + NEXT_TURN playerId=1 + . +``` + +Responses start with `+OK` on success or `-ERR` when something goes wrong. Commands are short, uppercase strings - sometimes followed by arguments. + +We don't use HTTP, there's no JSON body, no headers. Just a raw socket, a text stream, and a clearly defined set of commands. + +## Core Components & Their Roles + +Here's a quick rundown of the main building blocks: + +### `NetworkManager` +This is the entry point. It binds to a specific port and listens for incoming TCP connections. +Once a client connects, it creates a `Session` adds it to the `SessionManager` and goes back to waiting. Its not the responsibility of the `NetworkManager` to recieve and send data from and to each connected client. + +### `Session` +Every client that connects has its own `Session` instance running on its own thread. This component owns the lifecycle of that connection: it reads incoming data, passes it along for processing, and writes responses back. + +### `SessionManager` +The `SessionManager` stores all currently active sessions. It allows for the retrieval of a specific session by its id. + +### `ProtocolParser` +Raw text (encapsulated in a `RawRequest`) coming off the socket isn't immediately useful — the `ProtocolParser` turns it with the help of the `Tokenizer` into a `PrimitiveRequest`. + +### `RawRequest` +The `RawRequest` object is created after a message has been recieved. +It holds both a reference to the `Session` that recieved the message and the message itself. + +### `PrimitiveRequest` +The `PrimitiveRequest` object is created by the `ProtocolParser` after the content of the `RawRequest` has been tokenized by the `Tokenizer`. +The command is stored in its own attribute and the arguments are accessible as a dictionary. + +### `Tokenizer` +The job of the `tokenizer` is it, to take the raw string and turn it into tokens. +For example, the command `CHAT_LOBBY GAME=12 MESSAGE='All-in?'` will be decoded as `"CHAT_LOBBY", "GAME", "=", "12", "MESSAGE", "=", "All-in?"`. +It is unaware about the meaning. + +### `CommandParser` (per command) +Takes in the `PrimitiveRequest` and checks if all required fields are provided with the correct value. +Creates a command specific `Request` object containing command arguments in a structured manner. + +### `Request` (per command) +Has fields common along each implementation such as a reference to the `Session` that recieved the request. +Furthermore each implementation has fields unique to each command. For example, the `ChatLobbyRequest` has fields for the game and the message. + +### `CommandRouter` +By identifying the type of class of the `Request`, the `CommandRouter` routes the request to the matching `CommandHandler`. + +### `CommandHandler` (per command) +Each supported command has its own handler — a small, focused class that contains the logic for that specific command. They have access to the domain, containing inner parts of the game itself. + +### `Response` (per response) +The interface has two sub-interfaces for either failed `ErrorResponse` or successfull `SuccessResponse` execution of commands. + +Each step has exactly one responsibility. Increasing the ability to test and extend different components later on. + +## Concurrency - Handling Multiple Clients + +Every incoming connection spawns a new **Thread**. This means multiple clients can be served simultaneously without blocking each other. + +As each `Session` runs entirely on its own thread, there's no shared mutable state between sessions, with exception of the `SessionManager` and other key components explained later. From 32e4b04c87a31b288de398422ee7bed6baa9b9f8 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Wed, 11 Mar 2026 11:43:27 +0100 Subject: [PATCH 07/61] Add: SessionId to identify each session --- documents/docs/networking/server-architecture.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/documents/docs/networking/server-architecture.md b/documents/docs/networking/server-architecture.md index 93ad908..1be14eb 100644 --- a/documents/docs/networking/server-architecture.md +++ b/documents/docs/networking/server-architecture.md @@ -36,6 +36,9 @@ Once a client connects, it creates a `Session` adds it to the `SessionManager` a ### `Session` Every client that connects has its own `Session` instance running on its own thread. This component owns the lifecycle of that connection: it reads incoming data, passes it along for processing, and writes responses back. +### `SessionId` +The `SessionId` is used to uniquely identify each session. Essentially its a wrapper arround the UUID type. + ### `SessionManager` The `SessionManager` stores all currently active sessions. It allows for the retrieval of a specific session by its id. From fb6f14371ff191031ad56f33ae6ded8ccbbd33b9 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Wed, 11 Mar 2026 11:49:46 +0100 Subject: [PATCH 08/61] Add: PlantUML diagramm outlining each component --- .../networking_components.puml | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 documents/images/docs/networking/server-architecure/networking_components.puml diff --git a/documents/images/docs/networking/server-architecure/networking_components.puml b/documents/images/docs/networking/server-architecure/networking_components.puml new file mode 100644 index 0000000..647654a --- /dev/null +++ b/documents/images/docs/networking/server-architecure/networking_components.puml @@ -0,0 +1,96 @@ +@startuml +skinparam classAttributeIconSize 0 +skinparam packageStyle rectangle +skinparam linetype ortho +skinparam backgroundColor transparent + +' Network Layer +class NetworkManager { + + start(port: int) +} + +class Session { + - id: SessionId + + Session(id: SessionId) + + getId(): SessionId + + run() + + send(response: String) +} + +class SessionId { + - value: UUID + + SessionId() + + SessionId(value: UUID) +} + +class SessionManager { + - sessions: Map<UUID, Session> + + add(session: Session) + + getById(id: UUID): Session + + remove(id: UUID) +} + +NetworkManager --> Session : creates +NetworkManager --> SessionManager : adds session to +Session --> SessionId : identified by +Session --> RawRequest : creates +Session ..> Response : sends +SessionManager o-- Session : manages + +' Protocol Layer +class RawRequest { + - session: Session + - rawMessage: String +} + +class Tokenizer { + + tokenize(input: String): List<String> +} + +class ProtocolParser { + + parse(raw: RawRequest): PrimitiveRequest +} + +class PrimitiveRequest { + - command: String + - arguments: Map<String, String> +} + +RawRequest --> ProtocolParser : passed to +ProtocolParser --> Tokenizer : uses +ProtocolParser --> PrimitiveRequest : produces + +' Command Layer +interface CommandParser <<per command>> { + + parse(primitive: PrimitiveRequest): Request +} + +interface Request <<per command>> { + - session: Session +} + +class CommandRouter { + + route(request: Request) +} + +interface CommandHandler <<per command>> { + + handle(request: Request) +} + +PrimitiveRequest --> CommandParser : passed to +CommandParser --> Request : produces +CommandRouter --> CommandHandler : dispatches to +CommandRouter ..> Request : receives + +' Response Layer +interface Response { + + encode(): String +} + +interface SuccessResponse + +interface ErrorResponse + +Response <|-- SuccessResponse +Response <|-- ErrorResponse +@enduml From a5272b99ab44785de568ebf13f9376fc3f9a7c89 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Wed, 11 Mar 2026 11:50:59 +0100 Subject: [PATCH 09/61] Add: Include diagramm in document --- documents/docs/networking/server-architecture.md | 2 ++ .../networking/server-architecure/networking_components.svg | 1 + 2 files changed, 3 insertions(+) create mode 100644 documents/images/docs/networking/server-architecure/networking_components.svg diff --git a/documents/docs/networking/server-architecture.md b/documents/docs/networking/server-architecture.md index 1be14eb..8537382 100644 --- a/documents/docs/networking/server-architecture.md +++ b/documents/docs/networking/server-architecture.md @@ -27,6 +27,8 @@ We don't use HTTP, there's no JSON body, no headers. Just a raw socket, a text s ## Core Components & Their Roles +![PlantUML diagramm of all components outlined in this document](/documents/images/docs/networking/server-architecure/networking_components.svg) + Here's a quick rundown of the main building blocks: ### `NetworkManager` diff --git a/documents/images/docs/networking/server-architecure/networking_components.svg b/documents/images/docs/networking/server-architecure/networking_components.svg new file mode 100644 index 0000000..9db9f6d --- /dev/null +++ b/documents/images/docs/networking/server-architecure/networking_components.svg @@ -0,0 +1 @@ +<?plantuml 1.2026.2?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" data-diagram-type="CLASS" height="1132px" preserveAspectRatio="none" style="width:820px;height:1132px;" version="1.1" viewBox="0 0 820 1132" width="820px" zoomAndPan="magnify"><defs/><g><!--class NetworkManager--><g class="entity" data-qualified-name="NetworkManager" data-source-line="7" id="ent0002"><rect fill="#F1F1F1" height="64.4883" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="147.1445" x="257.37" y="7"/><ellipse cx="272.37" cy="23" fill="#ADD1B2" rx="11" ry="11" style="stroke:#181818;stroke-width:1;"/><path d="M274.8431,29.1431 Q274.2621,29.4419 273.6229,29.5913 Q272.9838,29.7407 272.2782,29.7407 Q269.7714,29.7407 268.4515,28.0889 Q267.1317,26.437 267.1317,23.3159 Q267.1317,20.1865 268.4515,18.5347 Q269.7714,16.8828 272.2782,16.8828 Q272.9838,16.8828 273.6312,17.0322 Q274.2787,17.1816 274.8431,17.4805 L274.8431,20.2031 Q274.2123,19.6221 273.6188,19.3523 Q273.0253,19.0825 272.3944,19.0825 Q271.0497,19.0825 270.3649,20.1492 Q269.6801,21.2158 269.6801,23.3159 Q269.6801,25.4077 270.3649,26.4744 Q271.0497,27.541 272.3944,27.541 Q273.0253,27.541 273.6188,27.2712 Q274.2123,27.0015 274.8431,26.4204 Z " fill="#000000"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="115.1445" x="286.37" y="28.291">NetworkManager</text><line style="stroke:#181818;stroke-width:0.5;" x1="258.37" x2="403.5145" y1="39" y2="39"/><line style="stroke:#181818;stroke-width:0.5;" x1="258.37" x2="403.5145" y1="47" y2="47"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="106.5107" x="263.37" y="64.5352">+start(port: int)</text></g><!--class Session--><g class="entity" data-qualified-name="Session" data-source-line="11" id="ent0003"><rect fill="#F1F1F1" height="130.4414" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="175.2012" x="243.34" y="305.44"/><ellipse cx="301.2165" cy="321.44" fill="#ADD1B2" rx="11" ry="11" style="stroke:#181818;stroke-width:1;"/><path d="M303.6896,327.5831 Q303.1086,327.8819 302.4694,328.0313 Q301.8302,328.1807 301.1247,328.1807 Q298.6178,328.1807 297.298,326.5289 Q295.9782,324.877 295.9782,321.7559 Q295.9782,318.6265 297.298,316.9747 Q298.6178,315.3228 301.1247,315.3228 Q301.8302,315.3228 302.4777,315.4722 Q303.1252,315.6216 303.6896,315.9205 L303.6896,318.6431 Q303.0588,318.0621 302.4652,317.7923 Q301.8717,317.5225 301.2409,317.5225 Q299.8962,317.5225 299.2113,318.5892 Q298.5265,319.6558 298.5265,321.7559 Q298.5265,323.8477 299.2113,324.9144 Q299.8962,325.981 301.2409,325.981 Q301.8717,325.981 302.4652,325.7112 Q303.0588,325.4415 303.6896,324.8604 Z " fill="#000000"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="50.9482" x="321.7165" y="326.731">Session</text><line style="stroke:#181818;stroke-width:0.5;" x1="244.34" x2="417.5412" y1="337.44" y2="337.44"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="93.6113" x="249.34" y="354.9752">-id: SessionId</text><line style="stroke:#181818;stroke-width:0.5;" x1="244.34" x2="417.5412" y1="361.9283" y2="361.9283"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="156.6934" x="249.34" y="379.4634">+Session(id: SessionId)</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="127.4971" x="249.34" y="395.9517">+getId(): SessionId</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="43.3398" x="249.34" y="412.44">+run()</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="163.2012" x="249.34" y="428.9283">+send(response: String)</text></g><!--class SessionId--><g class="entity" data-qualified-name="SessionId" data-source-line="19" id="ent0004"><rect fill="#F1F1F1" height="97.4648" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="174.3193" x="45.78" y="495.88"/><ellipse cx="96.7932" cy="511.88" fill="#ADD1B2" rx="11" ry="11" style="stroke:#181818;stroke-width:1;"/><path d="M99.2663,518.0231 Q98.6853,518.3219 98.0461,518.4713 Q97.407,518.6207 96.7014,518.6207 Q94.1946,518.6207 92.8747,516.9689 Q91.5549,515.317 91.5549,512.1959 Q91.5549,509.0665 92.8747,507.4147 Q94.1946,505.7628 96.7014,505.7628 Q97.407,505.7628 98.0544,505.9122 Q98.7019,506.0616 99.2663,506.3605 L99.2663,509.0831 Q98.6355,508.5021 98.042,508.2323 Q97.4485,507.9625 96.8176,507.9625 Q95.4729,507.9625 94.7881,509.0292 Q94.1032,510.0958 94.1032,512.1959 Q94.1032,514.2877 94.7881,515.3544 Q95.4729,516.421 96.8176,516.421 Q97.4485,516.421 98.042,516.1512 Q98.6355,515.8815 99.2663,515.3004 Z " fill="#000000"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="63.793" x="117.2932" y="517.171">SessionId</text><line style="stroke:#181818;stroke-width:0.5;" x1="46.78" x2="219.0993" y1="527.88" y2="527.88"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="86.3926" x="51.78" y="545.4152">-value: UUID</text><line style="stroke:#181818;stroke-width:0.5;" x1="46.78" x2="219.0993" y1="552.3683" y2="552.3683"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="84.0273" x="51.78" y="569.9034">+SessionId()</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="162.3193" x="51.78" y="586.3917">+SessionId(value: UUID)</text></g><!--class SessionManager--><g class="entity" data-qualified-name="SessionManager" data-source-line="25" id="ent0005"><rect fill="#F1F1F1" height="113.9531" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="231.2285" x="300.33" y="131.49"/><ellipse cx="356.9863" cy="147.49" fill="#ADD1B2" rx="11" ry="11" style="stroke:#181818;stroke-width:1;"/><path d="M359.4594,153.6331 Q358.8783,153.9319 358.2392,154.0813 Q357.6,154.2307 356.8945,154.2307 Q354.3876,154.2307 353.0678,152.5789 Q351.748,150.927 351.748,147.8059 Q351.748,144.6765 353.0678,143.0247 Q354.3876,141.3728 356.8945,141.3728 Q357.6,141.3728 358.2475,141.5222 Q358.8949,141.6716 359.4594,141.9705 L359.4594,144.6931 Q358.8285,144.1121 358.235,143.8423 Q357.6415,143.5725 357.0107,143.5725 Q355.6659,143.5725 354.9811,144.6392 Q354.2963,145.7058 354.2963,147.8059 Q354.2963,149.8977 354.9811,150.9644 Q355.6659,152.031 357.0107,152.031 Q357.6415,152.031 358.235,151.7612 Q358.8285,151.4915 359.4594,150.9104 Z " fill="#000000"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="109.416" x="377.4863" y="152.781">SessionManager</text><line style="stroke:#181818;stroke-width:0.5;" x1="301.33" x2="530.5585" y1="163.49" y2="163.49"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="219.2285" x="306.33" y="181.0252">-sessions: Map<UUID, Session></text><line style="stroke:#181818;stroke-width:0.5;" x1="301.33" x2="530.5585" y1="187.9783" y2="187.9783"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="155.9414" x="306.33" y="205.5134">+add(session: Session)</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="185.6572" x="306.33" y="222.0017">+getById(id: UUID): Session</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="126.1162" x="306.33" y="238.49">+remove(id: UUID)</text></g><!--class RawRequest--><g class="entity" data-qualified-name="RawRequest" data-source-line="35" id="ent0009"><rect fill="#F1F1F1" height="80.9766" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="151.5693" x="255.16" y="504.13"/><ellipse cx="287.2029" cy="520.13" fill="#ADD1B2" rx="11" ry="11" style="stroke:#181818;stroke-width:1;"/><path d="M289.676,526.2731 Q289.095,526.5719 288.4558,526.7213 Q287.8166,526.8707 287.1111,526.8707 Q284.6042,526.8707 283.2844,525.2189 Q281.9646,523.567 281.9646,520.4459 Q281.9646,517.3165 283.2844,515.6647 Q284.6042,514.0128 287.1111,514.0128 Q287.8166,514.0128 288.4641,514.1622 Q289.1116,514.3116 289.676,514.6105 L289.676,517.3331 Q289.0452,516.7521 288.4517,516.4823 Q287.8581,516.2125 287.2273,516.2125 Q285.8826,516.2125 285.1977,517.2792 Q284.5129,518.3458 284.5129,520.4459 Q284.5129,522.5377 285.1977,523.6044 Q285.8826,524.671 287.2273,524.671 Q287.8581,524.671 288.4517,524.4012 Q289.0452,524.1315 289.676,523.5504 Z " fill="#000000"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="81.6963" x="304.9902" y="525.421">RawRequest</text><line style="stroke:#181818;stroke-width:0.5;" x1="256.16" x2="405.7293" y1="536.13" y2="536.13"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="118.4531" x="261.16" y="553.6652">-session: Session</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="139.5693" x="261.16" y="570.1534">-rawMessage: String</text><line style="stroke:#181818;stroke-width:0.5;" x1="256.16" x2="405.7293" y1="577.1066" y2="577.1066"/></g><!--class Response--><g class="entity" data-qualified-name="Response" data-source-line="36" id="ent0011"><rect fill="#F1F1F1" height="64.4883" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="129.9336" x="484.98" y="512.37"/><ellipse cx="514.8788" cy="528.37" fill="#B4A7E5" rx="11" ry="11" style="stroke:#181818;stroke-width:1;"/><path d="M511.3065,524.6351 L511.3065,522.4769 L518.6859,522.4769 L518.6859,524.6351 L516.2206,524.6351 L516.2206,532.7118 L518.6859,532.7118 L518.6859,534.87 L511.3065,534.87 L511.3065,532.7118 L513.7718,532.7118 L513.7718,524.6351 Z " fill="#000000"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacing" textLength="64.8252" x="532.1896" y="533.661">Response</text><line style="stroke:#181818;stroke-width:0.5;" x1="485.98" x2="613.9136" y1="544.37" y2="544.37"/><line style="stroke:#181818;stroke-width:0.5;" x1="485.98" x2="613.9136" y1="552.37" y2="552.37"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="117.9336" x="490.98" y="569.9052">+encode(): String</text></g><!--class Tokenizer--><g class="entity" data-qualified-name="Tokenizer" data-source-line="45" id="ent0014"><rect fill="#F1F1F1" height="64.4883" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="269.8857" x="7" y="786.08"/><ellipse cx="103.8311" cy="802.08" fill="#ADD1B2" rx="11" ry="11" style="stroke:#181818;stroke-width:1;"/><path d="M106.3042,808.2231 Q105.7231,808.5219 105.084,808.6713 Q104.4448,808.8207 103.7393,808.8207 Q101.2324,808.8207 99.9126,807.1689 Q98.5928,805.517 98.5928,802.3959 Q98.5928,799.2665 99.9126,797.6147 Q101.2324,795.9628 103.7393,795.9628 Q104.4448,795.9628 105.0923,796.1122 Q105.7397,796.2616 106.3042,796.5605 L106.3042,799.2831 Q105.6733,798.7021 105.0798,798.4323 Q104.4863,798.1625 103.8555,798.1625 Q102.5107,798.1625 101.8259,799.2292 Q101.1411,800.2958 101.1411,802.3959 Q101.1411,804.4877 101.8259,805.5544 Q102.5107,806.621 103.8555,806.621 Q104.4863,806.621 105.0798,806.3512 Q105.6733,806.0815 106.3042,805.5004 Z " fill="#000000"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="67.7236" x="124.3311" y="807.371">Tokenizer</text><line style="stroke:#181818;stroke-width:0.5;" x1="8" x2="275.8857" y1="818.08" y2="818.08"/><line style="stroke:#181818;stroke-width:0.5;" x1="8" x2="275.8857" y1="826.08" y2="826.08"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="257.8857" x="13" y="843.6152">+tokenize(input: String): List<String></text></g><!--class ProtocolParser--><g class="entity" data-qualified-name="ProtocolParser" data-source-line="49" id="ent0015"><rect fill="#F1F1F1" height="64.4883" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="306.3896" x="133.75" y="653.35"/><ellipse cx="233.9033" cy="669.35" fill="#ADD1B2" rx="11" ry="11" style="stroke:#181818;stroke-width:1;"/><path d="M236.3765,675.4931 Q235.7954,675.7919 235.1563,675.9413 Q234.5171,676.0907 233.8115,676.0907 Q231.3047,676.0907 229.9849,674.4389 Q228.665,672.787 228.665,669.6659 Q228.665,666.5365 229.9849,664.8847 Q231.3047,663.2328 233.8115,663.2328 Q234.5171,663.2328 235.1646,663.3822 Q235.812,663.5316 236.3765,663.8305 L236.3765,666.5531 Q235.7456,665.9721 235.1521,665.7023 Q234.5586,665.4325 233.9277,665.4325 Q232.583,665.4325 231.8982,666.4992 Q231.2134,667.5658 231.2134,669.6659 Q231.2134,671.7577 231.8982,672.8244 Q232.583,673.891 233.9277,673.891 Q234.5586,673.891 235.1521,673.6212 Q235.7456,673.3515 236.3765,672.7704 Z " fill="#000000"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="97.583" x="254.4033" y="674.641">ProtocolParser</text><line style="stroke:#181818;stroke-width:0.5;" x1="134.75" x2="439.1396" y1="685.35" y2="685.35"/><line style="stroke:#181818;stroke-width:0.5;" x1="134.75" x2="439.1396" y1="693.35" y2="693.35"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="294.3896" x="139.75" y="710.8852">+parse(raw: RawRequest): PrimitiveRequest</text></g><!--class PrimitiveRequest--><g class="entity" data-qualified-name="PrimitiveRequest" data-source-line="53" id="ent0016"><rect fill="#F1F1F1" height="80.9766" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="241.4277" x="312.23" y="777.83"/><ellipse cx="372.0513" cy="793.83" fill="#ADD1B2" rx="11" ry="11" style="stroke:#181818;stroke-width:1;"/><path d="M374.5244,799.9731 Q373.9434,800.2719 373.3042,800.4213 Q372.6651,800.5707 371.9595,800.5707 Q369.4527,800.5707 368.1328,798.9189 Q366.813,797.267 366.813,794.1459 Q366.813,791.0165 368.1328,789.3647 Q369.4527,787.7128 371.9595,787.7128 Q372.6651,787.7128 373.3125,787.8622 Q373.96,788.0116 374.5244,788.3105 L374.5244,791.0331 Q373.8936,790.4521 373.3001,790.1823 Q372.7066,789.9125 372.0757,789.9125 Q370.731,789.9125 370.0462,790.9792 Q369.3613,792.0458 369.3613,794.1459 Q369.3613,796.2377 370.0462,797.3044 Q370.731,798.371 372.0757,798.371 Q372.7066,798.371 373.3001,798.1012 Q373.8936,797.8315 374.5244,797.2504 Z " fill="#000000"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="113.2852" x="392.5513" y="799.121">PrimitiveRequest</text><line style="stroke:#181818;stroke-width:0.5;" x1="313.23" x2="552.6577" y1="809.83" y2="809.83"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="124.0723" x="318.23" y="827.3652">-command: String</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="229.4277" x="318.23" y="843.8534">-arguments: Map<String, String></text><line style="stroke:#181818;stroke-width:0.5;" x1="313.23" x2="552.6577" y1="850.8066" y2="850.8066"/></g><!--class CommandParser--><g class="entity" data-qualified-name="CommandParser" data-source-line="63" id="ent0020"><rect fill="#F1F1F1" height="73.1094" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="314.8047" x="275.54" y="918.81"/><ellipse cx="372.9316" cy="939.1205" fill="#B4A7E5" rx="11" ry="11" style="stroke:#181818;stroke-width:1;"/><path d="M369.3593,935.3857 L369.3593,933.2275 L376.7387,933.2275 L376.7387,935.3857 L374.2734,935.3857 L374.2734,943.4623 L376.7387,943.4623 L376.7387,945.6205 L369.3593,945.6205 L369.3593,943.4623 L371.8247,943.4623 L371.8247,935.3857 Z " fill="#000000"/><text fill="#000000" font-family="sans-serif" font-size="12" font-style="italic" lengthAdjust="spacing" textLength="93.0703" x="402.6572" y="935.4116">«per command»</text><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacing" textLength="111.5215" x="393.4316" y="951.478">CommandParser</text><line style="stroke:#181818;stroke-width:0.5;" x1="276.54" x2="589.3447" y1="959.4311" y2="959.4311"/><line style="stroke:#181818;stroke-width:0.5;" x1="276.54" x2="589.3447" y1="967.4311" y2="967.4311"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="302.8047" x="281.54" y="984.9663">+parse(primitive: PrimitiveRequest): Request</text></g><!--class Request--><g class="entity" data-qualified-name="Request" data-source-line="67" id="ent0021"><rect fill="#F1F1F1" height="73.1094" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="130.4531" x="411.72" y="1051.92"/><ellipse cx="430.9423" cy="1072.2305" fill="#B4A7E5" rx="11" ry="11" style="stroke:#181818;stroke-width:1;"/><path d="M427.37,1068.4957 L427.37,1066.3375 L434.7494,1066.3375 L434.7494,1068.4957 L432.2841,1068.4957 L432.2841,1076.5723 L434.7494,1076.5723 L434.7494,1078.7305 L427.37,1078.7305 L427.37,1076.5723 L429.8353,1076.5723 L429.8353,1068.4957 Z " fill="#000000"/><text fill="#000000" font-family="sans-serif" font-size="12" font-style="italic" lengthAdjust="spacing" textLength="93.0703" x="443.8805" y="1068.5216">«per command»</text><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacing" textLength="54.3252" x="463.2531" y="1084.588">Request</text><line style="stroke:#181818;stroke-width:0.5;" x1="412.72" x2="541.1731" y1="1092.5411" y2="1092.5411"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="118.4531" x="417.72" y="1110.0763">-session: Session</text><line style="stroke:#181818;stroke-width:0.5;" x1="412.72" x2="541.1731" y1="1117.0294" y2="1117.0294"/></g><!--class CommandRouter--><g class="entity" data-qualified-name="CommandRouter" data-source-line="71" id="ent0022"><rect fill="#F1F1F1" height="64.4883" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="182.6729" x="625.61" y="923.12"/><ellipse cx="656.8592" cy="939.12" fill="#ADD1B2" rx="11" ry="11" style="stroke:#181818;stroke-width:1;"/><path d="M659.3324,945.2631 Q658.7513,945.5619 658.1121,945.7113 Q657.473,945.8607 656.7674,945.8607 Q654.2606,945.8607 652.9408,944.2089 Q651.6209,942.557 651.6209,939.4359 Q651.6209,936.3065 652.9408,934.6547 Q654.2606,933.0028 656.7674,933.0028 Q657.473,933.0028 658.1204,933.1522 Q658.7679,933.3016 659.3324,933.6005 L659.3324,936.3231 Q658.7015,935.7421 658.108,935.4723 Q657.5145,935.2025 656.8836,935.2025 Q655.5389,935.2025 654.8541,936.2692 Q654.1693,937.3358 654.1693,939.4359 Q654.1693,941.5277 654.8541,942.5944 Q655.5389,943.661 656.8836,943.661 Q657.5145,943.661 658.108,943.3912 Q658.7015,943.1215 659.3324,942.5404 Z " fill="#000000"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="114.5635" x="674.4702" y="944.411">CommandRouter</text><line style="stroke:#181818;stroke-width:0.5;" x1="626.61" x2="807.2829" y1="955.12" y2="955.12"/><line style="stroke:#181818;stroke-width:0.5;" x1="626.61" x2="807.2829" y1="963.12" y2="963.12"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="170.6729" x="631.61" y="980.6552">+route(request: Request)</text></g><!--class CommandHandler--><g class="entity" data-qualified-name="CommandHandler" data-source-line="75" id="ent0023"><rect fill="#F1F1F1" height="73.1094" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="192.3867" x="620.75" y="1051.92"/><ellipse cx="652.6852" cy="1072.2305" fill="#B4A7E5" rx="11" ry="11" style="stroke:#181818;stroke-width:1;"/><path d="M649.1129,1068.4957 L649.1129,1066.3375 L656.4923,1066.3375 L656.4923,1068.4957 L654.027,1068.4957 L654.027,1076.5723 L656.4923,1076.5723 L656.4923,1078.7305 L649.1129,1078.7305 L649.1129,1076.5723 L651.5783,1076.5723 L651.5783,1068.4957 Z " fill="#000000"/><text fill="#000000" font-family="sans-serif" font-size="12" font-style="italic" lengthAdjust="spacing" textLength="93.0703" x="685.2899" y="1068.5216">«per command»</text><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacing" textLength="122.7529" x="670.4486" y="1084.588">CommandHandler</text><line style="stroke:#181818;stroke-width:0.5;" x1="621.75" x2="812.1367" y1="1092.5411" y2="1092.5411"/><line style="stroke:#181818;stroke-width:0.5;" x1="621.75" x2="812.1367" y1="1100.5411" y2="1100.5411"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="180.3867" x="626.75" y="1118.0763">+handle(request: Request)</text></g><!--class SuccessResponse--><g class="entity" data-qualified-name="SuccessResponse" data-source-line="89" id="ent0028"><rect fill="#F1F1F1" height="48" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="149.4688" x="475.21" y="661.59"/><ellipse cx="490.21" cy="677.59" fill="#B4A7E5" rx="11" ry="11" style="stroke:#181818;stroke-width:1;"/><path d="M486.6377,673.8551 L486.6377,671.6969 L494.0171,671.6969 L494.0171,673.8551 L491.5518,673.8551 L491.5518,681.9318 L494.0171,681.9318 L494.0171,684.09 L486.6377,684.09 L486.6377,681.9318 L489.1031,681.9318 L489.1031,673.8551 Z " fill="#000000"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacing" textLength="117.4688" x="504.21" y="682.881">SuccessResponse</text><line style="stroke:#181818;stroke-width:0.5;" x1="476.21" x2="623.6788" y1="693.59" y2="693.59"/><line style="stroke:#181818;stroke-width:0.5;" x1="476.21" x2="623.6788" y1="701.59" y2="701.59"/></g><!--class ErrorResponse--><g class="entity" data-qualified-name="ErrorResponse" data-source-line="91" id="ent0029"><rect fill="#F1F1F1" height="48" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="130.1982" x="659.84" y="661.59"/><ellipse cx="674.84" cy="677.59" fill="#B4A7E5" rx="11" ry="11" style="stroke:#181818;stroke-width:1;"/><path d="M671.2677,673.8551 L671.2677,671.6969 L678.6471,671.6969 L678.6471,673.8551 L676.1818,673.8551 L676.1818,681.9318 L678.6471,681.9318 L678.6471,684.09 L671.2677,684.09 L671.2677,681.9318 L673.7331,681.9318 L673.7331,673.8551 Z " fill="#000000"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacing" textLength="98.1982" x="688.84" y="682.881">ErrorResponse</text><line style="stroke:#181818;stroke-width:0.5;" x1="660.84" x2="789.0382" y1="693.59" y2="693.59"/><line style="stroke:#181818;stroke-width:0.5;" x1="660.84" x2="789.0382" y1="701.59" y2="701.59"/></g><!--link NetworkManager to Session--><g class="link" data-entity-1="ent0002" data-entity-2="ent0003" data-link-type="dependency" data-source-line="32" id="lnk6"><path codeLine="32" d="M278.85,71.93 C278.85,125.9 278.85,228.67 278.85,299" fill="none" id="NetworkManager-to-Session" style="stroke:#181818;stroke-width:1;"/><polygon fill="#181818" points="278.85,305,282.85,296,278.85,300,274.85,296,278.85,305" style="stroke:#181818;stroke-width:1;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="45.1318" x="232.85" y="185.0384">creates</text></g><!--link NetworkManager to SessionManager--><g class="link" data-entity-1="ent0002" data-entity-2="ent0005" data-link-type="dependency" data-source-line="33" id="lnk7"><path codeLine="33" d="M352.42,71.62 C352.42,89.02 352.42,104.99 352.42,125.17" fill="none" id="NetworkManager-to-SessionManager" style="stroke:#181818;stroke-width:1;"/><polygon fill="#181818" points="352.42,131.17,356.42,122.17,352.42,126.17,348.42,122.17,352.42,131.17" style="stroke:#181818;stroke-width:1;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="98.1792" x="253.42" y="97.9684">adds session to</text></g><!--link Session to SessionId--><g class="link" data-entity-1="ent0003" data-entity-2="ent0004" data-link-type="dependency" data-source-line="34" id="lnk8"><path codeLine="34" d="M249.25,436.1 C249.25,468.19 249.25,500.03 249.25,500.03 C249.25,500.03 243.02,500.03 226.5,500.03" fill="none" id="Session-to-SessionId" style="stroke:#181818;stroke-width:1;"/><polygon fill="#181818" points="220.5,500.03,229.5,504.03,225.5,500.03,229.5,496.03,220.5,500.03" style="stroke:#181818;stroke-width:1;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="78.9204" x="170.25" y="479.0084">identified by</text></g><!--link Session to RawRequest--><g class="link" data-entity-1="ent0003" data-entity-2="ent0009" data-link-type="dependency" data-source-line="35" id="lnk10"><path codeLine="35" d="M330.94,435.93 C330.94,458.68 330.94,477.55 330.94,497.75" fill="none" id="Session-to-RawRequest" style="stroke:#181818;stroke-width:1;"/><polygon fill="#181818" points="330.94,503.75,334.94,494.75,330.94,498.75,326.94,494.75,330.94,503.75" style="stroke:#181818;stroke-width:1;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="45.1318" x="284.94" y="466.4084">creates</text></g><!--link Session to Response--><g class="link" data-entity-1="ent0003" data-entity-2="ent0011" data-link-type="dependency" data-source-line="36" id="lnk12"><path codeLine="36" d="M412.64,436.09 C412.64,485.56 412.64,545.03 412.64,545.03 C412.64,545.03 443.14,545.03 478.51,545.03" fill="none" id="Session-to-Response" style="stroke:#181818;stroke-width:1;stroke-dasharray:7,7;"/><polygon fill="#181818" points="484.51,545.03,475.51,541.03,479.51,545.03,475.51,549.03,484.51,545.03" style="stroke:#181818;stroke-width:1;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="36.7466" x="413.64" y="523.0584">sends</text></g><!--reverse link SessionManager to Session--><g class="link" data-entity-1="ent0005" data-entity-2="ent0003" data-link-type="aggregation" data-source-line="37" id="lnk13"><path codeLine="37" d="M359.44,257.61 C359.44,276.5 359.44,285.66 359.44,305.13" fill="none" id="SessionManager-backto-Session" style="stroke:#181818;stroke-width:1;"/><polygon fill="none" points="359.44,245.61,355.44,251.61,359.44,257.61,363.44,251.61,359.44,245.61" style="stroke:#181818;stroke-width:1;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="56.5386" x="302.44" y="271.9384">manages</text></g><!--link RawRequest to ProtocolParser--><g class="link" data-entity-1="ent0009" data-entity-2="ent0015" data-link-type="dependency" data-source-line="58" id="lnk17"><path codeLine="58" d="M330.94,585.39 C330.94,606.86 330.94,626.83 330.94,646.95" fill="none" id="RawRequest-to-ProtocolParser" style="stroke:#181818;stroke-width:1;"/><polygon fill="#181818" points="330.94,652.95,334.94,643.95,330.94,647.95,326.94,643.95,330.94,652.95" style="stroke:#181818;stroke-width:1;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="61.001" x="268.94" y="615.7384">passed to</text></g><!--link ProtocolParser to Tokenizer--><g class="link" data-entity-1="ent0015" data-entity-2="ent0014" data-link-type="dependency" data-source-line="59" id="lnk18"><path codeLine="59" d="M205.32,718.06 C205.32,738.63 205.32,759.14 205.32,779.73" fill="none" id="ProtocolParser-to-Tokenizer" style="stroke:#181818;stroke-width:1;"/><polygon fill="#181818" points="205.32,785.73,209.32,776.73,205.32,780.73,201.32,776.73,205.32,785.73" style="stroke:#181818;stroke-width:1;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="28.5645" x="176.32" y="748.4584">uses</text></g><!--link ProtocolParser to PrimitiveRequest--><g class="link" data-entity-1="ent0015" data-entity-2="ent0016" data-link-type="dependency" data-source-line="60" id="lnk19"><path codeLine="60" d="M376.18,718.06 C376.18,736.02 376.18,752.51 376.18,771.63" fill="none" id="ProtocolParser-to-PrimitiveRequest" style="stroke:#181818;stroke-width:1;"/><polygon fill="#181818" points="376.18,777.63,380.18,768.63,376.18,772.63,372.18,768.63,376.18,777.63" style="stroke:#181818;stroke-width:1;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="58.2651" x="317.18" y="744.4084">produces</text></g><!--link PrimitiveRequest to CommandParser--><g class="link" data-entity-1="ent0016" data-entity-2="ent0020" data-link-type="dependency" data-source-line="79" id="lnk24"><path codeLine="79" d="M432.94,859.07 C432.94,878.01 432.94,894.35 432.94,912.74" fill="none" id="PrimitiveRequest-to-CommandParser" style="stroke:#181818;stroke-width:1;"/><polygon fill="#181818" points="432.94,918.74,436.94,909.74,432.94,913.74,428.94,909.74,432.94,918.74" style="stroke:#181818;stroke-width:1;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="61.001" x="370.94" y="885.4784">passed to</text></g><!--link CommandParser to Request--><g class="link" data-entity-1="ent0020" data-entity-2="ent0021" data-link-type="dependency" data-source-line="80" id="lnk25"><path codeLine="80" d="M476.94,992.09 C476.94,1010.71 476.94,1027.26 476.94,1045.87" fill="none" id="CommandParser-to-Request" style="stroke:#181818;stroke-width:1;"/><polygon fill="#181818" points="476.94,1051.87,480.94,1042.87,476.94,1046.87,472.94,1042.87,476.94,1051.87" style="stroke:#181818;stroke-width:1;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="58.2651" x="417.94" y="1018.5484">produces</text></g><!--link CommandRouter to CommandHandler--><g class="link" data-entity-1="ent0022" data-entity-2="ent0023" data-link-type="dependency" data-source-line="81" id="lnk26"><path codeLine="81" d="M716.94,987.93 C716.94,1007.17 716.94,1025.59 716.94,1045.55" fill="none" id="CommandRouter-to-CommandHandler" style="stroke:#181818;stroke-width:1;"/><polygon fill="#181818" points="716.94,1051.55,720.94,1042.55,716.94,1046.55,712.94,1042.55,716.94,1051.55" style="stroke:#181818;stroke-width:1;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="84.3477" x="631.94" y="1016.3084">dispatches to</text></g><!--link CommandRouter to Request--><g class="link" data-entity-1="ent0022" data-entity-2="ent0021" data-link-type="dependency" data-source-line="82" id="lnk27"><path codeLine="82" d="M625.21,955.03 C613.52,955.03 605.55,955.03 605.55,955.03 C605.55,955.03 605.55,1088.03 605.55,1088.03 C605.55,1088.03 580.15,1088.03 548.38,1088.03" fill="none" id="CommandRouter-to-Request" style="stroke:#181818;stroke-width:1;stroke-dasharray:7,7;"/><polygon fill="#181818" points="542.38,1088.03,551.38,1092.03,547.38,1088.03,551.38,1084.03,542.38,1088.03" style="stroke:#181818;stroke-width:1;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="50.8193" x="554.55" y="1039.8584">receives</text></g><!--reverse link Response to SuccessResponse--><g class="link" data-entity-1="ent0011" data-entity-2="ent0028" data-link-type="extension" data-source-line="93" id="lnk30"><path codeLine="93" d="M549.94,595.31 C549.94,621.01 549.94,638.24 549.94,661.44" fill="none" id="Response-backto-SuccessResponse" style="stroke:#181818;stroke-width:1;"/><polygon fill="none" points="549.94,577.31,543.94,595.31,555.94,595.31,549.94,577.31" style="stroke:#181818;stroke-width:1;"/></g><!--reverse link Response to ErrorResponse--><g class="link" data-entity-1="ent0011" data-entity-2="ent0029" data-link-type="extension" data-source-line="94" id="lnk31"><path codeLine="94" d="M633.11,545.03 C682.86,545.03 724.94,545.03 724.94,545.03 C724.94,545.03 724.94,620.84 724.94,661.11" fill="none" id="Response-backto-ErrorResponse" style="stroke:#181818;stroke-width:1;"/><polygon fill="none" points="615.11,545.03,633.11,551.03,633.11,539.03,615.11,545.03" style="stroke:#181818;stroke-width:1;"/></g><?plantuml-src VLNRRXiX47tVh-2jQpKRzNbbMMhJIhMKL57SV01P9Wxo6hO3cyYz_7i75bXOkxLaIp1dpfmv0zWSf1euyYFhUcxCHsjHFeqMjfrME_aJs8VA9CZ0kmFVmywUUc08dULgtqC6wAK2Ung0QRGlEWiyKUOUzQZ4ZUurCejS6Gg1ibNrZdq3-wRnm6xv2R3oIkBU7LTK4jclYh7tp5YEjXw8lcLIsLNrfmhm7HWZjVAuXadHnfsjy9bXLHUHbG_jmMv5lLfcuAZg2M10YHh135eP81XvfFRdjRSYL7_b_KYunyVjvxosAx9OPyXplhprXaJuJTEoEpwiNSPL16uyAnUY3g3KIchmqucgkTvzgHIV6eMZVeKvweGit6-QJNBOVLhM8N0BvZ_0kDKwPIRgPrPNaIa3atVfGwCLT33aimJ1dau5_86_FS2F4OpDu55B15vV4p2CY-MyRf095lNffbdqTlGHuqxcFMghEzqNHpCJKSmcjxLXoD_kQ4bCySZC4_wk3w3eUiLpRSEwfWipsdJ4MdOhZLrFgysS7bNTSpI9Ot2BcggscK2Yk4Tvb5Q-GjZAQSf8wARJHt909DLkZ-D-FD9Gml6R8bSiKvQPuWQra4Yk3bII1ahpNyGSV3R4GKSoz1BgJ32neXPZDmthPX8UPaNl0-0pxo3k1wBrUg3VqEHcKxWtn0Bdjf6JcO4pUPHoYVR2oKZc1r4FcbxRE49q2tffF6EgjoeBXiIlzEt_sSwBZrwcEdFHMLjwb4-hZFXBcD_0R0HbInbhrDeo8UdPjzqBc8mw94pNjg2cFnaWeNwwwKOlnvi2K-kWEYt0l-VfritWtTYHL1Dpyj0NH8rp8D6kVxiNOf5NHelKlm00?></g></svg> \ No newline at end of file From 7daae485da714390df8b88ddc0a0050273fd472c Mon Sep 17 00:00:00 2001 From: Jona Walpert <jona.walpert@stud.unibas.ch> Date: Wed, 11 Mar 2026 13:22:33 +0100 Subject: [PATCH 10/61] Chore: Added javafx modules to build.gradle file --- .vscode/settings.json | 3 +++ bin/main/images/backround.png | Bin 0 -> 4917 bytes bin/main/images/logo.png | Bin 0 -> 3121 bytes bin/main/images/logoinverted.png | Bin 0 -> 198316 bytes build.gradle | 2 +- 5 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 .vscode/settings.json create mode 100644 bin/main/images/backround.png create mode 100644 bin/main/images/logo.png create mode 100644 bin/main/images/logoinverted.png diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e0f15db --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "automatic" +} \ No newline at end of file diff --git a/bin/main/images/backround.png b/bin/main/images/backround.png new file mode 100644 index 0000000000000000000000000000000000000000..e5e1841521b60a35d641455cf7ad2731329e2198 GIT binary patch literal 4917 zcmeAS@N?(olHy`uVBq!ia0y~yU}<1rVCvvt1B!5HH}V20#^NA%Cx&(BWL^R}Ea{HE zjtmSN`?>!lvI6;>1s;*b3=DjSL74G){)!Z!hW5;mh>{3jAFJg2T)o7U{G?R9irfOA zY6b=y`-+0Z<m}WEg{0K<%sgB5_q+G2D`ckFs)w8U8u%7Cr)Fe&R28KLSNVk`S7j$# zrl{N7@p9QzSOHDQO)W`OsL0L9E4HezRRSAq1!RMS^_A>wic-?7f?V97Du6s&rHqo2 z0xNy}^73-Ma$~*xqI7*jOG`_A10#JSBVC{h-Qvo;lEez#ykcdj0WPV<$wiq3C7Jno z3Lpa$lk!VTY?Vq&GgGY664OkRQc_HHlM;<nbPWwt5_J<(j16^DjEz&wO${xK(hQT7 zpw^dUrXlRlD~1LGFhKM{GWv#k1|TPc*jD~UnW;cmfULALv;oVbh}r0a9D?L9JBSF- zuRs<&WI(|L58c!}V33vr!%k<)3~mMnL32+R$B>G+w>Jz04=6AkI1nV;^P{|t?Z(6j zzvYz`lh2phfnsOVKXVXuA&G$zNF9-M0Pzah1c0Q&97Yx(C6GQUH5wYDX=5}yj1~@~ z<>6@UFj^swHV;NyM58UD(H7BYi)ge(G}<B>Z4nKt7LmByA9kD2J#|vRcxCW(^>bP0 Hl+XkK_IHxK literal 0 HcmV?d00001 diff --git a/bin/main/images/logo.png b/bin/main/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e906bd477ab5f700d7b1d62575ac4cfb9e9bf110 GIT binary patch literal 3121 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 a$AP*V*dH?OiofV(K0rF;O(_3~a`nI3SIF4_ literal 0 HcmV?d00001 diff --git a/bin/main/images/logoinverted.png b/bin/main/images/logoinverted.png new file mode 100644 index 0000000000000000000000000000000000000000..73053b6241f54d33d8d5422e05dd44236443b79d GIT binary patch literal 198316 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 literal 0 HcmV?d00001 diff --git a/build.gradle b/build.gradle index ca3f72c..99a402a 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ repositories { javafx { version = "25.0.2" - modules = ['javafx.controls', 'javafx.fxml', 'javafx.base'] + modules = ['javafx.controls', 'javafx.fxml', 'javafx.base', 'javafx.graphics', 'javafx.web'] } dependencies { From e5e93c1a65ed62c17a149bc8fcdb028bcbb3c85e Mon Sep 17 00:00:00 2001 From: Jona Walpert <98217482+Jona-Walpert@users.noreply.github.com> Date: Wed, 11 Mar 2026 14:20:18 +0100 Subject: [PATCH 11/61] Add: Added "bin" to gitignroe --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index aa33988..c29286e 100644 --- a/.gitignore +++ b/.gitignore @@ -118,4 +118,7 @@ $RECYCLE.BIN/ *.drawio.pdf ## IntelliJ -.idea \ No newline at end of file +.idea + +## bin +bin/ \ No newline at end of file From 2b012aa256cf9e0ca3077726a7713b2c31eb4988 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Wed, 11 Mar 2026 17:40:46 +0100 Subject: [PATCH 12/61] Add: log4j as gradle dependency --- build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.gradle b/build.gradle index 99a402a..0a481cf 100644 --- a/build.gradle +++ b/build.gradle @@ -27,6 +27,10 @@ javafx { } 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") + testImplementation("org.junit.jupiter:junit-jupiter:5.10.0") testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.10.0") } From 85c872df239b39329c8ff76f43b6e29bec237df9 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Wed, 11 Mar 2026 17:41:14 +0100 Subject: [PATCH 13/61] Add: Konfigure log4j with the bare minimum --- src/main/resources/log4j2.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/main/resources/log4j2.xml diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml new file mode 100644 index 0000000..1c7e406 --- /dev/null +++ b/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration status="warn"> + + <Appenders> + <Console name="Console" target="SYSTEM_OUT"> + <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> + </Console> + </Appenders> + + <Loggers> + <Root level="info"> + <AppenderRef ref="Console"/> + </Root> + </Loggers> + +</Configuration> \ No newline at end of file From a2256b2c1e3bd6747b75cfb85c5b84410d1e35d3 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Wed, 11 Mar 2026 17:44:19 +0100 Subject: [PATCH 14/61] Add: Jansi as gradle dependency to allow colored console output --- build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index 0a481cf..8196bdb 100644 --- a/build.gradle +++ b/build.gradle @@ -31,6 +31,9 @@ dependencies { implementation("org.apache.logging.log4j:log4j-api:2.25.3") runtimeOnly("org.apache.logging.log4j:log4j-core:2.25.3") + // Source: https://mvnrepository.com/artifact/org.fusesource.jansi/jansi + runtimeOnly("org.fusesource.jansi:jansi:2.4.2") + testImplementation("org.junit.jupiter:junit-jupiter:5.10.0") testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.10.0") } From 18331a7cc3e5153ad459ce16c08f4c20f445487a Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Wed, 11 Mar 2026 17:47:36 +0100 Subject: [PATCH 15/61] Add: Colored log output, based on log level --- src/main/resources/log4j2.xml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 1c7e406..01d74ca 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -3,12 +3,21 @@ <Appenders> <Console name="Console" target="SYSTEM_OUT"> - <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> + <PatternLayout disableAnsi="false" pattern= + "%style{%d{HH:mm:ss.SSS}}{bright,black} %highlight{%-8level}{ + FATAL=bright red, + ERROR=red, + WARN=yellow, + INFO=blue, + DEBUG=cyan, + TRACE=white + } %style{%-20logger{20}}{magenta} %msg%n" + /> </Console> </Appenders> <Loggers> - <Root level="info"> + <Root level="debug"> <AppenderRef ref="Console"/> </Root> </Loggers> From dac05de331631b3708fc6081a308c8b1bd77562b Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Wed, 11 Mar 2026 18:23:26 +0100 Subject: [PATCH 16/61] Add: Primitive first version of NetworkManager --- .../dbis/cs108/casono/server/ServerApp.java | 5 +++ .../casono/server/network/NetworkManager.java | 44 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java 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 520dc6b..e7dd3f3 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 @@ -1,8 +1,13 @@ package ch.unibas.dmi.dbis.cs108.casono.server; +import ch.unibas.dmi.dbis.cs108.casono.server.network.NetworkManager; + public class ServerApp { public static void start(String arg) { int port = Integer.parseInt(arg); System.out.println("You've selected the server. It will accept connections at port " + port); + + NetworkManager networkManager = new NetworkManager(port); + networkManager.start(); } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java new file mode 100644 index 0000000..c23f20a --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java @@ -0,0 +1,44 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import ch.unibas.dmi.dbis.cs108.casono.common.network.Session; + +public class NetworkManager implements Runnable { + private Integer port; + private Logger logger; + private Thread thread; + private Boolean running; + + public NetworkManager(Integer port) { + this.port = port; + this.logger = LogManager.getLogger(NetworkManager.class); + this.thread = new Thread(this, "networkManager"); + this.running = true; + } + + public void start() { + logger.debug("Starting server at port " + port); + thread.start(); + } + + @Override + public void run() { + try (ServerSocket serverSocket = new ServerSocket(port)) { + while (running) { + Socket clientSocket = serverSocket.accept(); + + System.out.println("Accepted connection from " + clientSocket.getRemoteSocketAddress()); + clientSocket.close(); + } + + } catch (IOException e) { + logger.fatal(e); + } + } +} From 5ff4dcec53cb33b23d4468874efe7466d4d39fa3 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Wed, 11 Mar 2026 18:53:30 +0100 Subject: [PATCH 17/61] Move: Session and SessionId from common to server --- .../dbis/cs108/casono/{common => server}/network/Session.java | 4 ++-- .../cs108/casono/{common => server}/network/SessionId.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/main/java/ch/unibas/dmi/dbis/cs108/casono/{common => server}/network/Session.java (74%) rename src/main/java/ch/unibas/dmi/dbis/cs108/casono/{common => server}/network/SessionId.java (87%) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/common/network/Session.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/Session.java similarity index 74% rename from src/main/java/ch/unibas/dmi/dbis/cs108/casono/common/network/Session.java rename to src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/Session.java index f4ac359..d4231fb 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/common/network/Session.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/Session.java @@ -1,9 +1,9 @@ -package ch.unibas.dmi.dbis.cs108.casono.common.network; +package ch.unibas.dmi.dbis.cs108.casono.server.network; public interface Session { /** * Outlines an interface all session implementations have to conform to - */ + */ SessionId id(); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/common/network/SessionId.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/SessionId.java similarity index 87% rename from src/main/java/ch/unibas/dmi/dbis/cs108/casono/common/network/SessionId.java rename to src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/SessionId.java index fded15a..0af06ad 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/common/network/SessionId.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/SessionId.java @@ -1,4 +1,4 @@ -package ch.unibas.dmi.dbis.cs108.casono.common.network; +package ch.unibas.dmi.dbis.cs108.casono.server.network; import java.util.UUID; From 02dc75c331a8584dec3061635db14eac6a77ce5e Mon Sep 17 00:00:00 2001 From: Jona Walpert <98217482+Jona-Walpert@users.noreply.github.com> Date: Thu, 12 Mar 2026 11:31:41 +0100 Subject: [PATCH 18/61] Add: Javadoc to client side files for lauching (lobby) UI, Main file --- .../ch/unibas/dmi/dbis/cs108/casono/Main.java | 17 +++++++++++ .../dbis/cs108/casono/client/ClientApp.java | 23 ++++++++++++++ .../dbis/cs108/casono/client/ui/Launcher.java | 16 ++++++++++ .../client/ui/lobbyui/Casinomainui.java | 25 ++++++++++++++++ .../ui/lobbyui/CasinomainuiController.java | 30 +++++++++++++++++++ 5 files changed, 111 insertions(+) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/Main.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/Main.java index d1890db..f7d2273 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/Main.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/Main.java @@ -3,8 +3,25 @@ package ch.unibas.dmi.dbis.cs108.casono; import ch.unibas.dmi.dbis.cs108.casono.client.ClientApp; import ch.unibas.dmi.dbis.cs108.casono.server.ServerApp; +/** + * Main entry point for Casono application. + * Handles client and server startup. + * <p> + * Standardkonstruktor für die Anwendung. + */ public final class Main { + /** + * Standardkonstruktor. + */ + public Main() { + // Standardkonstruktor + } + + /** + * Main entry point for Casono. + * @param args Command line arguments + */ public static void main(String[] args) { if (!isValid(args)) { printUsage(); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ClientApp.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ClientApp.java index 8c491ea..76dc06d 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ClientApp.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ClientApp.java @@ -1,6 +1,29 @@ package ch.unibas.dmi.dbis.cs108.casono.client; + +/** + * Entry point for the Casono client application. + * Handles client startup and connection parameters. + */ import ch.unibas.dmi.dbis.cs108.casono.client.ui.Launcher; +/** + * Entry point for the Casono client application. + * Handles client startup and connection parameters. + * <p> + * Standardkonstruktor für die Anwendung. + */ public class ClientApp { + + /** + * Standardkonstruktor. + */ + public ClientApp() { + // Standardkonstruktor + } + /** + * Starts the client application with the given address. + * @param arg Address in the format "ip:port". + * @throws IllegalArgumentException if the address format is invalid. + */ public static void start(String arg) { String[] parts = arg.split(":", 2); if (parts.length != 2) { diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/Launcher.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/Launcher.java index 5096144..c687abf 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/Launcher.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/Launcher.java @@ -2,7 +2,23 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui; import javafx.application.Application; +/** + * Launcher for the Casono main UI. + * <p> + * Standardkonstruktor für die Anwendung. + */ public class Launcher { + + /** + * Standardkonstruktor. + */ + public Launcher() { + // Standardkonstruktor + } + /** + * Main entry point for launching the UI. + * @param args Command line arguments + */ public static void main(String[] args) { Application.launch(ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui.Casinomainui.class, args); } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/Casinomainui.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/Casinomainui.java index 20cc74a..169da1d 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/Casinomainui.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/Casinomainui.java @@ -1,5 +1,9 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui; +/** + * Main UI application for Casono. + * Loads the main FXML layout and sets up the stage. + */ import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; @@ -7,8 +11,25 @@ import javafx.stage.Stage; import java.io.IOException; +/** + * JavaFX Application class for the Casono main UI. + * <p> + * Standardkonstruktor für die Anwendung. + */ public class Casinomainui extends Application { + + /** + * Standardkonstruktor. + */ + public Casinomainui() { + // Standardkonstruktor + } @Override + /** + * Starts the JavaFX application and loads the main UI. + * @param stage The primary stage for this application. + * @throws IOException If loading the FXML fails. + */ public void start(Stage stage) throws IOException { FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/ui-structure/Casinomainui.fxml")); Scene scene = new Scene(fxmlLoader.load(), 1200, 800); @@ -20,6 +41,10 @@ public class Casinomainui extends Application { stage.show(); } + /** + * Main entry point for launching the application. + * @param args Command line arguments + */ public static void main(String[] args) { launch(); } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/CasinomainuiController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/CasinomainuiController.java index c34990f..b13b034 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/CasinomainuiController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/CasinomainuiController.java @@ -1,4 +1,9 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui; + +/** + * Controller for the Casono main UI lobby. + * Handles UI initialization and user actions. + */ import javafx.application.Platform; import javafx.fxml.FXML; import javafx.scene.control.Label; @@ -16,23 +21,45 @@ import javafx.scene.input.MouseEvent; import javafx.scene.layout.HBox; import javafx.scene.layout.AnchorPane; +/** + * Controller for the Casono main UI lobby. + * Handles UI initialization and user actions. + * <p> + * Standardkonstruktor für den Controller. + */ public class CasinomainuiController { + + /** + * Standardkonstruktor. + */ + public CasinomainuiController() { + // Standardkonstruktor + } + /** Root pane of the UI. */ @FXML private AnchorPane rootPane; + /** Title label for the main UI. */ @FXML private Label titleLabel; + /** Subtitle label for the main UI. */ @FXML private Label subtitleLabel; + /** Logo image view. */ @FXML private javafx.scene.image.ImageView logoView; + /** Green box shape element. */ @FXML private javafx.scene.shape.Rectangle greenBox; + /** Exit button for closing the application. */ @FXML private Button exitbutton; + /** + * Initializes the UI components and sets default values. + */ public void initialize() { titleLabel.setText("Casono"); subtitleLabel.setText("Texas Hold'em Poker"); @@ -40,6 +67,9 @@ public class CasinomainuiController { logoView.setImage(new javafx.scene.image.Image(getClass().getResource("/images/logo.png").toExternalForm())); } + /** + * Handles the exit button action to close the application. + */ @FXML public void handleexitbutton() { Platform.exit(); From 5407e245f732294235ce61c18fddb38be48fcd8b Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Thu, 12 Mar 2026 13:43:35 +0100 Subject: [PATCH 19/61] Add: Modify NetworkManager to create Session uppon recieving a new connection --- .../cs108/casono/server/network/NetworkManager.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java index c23f20a..ca579ba 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java @@ -7,8 +7,9 @@ import java.net.Socket; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import ch.unibas.dmi.dbis.cs108.casono.common.network.Session; - +/** + * Creates and manages the server socket. Accepts new incomming connections and creates sessions. + */ public class NetworkManager implements Runnable { private Integer port; private Logger logger; @@ -22,6 +23,8 @@ public class NetworkManager implements Runnable { this.running = true; } + /* Starts the internal thread to accept new connections. + */ public void start() { logger.debug("Starting server at port " + port); thread.start(); @@ -32,9 +35,11 @@ public class NetworkManager implements Runnable { try (ServerSocket serverSocket = new ServerSocket(port)) { while (running) { Socket clientSocket = serverSocket.accept(); - + System.out.println("Accepted connection from " + clientSocket.getRemoteSocketAddress()); - clientSocket.close(); + + Session session = new Session(clientSocket); + session.start(); } } catch (IOException e) { From f4a32b1649b956fe06b71a353b1bf3765f6384df Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Thu, 12 Mar 2026 13:45:30 +0100 Subject: [PATCH 20/61] Add: Session Runnable to read incomming data, decode and print it --- .../cs108/casono/server/network/Session.java | 66 +++++++++++++++++-- 1 file changed, 59 insertions(+), 7 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/Session.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/Session.java index d4231fb..855b9be 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/Session.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/Session.java @@ -1,13 +1,65 @@ package ch.unibas.dmi.dbis.cs108.casono.server.network; -public interface Session { - /** - * Outlines an interface all session implementations have to conform to - */ +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.net.Socket; +import java.nio.charset.StandardCharsets; - SessionId id(); +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; - void send(byte[] payload); +public class Session implements Runnable { + private SessionId id; + private Thread thread; + private Socket socket; + private DataInputStream in; + private DataOutputStream out; + private Logger logger; + + public Session(Socket socket) throws IOException { + this.id = new SessionId(); + this.thread = new Thread(this, "session-" + this.id.value()); + this.socket = socket; + this.in = new DataInputStream(socket.getInputStream()); + this.out = new DataOutputStream(socket.getOutputStream()); - void close(); + this.logger = LogManager.getLogger(Session.class.toString() + id.value()); + this.logger.info("Created new session"); + } + + public SessionId getId() { + return this.id; + } + + public void start() { + thread.start(); + } + + public String read() throws IOException { + int length = in.readInt(); + byte[] payload = new byte[length]; + in.readFully(payload); + return new String(payload, StandardCharsets.UTF_8); + } + + public void close() throws IOException { + socket.close(); + } + + @Override + public void run() { + while (true) { + try { + System.out.println("Recieved: " + read()); + } catch (EOFException e) { + logger.info("Client disconnected"); + break; + } catch (IOException e) { + e.printStackTrace(); + break; + } + } + } } \ No newline at end of file From d414c27ff23a460493d540a42290a6009f9f8262 Mon Sep 17 00:00:00 2001 From: Jona Walpert <98217482+Jona-Walpert@users.noreply.github.com> Date: Thu, 12 Mar 2026 15:23:40 +0100 Subject: [PATCH 21/61] Chore: Added Json dependency in Gradle --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index ca3f72c..32faa79 100644 --- a/build.gradle +++ b/build.gradle @@ -29,6 +29,7 @@ javafx { dependencies { testImplementation("org.junit.jupiter:junit-jupiter:5.10.0") testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.10.0") + implementation 'org.json:json:20240303' } test { From d2407d61f4a81f72e41d11ee2add26b77bde771b Mon Sep 17 00:00:00 2001 From: Jona Walpert <98217482+Jona-Walpert@users.noreply.github.com> Date: Thu, 12 Mar 2026 15:25:18 +0100 Subject: [PATCH 22/61] Add: Lobby display in lobby GUI with placeholder functions. --- .../ui/lobbyui/CasinomainuiController.java | 45 ++++++- .../ui/lobbyui/LobbyButtonGridManager.java | 101 +++++++++++++++ .../LobbyButtonTranslationManager.java | 116 ++++++++++++++++++ .../resources/ui-structure/Casinomainui.css | 19 ++- .../resources/ui-structure/Casinomainui.fxml | 12 ++ .../lobby_button_translation.json | 10 ++ 6 files changed, 292 insertions(+), 11 deletions(-) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonTranslationManager.java create mode 100644 src/main/resources/ui-structure/lobby_button_translation.json diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/CasinomainuiController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/CasinomainuiController.java index b13b034..8f50083 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/CasinomainuiController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/CasinomainuiController.java @@ -1,5 +1,10 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui; +/** + * Controller for the Casono main UI lobby. + * Handles UI initialization and user actions. + */ + /** * Controller for the Casono main UI lobby. * Handles UI initialization and user actions. @@ -30,11 +35,12 @@ import javafx.scene.layout.AnchorPane; public class CasinomainuiController { /** - * Standardkonstruktor. + * Default constructor for the controller. */ public CasinomainuiController() { - // Standardkonstruktor + // Default constructor } + /** Root pane of the UI. */ @FXML private AnchorPane rootPane; @@ -53,9 +59,13 @@ public class CasinomainuiController { /** Exit button for closing the application. */ @FXML private Button exitbutton; - - + /** Casino table VBox for grid rendering. */ + @FXML + private javafx.scene.layout.VBox casinoTable; + private LobbyButtonTranslationManager translationManager; + private LobbyButtonGridManager gridManager; + private int nextButtonId = 1; /** * Initializes the UI components and sets default values. @@ -63,8 +73,12 @@ public class CasinomainuiController { public void initialize() { titleLabel.setText("Casono"); subtitleLabel.setText("Texas Hold'em Poker"); - // Logo laden logoView.setImage(new javafx.scene.image.Image(getClass().getResource("/images/logo.png").toExternalForm())); + + translationManager = new LobbyButtonTranslationManager(); + gridManager = new LobbyButtonGridManager(new javafx.scene.layout.GridPane(), translationManager); + casinoTable.getChildren().add(gridManager.getGridPane()); + gridManager.renderLobbyButtons(); } /** @@ -75,5 +89,24 @@ public class CasinomainuiController { Platform.exit(); } -} + /** + * Handles creation of a new lobby button. + */ + @FXML + public void handleCreateLobbyButton() { + if (translationManager.isFull()) { + System.out.println("Grid voll! Keine weiteren Lobbys moeglich."); + return; + } + int buttonId = nextButtonId++; + int lobbyId = gridManager.createLobby(); + try { + translationManager.addLobbyButton(buttonId, lobbyId); + System.out.println("ButtonID: " + buttonId + ", LobbyID: " + lobbyId); + gridManager.renderLobbyButtons(); + } catch (Exception e) { + System.out.println("Fehler beim Hinzufügen: " + e.getMessage()); + } + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java new file mode 100644 index 0000000..6add82d --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java @@ -0,0 +1,101 @@ +package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui; + +/** + * Manages the grid for lobby buttons and rendering. + * Uses LobbyButtonTranslationManager for mapping ButtonID to LobbyID. + */ + +import javafx.scene.control.Button; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.GridPane; +import java.util.Map; + +/** + * Manages the grid for lobby buttons and rendering. + * Uses LobbyButtonTranslationManager for mapping ButtonID to LobbyID. + */ +public class LobbyButtonGridManager { + + /** GridPane for the button grid. */ + private final GridPane gridPane; + /** Manager for mapping ButtonID to LobbyID. */ + private final LobbyButtonTranslationManager translationManager; + /** Number of rows in the grid. */ + private final int rows = 2; + /** Number of columns in the grid. */ + private final int cols = 4; + /** Path to the button image. */ + private final String buttonImagePath = "/images/logo.png"; + + /** + * Constructor for the GridManager. + * + * @param gridPane the GridPane for rendering + * @param translationManager the manager for mapping ButtonID to LobbyID + */ + public LobbyButtonGridManager(GridPane gridPane, LobbyButtonTranslationManager translationManager) { + this.gridPane = gridPane; + this.translationManager = translationManager; + } + + /** + * Renders all lobby buttons in the grid. + * Creates a button for each mapping with image and event handler. + */ + public void renderLobbyButtons() { + gridPane.getChildren().clear(); + int index = 0; + Map<Integer, Integer> mapping = translationManager.getButtonIdToLobbyId(); + if (mapping.isEmpty()) { + // No buttons to render + return; + } + for (Map.Entry<Integer, Integer> entry : mapping.entrySet()) { + int buttonId = entry.getKey(); + Button btn = new Button(); + btn.setId("lobbyBtn-" + buttonId); + btn.setGraphic(new ImageView(new Image(getClass().getResourceAsStream(buttonImagePath)))); + btn.setOnAction(e -> { + Integer lobbyId = translationManager.getLobbyIdForButton(buttonId); + if (lobbyId != null) + joinLobby(lobbyId); + }); + int row = index / cols; + int col = index % cols; + gridPane.add(btn, col, row); + index++; + } + } + + /** + * Placeholder for lobby creation logic. Returns a generated lobbyId. + * + * @return The generated lobbyId + */ + public int createLobby() { + // TODO: Replace with actual lobby creation logic + int lobbyId = (int) (Math.random() * 10000 + 1); + System.out.println("Lobby created: " + lobbyId); + return lobbyId; + } + + /** + * Placeholder for joining a lobby. + * + * @param lobbyId The lobbyId to join + */ + public void joinLobby(int lobbyId) { + // TODO: Replace with actual join logic + System.out.println("Joining lobby: " + lobbyId); + } + + /** + * Getter for the GridPane. + * + * @return The GridPane for the button grid + */ + public javafx.scene.layout.GridPane getGridPane() { + return gridPane; + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonTranslationManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonTranslationManager.java new file mode 100644 index 0000000..60b0f94 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonTranslationManager.java @@ -0,0 +1,116 @@ +package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui; + +import java.util.HashMap; +import java.util.Map; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.json.JSONObject; + +/** + * Handles JSON translation between ButtonID and LobbyID. + * <p> + * Verwaltet das Mapping zwischen Button-IDs und Lobby-IDs, speichert und lädt die Zuordnung aus einer JSON-Datei. + */ +public class LobbyButtonTranslationManager { + /** + * Removes all lobbies from the mapping and saves the file. + */ + public void clearAllLobbies() { + buttonIdToLobbyId.clear(); + saveTranslation(); + } + /** Pfad zur JSON-Translationsdatei. */ + private final String translationFilePath = "src/main/resources/ui-structure/lobby_button_translation.json"; + /** Zuordnung ButtonID → LobbyID. */ + private final Map<Integer, Integer> buttonIdToLobbyId = new HashMap<>(); + /** Maximale Anzahl an Buttons im Grid. */ + private final int maxButtons = 8; + + /** + * Konstruktor: lädt die Zuordnung aus der JSON-Datei. + */ + public LobbyButtonTranslationManager() { + loadTranslation(); + } + + /** + * Lädt die Zuordnung ButtonID → LobbyID aus der JSON-Datei. + * Falls die Datei nicht existiert, bleibt die Zuordnung leer. + */ + public void loadTranslation() { + try { + Path path = Path.of(translationFilePath); + if (!Files.exists(path)) return; + String json = Files.readString(path); + JSONObject obj = new JSONObject(json); + buttonIdToLobbyId.clear(); + for (String key : obj.keySet()) { + buttonIdToLobbyId.put(Integer.parseInt(key), obj.getInt(key)); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Speichert die aktuelle Zuordnung ButtonID → LobbyID in die JSON-Datei. + */ + public void saveTranslation() { + try { + JSONObject obj = new JSONObject(); + for (Map.Entry<Integer, Integer> entry : buttonIdToLobbyId.entrySet()) { + obj.put(String.valueOf(entry.getKey()), entry.getValue()); + } + Files.write(Path.of(translationFilePath), obj.toString(2).getBytes()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Prüft, ob das Grid voll ist (maxButtons erreicht). + * @return true, wenn Grid voll; sonst false + */ + public boolean isFull() { + return buttonIdToLobbyId.size() >= maxButtons; + } + + /** + * Fügt eine Zuordnung ButtonID → LobbyID hinzu und speichert sie. + * @param buttonId Die ID des Buttons + * @param lobbyId Die ID der Lobby + * @throws Exception wenn das Grid voll ist + */ + public void addLobbyButton(int buttonId, int lobbyId) throws Exception { + if (isFull()) throw new Exception("Grid is full!"); + buttonIdToLobbyId.put(buttonId, lobbyId); + saveTranslation(); + } + + /** + * Entfernt eine Zuordnung für die gegebene ButtonID und speichert. + * @param buttonId Die ID des zu entfernenden Buttons + */ + public void removeLobbyButton(int buttonId) { + buttonIdToLobbyId.remove(buttonId); + saveTranslation(); + } + + /** + * Gibt die LobbyID für eine gegebene ButtonID zurück. + * @param buttonId Die ButtonID + * @return Die zugehoerige LobbyID oder null, falls nicht vorhanden + */ + public Integer getLobbyIdForButton(int buttonId) { + return buttonIdToLobbyId.get(buttonId); + } + + /** + * Gibt die gesamte Zuordnung ButtonID → LobbyID zurück. + * @return Map aller Zuordnungen + */ + public Map<Integer, Integer> getButtonIdToLobbyId() { + return buttonIdToLobbyId; + } +} diff --git a/src/main/resources/ui-structure/Casinomainui.css b/src/main/resources/ui-structure/Casinomainui.css index 129c457..8a9dc97 100644 --- a/src/main/resources/ui-structure/Casinomainui.css +++ b/src/main/resources/ui-structure/Casinomainui.css @@ -120,6 +120,7 @@ -fx-padding: 0 0 40 0; } +/* EXIT BUTTON */ .button-exit { -fx-background-radius: 12; -fx-border-radius: 12; @@ -128,17 +129,17 @@ -fx-border-width: 2; -fx-padding: 6 15; -fx-cursor: hand; + -fx-background-color: #333333; + -fx-border-color: #666666; + -fx-text-fill: #CCCCCC; } - .button-exit:hover { -fx-translate-y: -3; -fx-effect: dropshadow(one-pass-box, rgba(0, 0, 0, 0.8), 0, 0, 3, 3); } -.button-exit { - -fx-background-color: #333333; - -fx-border-color: #666666; - -fx-text-fill: #CCCCCC; +/* LOBBY ERSTELLEN BUTTON */ +.button-create-lobby { -fx-background-radius: 12; -fx-border-radius: 12; -fx-font-family: "Courier New"; @@ -146,6 +147,14 @@ -fx-border-width: 2; -fx-padding: 6 15; -fx-cursor: hand; + -fx-background-color: #1a7f2e; + -fx-border-color: #2ecc71; + -fx-text-fill: #ffffff; +} +.button-create-lobby:hover { + -fx-translate-y: -3; + -fx-effect: dropshadow(one-pass-box, rgba(0, 0, 0, 0.8), 0, 0, 3, 3); + -fx-background-color: #27ae60; } diff --git a/src/main/resources/ui-structure/Casinomainui.fxml b/src/main/resources/ui-structure/Casinomainui.fxml index 3b8bee7..2d18a48 100644 --- a/src/main/resources/ui-structure/Casinomainui.fxml +++ b/src/main/resources/ui-structure/Casinomainui.fxml @@ -68,6 +68,18 @@ <Insets top="20" left="20" /> </GridPane.margin> </Button> + <Button text="Lobby erstellen" + styleClass="button-create-lobby" + fx:id="createLobbyButton" + onAction="#handleCreateLobbyButton" + GridPane.rowIndex="0" + GridPane.columnIndex="0" + GridPane.halignment="LEFT" + GridPane.valignment="TOP"> + <GridPane.margin> + <Insets top="60" left="20" /> + </GridPane.margin> + </Button> <VBox fx:id="casinoTable" alignment="CENTER" diff --git a/src/main/resources/ui-structure/lobby_button_translation.json b/src/main/resources/ui-structure/lobby_button_translation.json new file mode 100644 index 0000000..aadb243 --- /dev/null +++ b/src/main/resources/ui-structure/lobby_button_translation.json @@ -0,0 +1,10 @@ +{ + "1": 1240, + "2": 3640, + "3": 7589, + "4": 3982, + "5": 5655, + "6": 6418, + "7": 8586, + "8": 7378 +} \ No newline at end of file From fa95ce1418c15ee330a67cb7248b79cfd79a192c Mon Sep 17 00:00:00 2001 From: Jona Walpert <98217482+Jona-Walpert@users.noreply.github.com> Date: Thu, 12 Mar 2026 15:36:13 +0100 Subject: [PATCH 23/61] Test: Unit test for creating lobbies and trying to join them --- .../lobbyui/LobbyButtonGridManagerTest.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManagerTest.java diff --git a/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManagerTest.java b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManagerTest.java new file mode 100644 index 0000000..4042ce0 --- /dev/null +++ b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManagerTest.java @@ -0,0 +1,33 @@ +package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui; + +import javafx.scene.layout.GridPane; +import org.junit.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.*; + +class LobbyButtonGridManagerTest { + LobbyButtonGridManager gridManager; + LobbyButtonTranslationManager translationManager; + GridPane gridPane; + + @BeforeEach + void setUp() { + gridPane = new GridPane(); + translationManager = new LobbyButtonTranslationManager(); + translationManager.getButtonIdToLobbyId().clear(); + translationManager.saveTranslation(); + gridManager = new LobbyButtonGridManager(gridPane, translationManager); + } + + @Test + void testCreateLobbyReturnsId() { + int lobbyId = gridManager.createLobby(); + assertTrue(lobbyId > 0); + } + + @Test + void testJoinLobbyPlaceholder() { + // check if exception is thrown + assertDoesNotThrow(() -> gridManager.joinLobby(123)); + } +} From 59765e33bb242a96830ba600d990e2ae865e575c Mon Sep 17 00:00:00 2001 From: Jona Walpert <98217482+Jona-Walpert@users.noreply.github.com> Date: Thu, 12 Mar 2026 15:36:59 +0100 Subject: [PATCH 24/61] Test: Unit test for Translating Button IDs into backend Lobby IDs --- .../LobbyButtonTranslationManagerTest.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonTranslationManagerTest.java diff --git a/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonTranslationManagerTest.java b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonTranslationManagerTest.java new file mode 100644 index 0000000..82a35ad --- /dev/null +++ b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonTranslationManagerTest.java @@ -0,0 +1,57 @@ +package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui; + +import org.junit.jupiter.api.*; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +class LobbyButtonTranslationManagerTest { + LobbyButtonTranslationManager manager; + + @BeforeEach + void setUp() { + manager = new LobbyButtonTranslationManager(); + manager.getButtonIdToLobbyId().clear(); + manager.saveTranslation(); + } + + @Test + void testAddLobbyButton() throws Exception { + manager.addLobbyButton(1, 100); + assertEquals(100, manager.getLobbyIdForButton(1)); + } + + @Test + void testRemoveLobbyButton() throws Exception { + manager.addLobbyButton(2, 200); + manager.removeLobbyButton(2); + assertNull(manager.getLobbyIdForButton(2)); + } + + @Test + void testIsFull() throws Exception { + for (int i = 1; i <= 8; i++) { + manager.addLobbyButton(i, 100 + i); + } + assertTrue(manager.isFull()); + Exception ex = assertThrows(Exception.class, () -> manager.addLobbyButton(9, 109)); + assertEquals("Grid is full!", ex.getMessage()); + } + + @Test + void testSaveAndLoadTranslation() throws Exception { + manager.addLobbyButton(3, 300); + manager.saveTranslation(); + manager.getButtonIdToLobbyId().clear(); + manager.loadTranslation(); + assertEquals(300, manager.getLobbyIdForButton(3)); + } + + @Test + void testGetButtonIdToLobbyId() throws Exception { + manager.addLobbyButton(4, 400); + Map<Integer, Integer> map = manager.getButtonIdToLobbyId(); + assertTrue(map.containsKey(4)); + assertEquals(400, map.get(4)); + } +} From 48d77b6cbe3bb0825dfc37f330595ae57d07a352 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Thu, 12 Mar 2026 15:41:23 +0100 Subject: [PATCH 25/61] Add: TransportLayer interface to abstract direct socket access --- .../dbis/cs108/casono/server/network/TransportLayer.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/TransportLayer.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/TransportLayer.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/TransportLayer.java new file mode 100644 index 0000000..30aac09 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/TransportLayer.java @@ -0,0 +1,9 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network; + +import java.io.IOException; + +public interface TransportLayer { + String read() throws IOException; + void write(String data) throws IOException; + void close() throws IOException; +} From b073760eefd3e1c25e616e3357994fdcd0b0aa6f Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Thu, 12 Mar 2026 15:51:35 +0100 Subject: [PATCH 26/61] Add: TcpTransport class to implement TransportLayer --- .../casono/server/network/TcpTransport.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/TcpTransport.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/TcpTransport.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/TcpTransport.java new file mode 100644 index 0000000..4cb9910 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/TcpTransport.java @@ -0,0 +1,37 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.Socket; +import java.nio.charset.StandardCharsets; + +public class TcpTransport implements TransportLayer { + private Socket socket; + private DataInputStream in; + private DataOutputStream out; + + public TcpTransport(Socket socket) throws IOException { + this.socket = socket; + this.in = new DataInputStream(socket.getInputStream()); + this.out = new DataOutputStream(socket.getOutputStream()); + } + + public String read() throws IOException { + int length = in.readInt(); + byte[] payload = new byte[length]; + in.readFully(payload); + return new String(payload, StandardCharsets.UTF_8); + } + + public void write(String payload) throws IOException { + byte[] rawPayload = payload.getBytes(StandardCharsets.UTF_8); + out.writeInt(rawPayload.length); + out.write(rawPayload); + out.flush(); + } + + public void close() throws IOException { + socket.close(); + } +} From d4bf69dc594011009560336f333fd70e1fa3a6c5 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Thu, 12 Mar 2026 15:54:06 +0100 Subject: [PATCH 27/61] Refactor: Session to use TransportLayer for socket communication --- .../casono/server/network/NetworkManager.java | 2 +- .../cs108/casono/server/network/Session.java | 31 ++++++------------- 2 files changed, 10 insertions(+), 23 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java index ca579ba..aed074a 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java @@ -38,7 +38,7 @@ public class NetworkManager implements Runnable { System.out.println("Accepted connection from " + clientSocket.getRemoteSocketAddress()); - Session session = new Session(clientSocket); + Session session = new Session(new TcpTransport(clientSocket)); session.start(); } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/Session.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/Session.java index 855b9be..24b30d2 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/Session.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/Session.java @@ -1,29 +1,22 @@ package ch.unibas.dmi.dbis.cs108.casono.server.network; -import java.io.DataInputStream; -import java.io.DataOutputStream; import java.io.EOFException; import java.io.IOException; -import java.net.Socket; -import java.nio.charset.StandardCharsets; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class Session implements Runnable { private SessionId id; private Thread thread; - private Socket socket; - private DataInputStream in; - private DataOutputStream out; + private TransportLayer transport; private Logger logger; + private Boolean running; - public Session(Socket socket) throws IOException { + public Session(TransportLayer transport) throws IOException { this.id = new SessionId(); this.thread = new Thread(this, "session-" + this.id.value()); - this.socket = socket; - this.in = new DataInputStream(socket.getInputStream()); - this.out = new DataOutputStream(socket.getOutputStream()); + this.transport = transport; + this.running = true; this.logger = LogManager.getLogger(Session.class.toString() + id.value()); this.logger.info("Created new session"); @@ -37,22 +30,16 @@ public class Session implements Runnable { thread.start(); } - public String read() throws IOException { - int length = in.readInt(); - byte[] payload = new byte[length]; - in.readFully(payload); - return new String(payload, StandardCharsets.UTF_8); - } - public void close() throws IOException { - socket.close(); + transport.close(); + this.running = false; } @Override public void run() { - while (true) { + while (running) { try { - System.out.println("Recieved: " + read()); + System.out.println("Recieved: " + transport.read()); } catch (EOFException e) { logger.info("Client disconnected"); break; From 7fcb47ed6ffeadd8f611c1e10e8e26b72bd65f42 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Thu, 12 Mar 2026 19:12:53 +0100 Subject: [PATCH 28/61] Add: EventBus class for event handling and subscription management --- .../cs108/casono/server/network/EventBus.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/EventBus.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/EventBus.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/EventBus.java new file mode 100644 index 0000000..5802c8c --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/EventBus.java @@ -0,0 +1,23 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; + +public class EventBus { + private final Map<Class<?>, List<Consumer<Object>>> handlers = new ConcurrentHashMap<>(); + + public <T> void subscribe(Class<T> eventType, Consumer<T> handler) { + handlers.computeIfAbsent(eventType, k -> new CopyOnWriteArrayList<>()) + .add((Consumer<Object>) handler); + } + + public <T> void publish(T event) { + List<Consumer<Object>> subscribers = handlers.get(event.getClass()); + if (subscribers != null) { + subscribers.forEach(h -> h.accept(event)); + } + } +} From fc8a92789ab453c4997bbb147e7060078cf4d5fa Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Thu, 12 Mar 2026 19:13:25 +0100 Subject: [PATCH 29/61] Add: DisconnectEvent as record --- .../dmi/dbis/cs108/casono/server/network/DisconnectEvent.java | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/DisconnectEvent.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/DisconnectEvent.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/DisconnectEvent.java new file mode 100644 index 0000000..a271ed6 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/DisconnectEvent.java @@ -0,0 +1,3 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network; + +public record DisconnectEvent(SessionId sessionId) {} From 54588276d116031bb2aad90af8bbac59cc00a749 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Thu, 12 Mar 2026 19:15:25 +0100 Subject: [PATCH 30/61] Add: Let Session publish DisconnectEvent and EventHandler subscribe and handle it --- .../dmi/dbis/cs108/casono/server/ServerApp.java | 4 +++- .../cs108/casono/server/network/NetworkManager.java | 11 +++++++++-- .../dmi/dbis/cs108/casono/server/network/Session.java | 5 ++++- 3 files changed, 16 insertions(+), 4 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 e7dd3f3..d056413 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 @@ -1,5 +1,6 @@ package ch.unibas.dmi.dbis.cs108.casono.server; +import ch.unibas.dmi.dbis.cs108.casono.server.network.EventBus; import ch.unibas.dmi.dbis.cs108.casono.server.network.NetworkManager; public class ServerApp { @@ -7,7 +8,8 @@ public class ServerApp { int port = Integer.parseInt(arg); System.out.println("You've selected the server. It will accept connections at port " + port); - NetworkManager networkManager = new NetworkManager(port); + EventBus eventBus = new EventBus(); + NetworkManager networkManager = new NetworkManager(port, eventBus); networkManager.start(); } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java index aed074a..8699852 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java @@ -15,12 +15,15 @@ public class NetworkManager implements Runnable { private Logger logger; private Thread thread; private Boolean running; + private EventBus eventBus; - public NetworkManager(Integer port) { + public NetworkManager(Integer port, EventBus eventBus) { this.port = port; this.logger = LogManager.getLogger(NetworkManager.class); this.thread = new Thread(this, "networkManager"); this.running = true; + this.eventBus = eventBus; + this.eventBus.subscribe(DisconnectEvent.class, event -> clientDisconnected(event)); } /* Starts the internal thread to accept new connections. @@ -30,6 +33,10 @@ public class NetworkManager implements Runnable { thread.start(); } + public void clientDisconnected(DisconnectEvent event) { + logger.info("Session " + event.sessionId().value() + " disconnected adhasghd"); + } + @Override public void run() { try (ServerSocket serverSocket = new ServerSocket(port)) { @@ -38,7 +45,7 @@ public class NetworkManager implements Runnable { System.out.println("Accepted connection from " + clientSocket.getRemoteSocketAddress()); - Session session = new Session(new TcpTransport(clientSocket)); + Session session = new Session(new TcpTransport(clientSocket), eventBus); session.start(); } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/Session.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/Session.java index 24b30d2..1ae946c 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/Session.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/Session.java @@ -11,12 +11,14 @@ public class Session implements Runnable { private TransportLayer transport; private Logger logger; private Boolean running; + private EventBus eventBus; - public Session(TransportLayer transport) throws IOException { + public Session(TransportLayer transport, EventBus eventBus) throws IOException { this.id = new SessionId(); this.thread = new Thread(this, "session-" + this.id.value()); this.transport = transport; this.running = true; + this.eventBus = eventBus; this.logger = LogManager.getLogger(Session.class.toString() + id.value()); this.logger.info("Created new session"); @@ -42,6 +44,7 @@ public class Session implements Runnable { System.out.println("Recieved: " + transport.read()); } catch (EOFException e) { logger.info("Client disconnected"); + eventBus.publish(new DisconnectEvent(id)); break; } catch (IOException e) { e.printStackTrace(); From 7b32fe11473b22309cb15d46d34b2e5cb33bdfb5 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Thu, 12 Mar 2026 19:16:59 +0100 Subject: [PATCH 31/61] Add: Event interface and implement it in DisconnectEvent, update EventBus to only allow Event type --- .../dmi/dbis/cs108/casono/server/network/DisconnectEvent.java | 2 +- .../ch/unibas/dmi/dbis/cs108/casono/server/network/Event.java | 3 +++ .../unibas/dmi/dbis/cs108/casono/server/network/EventBus.java | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/Event.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/DisconnectEvent.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/DisconnectEvent.java index a271ed6..ea23e18 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/DisconnectEvent.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/DisconnectEvent.java @@ -1,3 +1,3 @@ package ch.unibas.dmi.dbis.cs108.casono.server.network; -public record DisconnectEvent(SessionId sessionId) {} +public record DisconnectEvent(SessionId sessionId) implements Event {} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/Event.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/Event.java new file mode 100644 index 0000000..0c89aa1 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/Event.java @@ -0,0 +1,3 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network; + +interface Event {} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/EventBus.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/EventBus.java index 5802c8c..e099f67 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/EventBus.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/EventBus.java @@ -9,12 +9,12 @@ import java.util.function.Consumer; public class EventBus { private final Map<Class<?>, List<Consumer<Object>>> handlers = new ConcurrentHashMap<>(); - public <T> void subscribe(Class<T> eventType, Consumer<T> handler) { + public <T extends Event> void subscribe(Class<T> eventType, Consumer<T> handler) { handlers.computeIfAbsent(eventType, k -> new CopyOnWriteArrayList<>()) .add((Consumer<Object>) handler); } - public <T> void publish(T event) { + public <T extends Event> void publish(T event) { List<Consumer<Object>> subscribers = handlers.get(event.getClass()); if (subscribers != null) { subscribers.forEach(h -> h.accept(event)); From db7892d9c423b55a7391d8c5e19a08bf76fbe524 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Thu, 12 Mar 2026 19:26:00 +0100 Subject: [PATCH 32/61] Fix: 'Type safety: Unchecked cast from ...' --- .../ch/unibas/dmi/dbis/cs108/casono/server/network/EventBus.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/EventBus.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/EventBus.java index e099f67..a77da29 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/EventBus.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/EventBus.java @@ -9,6 +9,7 @@ import java.util.function.Consumer; public class EventBus { private final Map<Class<?>, List<Consumer<Object>>> handlers = new ConcurrentHashMap<>(); + @SuppressWarnings("unchecked") // This cast is safe, because handlers only get passed the type they subscribed to public <T extends Event> void subscribe(Class<T> eventType, Consumer<T> handler) { handlers.computeIfAbsent(eventType, k -> new CopyOnWriteArrayList<>()) .add((Consumer<Object>) handler); From e1029932d8940b7acd15821841ff1c6f2ff1aa31 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Thu, 12 Mar 2026 19:27:29 +0100 Subject: [PATCH 33/61] Fix: 'Potential null pointer access' --- .../unibas/dmi/dbis/cs108/casono/server/network/EventBus.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/EventBus.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/EventBus.java index a77da29..ba8d9cd 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/EventBus.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/EventBus.java @@ -2,6 +2,7 @@ package ch.unibas.dmi.dbis.cs108.casono.server.network; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Consumer; @@ -16,6 +17,7 @@ public class EventBus { } public <T extends Event> void publish(T event) { + Objects.requireNonNull(event, "event must not be null"); List<Consumer<Object>> subscribers = handlers.get(event.getClass()); if (subscribers != null) { subscribers.forEach(h -> h.accept(event)); From c301039bfc13085f51292c06f63f547c7f5644cb Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Thu, 12 Mar 2026 19:34:46 +0100 Subject: [PATCH 34/61] Add: SessionManager to store all active sessions --- .../casono/server/network/SessionManager.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/SessionManager.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/SessionManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/SessionManager.java new file mode 100644 index 0000000..3a794f9 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/SessionManager.java @@ -0,0 +1,28 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class SessionManager { + private Map<SessionId, Session> sessions; + + public SessionManager() { + this.sessions = new ConcurrentHashMap<>(); + } + + public void addSession(Session session) { + sessions.put(session.getId(), session); + } + + public Session removeSession(SessionId id) { + return sessions.remove(id); + } + + public Session removeSession(Session session) { + return sessions.remove(session.getId()); + } + + public Session getSessionById(SessionId id) { + return sessions.get(id); + } +} From 74566f99cf096b5be1eca15796a3b5dd8897f0e8 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Thu, 12 Mar 2026 20:12:52 +0100 Subject: [PATCH 35/61] Fix: Direct (unallowed) cast from Consumer<T> to Consumer<Object> --- .../unibas/dmi/dbis/cs108/casono/server/network/EventBus.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/EventBus.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/EventBus.java index ba8d9cd..5b9d6c6 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/EventBus.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/EventBus.java @@ -13,7 +13,7 @@ public class EventBus { @SuppressWarnings("unchecked") // This cast is safe, because handlers only get passed the type they subscribed to public <T extends Event> void subscribe(Class<T> eventType, Consumer<T> handler) { handlers.computeIfAbsent(eventType, k -> new CopyOnWriteArrayList<>()) - .add((Consumer<Object>) handler); + .add((Consumer<Object>) (Consumer<?>) handler); } public <T extends Event> void publish(T event) { From 711de2e6aaa153066499e302355685fab7e7b0e0 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Thu, 12 Mar 2026 20:13:50 +0100 Subject: [PATCH 36/61] Add: Make use of SessionManager --- .../ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java | 6 +++++- .../dbis/cs108/casono/server/network/NetworkManager.java | 5 ++++- .../dbis/cs108/casono/server/network/SessionManager.java | 2 ++ 3 files changed, 11 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 d056413..c704d5e 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 @@ -1,7 +1,9 @@ package ch.unibas.dmi.dbis.cs108.casono.server; +import ch.unibas.dmi.dbis.cs108.casono.server.network.DisconnectEvent; import ch.unibas.dmi.dbis.cs108.casono.server.network.EventBus; import ch.unibas.dmi.dbis.cs108.casono.server.network.NetworkManager; +import ch.unibas.dmi.dbis.cs108.casono.server.network.SessionManager; public class ServerApp { public static void start(String arg) { @@ -9,7 +11,9 @@ public class ServerApp { System.out.println("You've selected the server. It will accept connections at port " + port); EventBus eventBus = new EventBus(); - NetworkManager networkManager = new NetworkManager(port, eventBus); + SessionManager sessionManager = new SessionManager(); + eventBus.subscribe(DisconnectEvent.class, event -> sessionManager.removeSession(event.sessionId())); + NetworkManager networkManager = new NetworkManager(port, sessionManager, eventBus); networkManager.start(); } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java index 8699852..3e60e59 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java @@ -15,13 +15,15 @@ public class NetworkManager implements Runnable { private Logger logger; private Thread thread; private Boolean running; + private SessionManager sessionManager; private EventBus eventBus; - public NetworkManager(Integer port, EventBus eventBus) { + public NetworkManager(Integer port, SessionManager sessionManager, EventBus eventBus) { this.port = port; this.logger = LogManager.getLogger(NetworkManager.class); this.thread = new Thread(this, "networkManager"); this.running = true; + this.sessionManager = sessionManager; this.eventBus = eventBus; this.eventBus.subscribe(DisconnectEvent.class, event -> clientDisconnected(event)); } @@ -46,6 +48,7 @@ public class NetworkManager implements Runnable { System.out.println("Accepted connection from " + clientSocket.getRemoteSocketAddress()); Session session = new Session(new TcpTransport(clientSocket), eventBus); + sessionManager.addSession(session); session.start(); } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/SessionManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/SessionManager.java index 3a794f9..69fcdbe 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/SessionManager.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/SessionManager.java @@ -12,9 +12,11 @@ public class SessionManager { public void addSession(Session session) { sessions.put(session.getId(), session); + System.out.println("Added session " + session.getId().value() + " to session manager"); } public Session removeSession(SessionId id) { + System.out.println("Removed session " + id.value() + " from session manager"); return sessions.remove(id); } From 0b51f4f45ae97cf6816c8e4e2ab96416cacab0ac Mon Sep 17 00:00:00 2001 From: Jona Walpert <98217482+Jona-Walpert@users.noreply.github.com> Date: Fri, 13 Mar 2026 11:29:21 +0100 Subject: [PATCH 37/61] Chore: Add client run task to gradle --- build.gradle | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/build.gradle b/build.gradle index 99a402a..469141c 100644 --- a/build.gradle +++ b/build.gradle @@ -41,6 +41,12 @@ tasks.named('jar', Jar) { } } +tasks.register('cruntest', JavaExec) { + classpath = sourceSets.main.runtimeClasspath + mainClass = 'ch.unibas.dmi.dbis.cs108.casono.Main' + jvmArgs '--enable-native-access=ALL-UNNAMED' + args 'client', '0.0.0.0:1234' +} tasks.register('fatJar', Jar) { group = 'build' From 49019075e4479014adfb5b69d3efef59d1c2c9b7 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Fri, 13 Mar 2026 14:42:19 +0100 Subject: [PATCH 38/61] Move: Network related files into respective folders --- .../dbis/cs108/casono/server/network/DisconnectEvent.java | 3 --- .../unibas/dmi/dbis/cs108/casono/server/network/Event.java | 3 --- .../dbis/cs108/casono/server/network/NetworkManager.java | 6 ++++++ .../cs108/casono/server/network/events/DisconnectEvent.java | 5 +++++ .../dmi/dbis/cs108/casono/server/network/events/Event.java | 3 +++ .../cs108/casono/server/network/{ => events}/EventBus.java | 2 +- .../cs108/casono/server/network/{ => sessions}/Session.java | 6 +++++- .../casono/server/network/{ => sessions}/SessionId.java | 2 +- .../server/network/{ => sessions}/SessionManager.java | 2 +- .../casono/server/network/{ => transport}/TcpTransport.java | 2 +- .../server/network/{ => transport}/TransportLayer.java | 2 +- 11 files changed, 24 insertions(+), 12 deletions(-) delete mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/DisconnectEvent.java delete mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/Event.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/DisconnectEvent.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/Event.java rename src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/{ => events}/EventBus.java (93%) rename src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/{ => sessions}/Session.java (83%) rename src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/{ => sessions}/SessionId.java (85%) rename src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/{ => sessions}/SessionManager.java (92%) rename src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/{ => transport}/TcpTransport.java (94%) rename src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/{ => transport}/TransportLayer.java (74%) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/DisconnectEvent.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/DisconnectEvent.java deleted file mode 100644 index ea23e18..0000000 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/DisconnectEvent.java +++ /dev/null @@ -1,3 +0,0 @@ -package ch.unibas.dmi.dbis.cs108.casono.server.network; - -public record DisconnectEvent(SessionId sessionId) implements Event {} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/Event.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/Event.java deleted file mode 100644 index 0c89aa1..0000000 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/Event.java +++ /dev/null @@ -1,3 +0,0 @@ -package ch.unibas.dmi.dbis.cs108.casono.server.network; - -interface Event {} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java index 3e60e59..880bd56 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java @@ -7,6 +7,12 @@ import java.net.Socket; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import ch.unibas.dmi.dbis.cs108.casono.server.network.events.DisconnectEvent; +import ch.unibas.dmi.dbis.cs108.casono.server.network.events.EventBus; +import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.Session; +import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionManager; +import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.TcpTransport; + /** * Creates and manages the server socket. Accepts new incomming connections and creates sessions. */ diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/DisconnectEvent.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/DisconnectEvent.java new file mode 100644 index 0000000..65f8152 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/DisconnectEvent.java @@ -0,0 +1,5 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.events; + +import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId; + +public record DisconnectEvent(SessionId sessionId) implements Event {} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/Event.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/Event.java new file mode 100644 index 0000000..4fb6948 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/Event.java @@ -0,0 +1,3 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.events; + +interface Event {} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/EventBus.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/EventBus.java similarity index 93% rename from src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/EventBus.java rename to src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/EventBus.java index 5b9d6c6..6644313 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/EventBus.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/EventBus.java @@ -1,4 +1,4 @@ -package ch.unibas.dmi.dbis.cs108.casono.server.network; +package ch.unibas.dmi.dbis.cs108.casono.server.network.events; import java.util.List; import java.util.Map; diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/Session.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/Session.java similarity index 83% rename from src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/Session.java rename to src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/Session.java index 1ae946c..e2ece59 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/Session.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/Session.java @@ -1,10 +1,14 @@ -package ch.unibas.dmi.dbis.cs108.casono.server.network; +package ch.unibas.dmi.dbis.cs108.casono.server.network.sessions; import java.io.EOFException; import java.io.IOException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import ch.unibas.dmi.dbis.cs108.casono.server.network.events.DisconnectEvent; +import ch.unibas.dmi.dbis.cs108.casono.server.network.events.EventBus; +import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.TransportLayer; + public class Session implements Runnable { private SessionId id; private Thread thread; diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/SessionId.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionId.java similarity index 85% rename from src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/SessionId.java rename to src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionId.java index 0af06ad..117dba3 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/SessionId.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionId.java @@ -1,4 +1,4 @@ -package ch.unibas.dmi.dbis.cs108.casono.server.network; +package ch.unibas.dmi.dbis.cs108.casono.server.network.sessions; import java.util.UUID; diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/SessionManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionManager.java similarity index 92% rename from src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/SessionManager.java rename to src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionManager.java index 69fcdbe..f583be1 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/SessionManager.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionManager.java @@ -1,4 +1,4 @@ -package ch.unibas.dmi.dbis.cs108.casono.server.network; +package ch.unibas.dmi.dbis.cs108.casono.server.network.sessions; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/TcpTransport.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/transport/TcpTransport.java similarity index 94% rename from src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/TcpTransport.java rename to src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/transport/TcpTransport.java index 4cb9910..6a761e6 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/TcpTransport.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/transport/TcpTransport.java @@ -1,4 +1,4 @@ -package ch.unibas.dmi.dbis.cs108.casono.server.network; +package ch.unibas.dmi.dbis.cs108.casono.server.network.transport; import java.io.DataInputStream; import java.io.DataOutputStream; diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/TransportLayer.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/transport/TransportLayer.java similarity index 74% rename from src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/TransportLayer.java rename to src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/transport/TransportLayer.java index 30aac09..a4453e4 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/TransportLayer.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/transport/TransportLayer.java @@ -1,4 +1,4 @@ -package ch.unibas.dmi.dbis.cs108.casono.server.network; +package ch.unibas.dmi.dbis.cs108.casono.server.network.transport; import java.io.IOException; From a39775bd0466d50cdb4704113fa204605a0fb036 Mon Sep 17 00:00:00 2001 From: Jona Walpert <98217482+Jona-Walpert@users.noreply.github.com> Date: Fri, 13 Mar 2026 14:54:47 +0100 Subject: [PATCH 39/61] refactor: Button_ID - Lobby_ID translation is now only stored in hasmap and not in a Json file anymore --- .../ui/lobbyui/CasinomainuiController.java | 53 ++++---------- .../LobbyButtonTranslationManager.java | 72 +++---------------- .../lobbyui/LobbyButtonGridManagerTest.java | 1 - .../LobbyButtonTranslationManagerTest.java | 10 --- 4 files changed, 24 insertions(+), 112 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/CasinomainuiController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/CasinomainuiController.java index 8f50083..5c235cf 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/CasinomainuiController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/CasinomainuiController.java @@ -1,82 +1,56 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui; -/** - * Controller for the Casono main UI lobby. - * Handles UI initialization and user actions. - */ - -/** - * Controller for the Casono main UI lobby. - * Handles UI initialization and user actions. - */ import javafx.application.Platform; import javafx.fxml.FXML; import javafx.scene.control.Label; -import javafx.scene.layout.AnchorPane; import javafx.scene.control.Button; +import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import javafx.scene.shape.Rectangle; -import javafx.application.Platform; -import javafx.fxml.FXML; -import javafx.scene.control.Label; -import javafx.scene.control.TextField; -import javafx.scene.input.KeyCode; -import javafx.scene.input.KeyEvent; -import javafx.scene.input.MouseEvent; -import javafx.scene.layout.HBox; import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import javafx.scene.shape.Rectangle; /** * Controller for the Casono main UI lobby. * Handles UI initialization and user actions. - * <p> - * Standardkonstruktor für den Controller. */ public class CasinomainuiController { - /** - * Default constructor for the controller. - */ - public CasinomainuiController() { - // Default constructor - } - - /** Root pane of the UI. */ @FXML private AnchorPane rootPane; - /** Title label for the main UI. */ @FXML private Label titleLabel; - /** Subtitle label for the main UI. */ @FXML private Label subtitleLabel; - /** Logo image view. */ @FXML - private javafx.scene.image.ImageView logoView; - /** Green box shape element. */ + private ImageView logoView; @FXML - private javafx.scene.shape.Rectangle greenBox; - /** Exit button for closing the application. */ + private Rectangle greenBox; @FXML private Button exitbutton; - /** Casino table VBox for grid rendering. */ @FXML - private javafx.scene.layout.VBox casinoTable; + private VBox casinoTable; private LobbyButtonTranslationManager translationManager; private LobbyButtonGridManager gridManager; private int nextButtonId = 1; + public CasinomainuiController() { + // Default constructor + } + /** * Initializes the UI components and sets default values. */ + @FXML public void initialize() { titleLabel.setText("Casono"); subtitleLabel.setText("Texas Hold'em Poker"); - logoView.setImage(new javafx.scene.image.Image(getClass().getResource("/images/logo.png").toExternalForm())); + logoView.setImage(new Image(getClass().getResource("/images/logo.png").toExternalForm())); translationManager = new LobbyButtonTranslationManager(); gridManager = new LobbyButtonGridManager(new javafx.scene.layout.GridPane(), translationManager); + casinoTable.getChildren().clear(); casinoTable.getChildren().add(gridManager.getGridPane()); gridManager.renderLobbyButtons(); } @@ -108,5 +82,4 @@ public class CasinomainuiController { System.out.println("Fehler beim Hinzufügen: " + e.getMessage()); } } - } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonTranslationManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonTranslationManager.java index 60b0f94..e3dd72e 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonTranslationManager.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonTranslationManager.java @@ -2,82 +2,34 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui; import java.util.HashMap; import java.util.Map; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import org.json.JSONObject; /** - * Handles JSON translation between ButtonID and LobbyID. - * <p> - * Verwaltet das Mapping zwischen Button-IDs und Lobby-IDs, speichert und lädt die Zuordnung aus einer JSON-Datei. + * Verwaltet das Mapping zwischen Button-IDs und Lobby-IDs rein im Speicher. + * Keine Dateioperationen, nur Laufzeitdatenstruktur. */ public class LobbyButtonTranslationManager { - /** - * Removes all lobbies from the mapping and saves the file. - */ - public void clearAllLobbies() { - buttonIdToLobbyId.clear(); - saveTranslation(); - } - /** Pfad zur JSON-Translationsdatei. */ - private final String translationFilePath = "src/main/resources/ui-structure/lobby_button_translation.json"; - /** Zuordnung ButtonID → LobbyID. */ + /** Maximale Anzahl an Buttons/Lobbys */ + private static final int MAX_BUTTONS = 8; + /** Zuordnung ButtonID → LobbyID */ private final Map<Integer, Integer> buttonIdToLobbyId = new HashMap<>(); - /** Maximale Anzahl an Buttons im Grid. */ - private final int maxButtons = 8; /** - * Konstruktor: lädt die Zuordnung aus der JSON-Datei. + * Konstruktor: initialisiert die Zuordnung leer. */ public LobbyButtonTranslationManager() { - loadTranslation(); + // Zuordnung bleibt leer beim Start } /** - * Lädt die Zuordnung ButtonID → LobbyID aus der JSON-Datei. - * Falls die Datei nicht existiert, bleibt die Zuordnung leer. - */ - public void loadTranslation() { - try { - Path path = Path.of(translationFilePath); - if (!Files.exists(path)) return; - String json = Files.readString(path); - JSONObject obj = new JSONObject(json); - buttonIdToLobbyId.clear(); - for (String key : obj.keySet()) { - buttonIdToLobbyId.put(Integer.parseInt(key), obj.getInt(key)); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - /** - * Speichert die aktuelle Zuordnung ButtonID → LobbyID in die JSON-Datei. - */ - public void saveTranslation() { - try { - JSONObject obj = new JSONObject(); - for (Map.Entry<Integer, Integer> entry : buttonIdToLobbyId.entrySet()) { - obj.put(String.valueOf(entry.getKey()), entry.getValue()); - } - Files.write(Path.of(translationFilePath), obj.toString(2).getBytes()); - } catch (IOException e) { - e.printStackTrace(); - } - } - - /** - * Prüft, ob das Grid voll ist (maxButtons erreicht). + * Prüft, ob das Grid voll ist (MAX_BUTTONS erreicht). * @return true, wenn Grid voll; sonst false */ public boolean isFull() { - return buttonIdToLobbyId.size() >= maxButtons; + return buttonIdToLobbyId.size() >= MAX_BUTTONS; } /** - * Fügt eine Zuordnung ButtonID → LobbyID hinzu und speichert sie. + * Fügt eine Zuordnung ButtonID → LobbyID hinzu. * @param buttonId Die ID des Buttons * @param lobbyId Die ID der Lobby * @throws Exception wenn das Grid voll ist @@ -85,16 +37,14 @@ public class LobbyButtonTranslationManager { public void addLobbyButton(int buttonId, int lobbyId) throws Exception { if (isFull()) throw new Exception("Grid is full!"); buttonIdToLobbyId.put(buttonId, lobbyId); - saveTranslation(); } /** - * Entfernt eine Zuordnung für die gegebene ButtonID und speichert. + * Entfernt eine Zuordnung für die gegebene ButtonID. * @param buttonId Die ID des zu entfernenden Buttons */ public void removeLobbyButton(int buttonId) { buttonIdToLobbyId.remove(buttonId); - saveTranslation(); } /** diff --git a/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManagerTest.java b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManagerTest.java index 4042ce0..3f4d574 100644 --- a/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManagerTest.java +++ b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManagerTest.java @@ -15,7 +15,6 @@ class LobbyButtonGridManagerTest { gridPane = new GridPane(); translationManager = new LobbyButtonTranslationManager(); translationManager.getButtonIdToLobbyId().clear(); - translationManager.saveTranslation(); gridManager = new LobbyButtonGridManager(gridPane, translationManager); } diff --git a/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonTranslationManagerTest.java b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonTranslationManagerTest.java index 82a35ad..4c1e526 100644 --- a/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonTranslationManagerTest.java +++ b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonTranslationManagerTest.java @@ -12,7 +12,6 @@ class LobbyButtonTranslationManagerTest { void setUp() { manager = new LobbyButtonTranslationManager(); manager.getButtonIdToLobbyId().clear(); - manager.saveTranslation(); } @Test @@ -38,15 +37,6 @@ class LobbyButtonTranslationManagerTest { assertEquals("Grid is full!", ex.getMessage()); } - @Test - void testSaveAndLoadTranslation() throws Exception { - manager.addLobbyButton(3, 300); - manager.saveTranslation(); - manager.getButtonIdToLobbyId().clear(); - manager.loadTranslation(); - assertEquals(300, manager.getLobbyIdForButton(3)); - } - @Test void testGetButtonIdToLobbyId() throws Exception { manager.addLobbyButton(4, 400); From 4231ca3e97918ff34a7dcc41bf381467c440788d Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Fri, 13 Mar 2026 15:12:06 +0100 Subject: [PATCH 40/61] Ammend to 49019075e4479014adfb5b69d3efef59d1c2c9b7 --- .../unibas/dmi/dbis/cs108/casono/server/ServerApp.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 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 c704d5e..a7814b0 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 @@ -1,10 +1,13 @@ package ch.unibas.dmi.dbis.cs108.casono.server; -import ch.unibas.dmi.dbis.cs108.casono.server.network.DisconnectEvent; -import ch.unibas.dmi.dbis.cs108.casono.server.network.EventBus; import ch.unibas.dmi.dbis.cs108.casono.server.network.NetworkManager; -import ch.unibas.dmi.dbis.cs108.casono.server.network.SessionManager; +import ch.unibas.dmi.dbis.cs108.casono.server.network.events.DisconnectEvent; +import ch.unibas.dmi.dbis.cs108.casono.server.network.events.EventBus; +import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionManager; +/** + * Application class for starting the server. + */ public class ServerApp { public static void start(String arg) { int port = Integer.parseInt(arg); From 38133e676151091fb6b39ebb92dc5b06523db4e8 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Fri, 13 Mar 2026 15:16:15 +0100 Subject: [PATCH 41/61] Docs: Add JavDoc for all classes and methods --- .../casono/server/network/NetworkManager.java | 20 +++++++++++-- .../network/events/DisconnectEvent.java | 3 ++ .../casono/server/network/events/Event.java | 3 ++ .../server/network/events/EventBus.java | 14 +++++++++ .../server/network/sessions/Session.java | 26 +++++++++++++++++ .../server/network/sessions/SessionId.java | 19 ++++++++++-- .../network/sessions/SessionManager.java | 29 +++++++++++++++++++ .../network/transport/TcpTransport.java | 26 +++++++++++++++++ .../network/transport/TransportLayer.java | 22 ++++++++++++++ 9 files changed, 157 insertions(+), 5 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java index 880bd56..50b53d9 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java @@ -14,7 +14,7 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionManager; import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.TcpTransport; /** - * Creates and manages the server socket. Accepts new incomming connections and creates sessions. + * Creates and manages the server socket. Accepts new incoming connections and creates sessions. */ public class NetworkManager implements Runnable { private Integer port; @@ -24,6 +24,13 @@ public class NetworkManager implements Runnable { private SessionManager sessionManager; private EventBus eventBus; + /** + * Creates a new NetworkManager with the given port, session manager, and event bus. + * + * @param port the port to listen on + * @param sessionManager the session manager to use + * @param eventBus the event bus for events + */ public NetworkManager(Integer port, SessionManager sessionManager, EventBus eventBus) { this.port = port; this.logger = LogManager.getLogger(NetworkManager.class); @@ -34,17 +41,26 @@ public class NetworkManager implements Runnable { this.eventBus.subscribe(DisconnectEvent.class, event -> clientDisconnected(event)); } - /* Starts the internal thread to accept new connections. + /** + * Starts the internal thread to accept new connections. */ public void start() { logger.debug("Starting server at port " + port); thread.start(); } + /** + * Handles client disconnection events. + * + * @param event the disconnect event + */ public void clientDisconnected(DisconnectEvent event) { logger.info("Session " + event.sessionId().value() + " disconnected adhasghd"); } + /** + * Runs the network manager loop, accepting connections. + */ @Override public void run() { try (ServerSocket serverSocket = new ServerSocket(port)) { diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/DisconnectEvent.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/DisconnectEvent.java index 65f8152..b6b0d78 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/DisconnectEvent.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/DisconnectEvent.java @@ -2,4 +2,7 @@ package ch.unibas.dmi.dbis.cs108.casono.server.network.events; import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId; +/** + * Represents a disconnect event for a session. + */ public record DisconnectEvent(SessionId sessionId) implements Event {} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/Event.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/Event.java index 4fb6948..75c0954 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/Event.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/Event.java @@ -1,3 +1,6 @@ package ch.unibas.dmi.dbis.cs108.casono.server.network.events; +/** + * Marker interface for events in the event bus system. + */ interface Event {} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/EventBus.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/EventBus.java index 6644313..4141ab8 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/EventBus.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/EventBus.java @@ -7,15 +7,29 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Consumer; +/** + * A simple event bus for publishing and subscribing to events. + */ public class EventBus { private final Map<Class<?>, List<Consumer<Object>>> handlers = new ConcurrentHashMap<>(); + /** + * Subscribes a handler to a specific event type. + * + * @param eventType the class of the event to subscribe to + * @param handler the consumer to handle the event + */ @SuppressWarnings("unchecked") // This cast is safe, because handlers only get passed the type they subscribed to public <T extends Event> void subscribe(Class<T> eventType, Consumer<T> handler) { handlers.computeIfAbsent(eventType, k -> new CopyOnWriteArrayList<>()) .add((Consumer<Object>) (Consumer<?>) handler); } + /** + * Publishes an event to all subscribed handlers. + * + * @param event the event to publish + */ public <T extends Event> void publish(T event) { Objects.requireNonNull(event, "event must not be null"); List<Consumer<Object>> subscribers = handlers.get(event.getClass()); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/Session.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/Session.java index e2ece59..9b2f344 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/Session.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/Session.java @@ -9,6 +9,9 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.events.DisconnectEvent; import ch.unibas.dmi.dbis.cs108.casono.server.network.events.EventBus; import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.TransportLayer; +/** + * Represents a client session in the network server. + */ public class Session implements Runnable { private SessionId id; private Thread thread; @@ -17,6 +20,13 @@ public class Session implements Runnable { private Boolean running; private EventBus eventBus; + /** + * Creates a new Session with the given transport and event bus. + * + * @param transport the transport layer for communication + * @param eventBus the event bus for publishing events + * @throws IOException if an I/O error occurs during initialization + */ public Session(TransportLayer transport, EventBus eventBus) throws IOException { this.id = new SessionId(); this.thread = new Thread(this, "session-" + this.id.value()); @@ -28,19 +38,35 @@ public class Session implements Runnable { this.logger.info("Created new session"); } + /** + * Returns the ID of this session. + * + * @return the session ID + */ public SessionId getId() { return this.id; } + /** + * Starts the session thread. + */ public void start() { thread.start(); } + /** + * Closes the session and its transport. + * + * @throws IOException if an I/O error occurs + */ public void close() throws IOException { transport.close(); this.running = false; } + /** + * Runs the session loop, reading from the transport. + */ @Override public void run() { while (running) { diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionId.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionId.java index 117dba3..431ebdc 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionId.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionId.java @@ -2,20 +2,33 @@ package ch.unibas.dmi.dbis.cs108.casono.server.network.sessions; import java.util.UUID; +/** + * Represents a unique identifier for a session. + */ public class SessionId { - /** - * The SessionId is used to identify a unique client connection in the SessionRegistry - */ private final UUID value; + /** + * Creates a new SessionId with a randomly generated UUID. + */ public SessionId() { this.value = UUID.randomUUID(); } + /** + * Creates a new SessionId with the specified UUID. + * + * @param UUID to use for this SessionId + */ public SessionId(UUID value) { this.value = value; } + /** + * Returns the UUID value of this SessionId. + * + * @return the UUID value + */ public UUID value() { return value; } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionManager.java index f583be1..42bc4ac 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionManager.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionManager.java @@ -3,27 +3,56 @@ package ch.unibas.dmi.dbis.cs108.casono.server.network.sessions; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +/** + * Manages active sessions in the server. + */ public class SessionManager { private Map<SessionId, Session> sessions; + /** + * Constructs a new SessionManager. + */ public SessionManager() { this.sessions = new ConcurrentHashMap<>(); } + /** + * Adds a session to the manager. + * + * @param session the session to add + */ public void addSession(Session session) { sessions.put(session.getId(), session); System.out.println("Added session " + session.getId().value() + " to session manager"); } + /** + * Removes a session by its ID. + * + * @param id the ID of the session to remove + * @return the removed session, or null if not found + */ public Session removeSession(SessionId id) { System.out.println("Removed session " + id.value() + " from session manager"); return sessions.remove(id); } + /** + * Removes the specified session. + * + * @param session the session to remove + * @return the removed session, or null if not found + */ public Session removeSession(Session session) { return sessions.remove(session.getId()); } + /** + * Retrieves a session by its ID. + * + * @param id the ID of the session to retrieve + * @return the session with the specified ID, or null if not found + */ public Session getSessionById(SessionId id) { return sessions.get(id); } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/transport/TcpTransport.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/transport/TcpTransport.java index 6a761e6..3f80d6c 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/transport/TcpTransport.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/transport/TcpTransport.java @@ -6,17 +6,32 @@ import java.io.IOException; import java.net.Socket; import java.nio.charset.StandardCharsets; +/** + * Implements TCP-based transport layer for network communication. + */ public class TcpTransport implements TransportLayer { private Socket socket; private DataInputStream in; private DataOutputStream out; + /** + * Creates a new TcpTransport with the given socket. + * + * @param socket the socket to use for communication + * @throws IOException if an I/O error occurs + */ public TcpTransport(Socket socket) throws IOException { this.socket = socket; this.in = new DataInputStream(socket.getInputStream()); this.out = new DataOutputStream(socket.getOutputStream()); } + /** + * Reads a string from the socket. + * + * @return the read string + * @throws IOException if an I/O error occurs + */ public String read() throws IOException { int length = in.readInt(); byte[] payload = new byte[length]; @@ -24,6 +39,12 @@ public class TcpTransport implements TransportLayer { return new String(payload, StandardCharsets.UTF_8); } + /** + * Writes a string to the socket. + * + * @param payload the string to write + * @throws IOException if an I/O error occurs + */ public void write(String payload) throws IOException { byte[] rawPayload = payload.getBytes(StandardCharsets.UTF_8); out.writeInt(rawPayload.length); @@ -31,6 +52,11 @@ public class TcpTransport implements TransportLayer { out.flush(); } + /** + * Closes the socket. + * + * @throws IOException if an I/O error occurs + */ public void close() throws IOException { socket.close(); } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/transport/TransportLayer.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/transport/TransportLayer.java index a4453e4..f487a9c 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/transport/TransportLayer.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/transport/TransportLayer.java @@ -2,8 +2,30 @@ package ch.unibas.dmi.dbis.cs108.casono.server.network.transport; import java.io.IOException; +/** + * Interface for transport layer implementations. + */ public interface TransportLayer { + /** + * Reads data from the transport layer. + * + * @return the read data as a string + * @throws IOException if an I/O error occurs + */ String read() throws IOException; + + /** + * Writes data to the transport layer. + * + * @param data the data to write + * @throws IOException if an I/O error occurs + */ void write(String data) throws IOException; + + /** + * Closes the transport layer. + * + * @throws IOException if an I/O error occurs + */ void close() throws IOException; } From 0e30e082478721a5dca9555a6e13c64fa282d246 Mon Sep 17 00:00:00 2001 From: Jona Walpert <98217482+Jona-Walpert@users.noreply.github.com> Date: Fri, 13 Mar 2026 15:16:42 +0100 Subject: [PATCH 42/61] chore: fix JVM native access warnings for JavaFX --- build.gradle | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/build.gradle b/build.gradle index b9a94a7..825c944 100644 --- a/build.gradle +++ b/build.gradle @@ -48,6 +48,16 @@ tasks.named('jar', Jar) { } } + +// Dieses JVM-Argument erlaubt JavaFX und anderen Libraries den Zugriff auf native Methoden. +// Ohne diese Einstellung erscheinen Warnungen und zukünftige Java-Versionen könnten den Zugriff blockieren. +// Siehe: https://openjdk.org/jeps/472 +tasks.withType(JavaExec) { + jvmArgs += '--enable-native-access=ALL-UNNAMED' +} + + + tasks.register('cruntest', JavaExec) { classpath = sourceSets.main.runtimeClasspath mainClass = 'ch.unibas.dmi.dbis.cs108.casono.Main' From 4bcb9a987417a41ad3e845d3f092f430025303e5 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Fri, 13 Mar 2026 15:46:12 +0100 Subject: [PATCH 43/61] Style: Newline at end of file --- .../dmi/dbis/cs108/casono/server/network/sessions/Session.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/Session.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/Session.java index 9b2f344..d2464f1 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/Session.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/Session.java @@ -82,4 +82,4 @@ public class Session implements Runnable { } } } -} \ No newline at end of file +} From 004b2e23139874add39e2eae6421dacfca15cef0 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Fri, 13 Mar 2026 16:38:56 +0100 Subject: [PATCH 44/61] Build: Add checkstyle plugin and add gradle task --- build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.gradle b/build.gradle index 9fd8b42..597dec4 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id 'application' id 'java' id 'org.openjfx.javafxplugin' version '0.1.0' + id 'checkstyle' } group = 'ch.unibas.dmi.dbis' @@ -43,6 +44,10 @@ test { useJUnitPlatform() } +checkstyle { + toolVersion = '10.21.0' +} + tasks.named('jar', Jar) { manifest { attributes('Main-Class': application.mainClass.get()) From 57010d28728703556a67a53ba99804a3cd997b81 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Fri, 13 Mar 2026 17:28:24 +0100 Subject: [PATCH 45/61] Add: Configuration file for checkstyle --- config/checkstyle/checkstyle.xml | 165 +++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 config/checkstyle/checkstyle.xml diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000..59645dd --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,165 @@ +<?xml version="1.0"?> +<!DOCTYPE module PUBLIC + "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" + "https://checkstyle.org/dtds/configuration_1_3.dtd"> + +<module name="Checker"> + <!-- Fail build if checkstyle finds violations --> + <!-- https://checkstyle.sourceforge.io/config.html#Properties_1 --> + <property name="severity" value="error"/> + + <!-- Only check java files --> + <!-- https://checkstyle.sourceforge.io/config.html#Properties_1 --> + <property name="fileExtensions" value="java"/> + + <!-- Only allow spaces --> + <!-- https://checkstyle.sourceforge.io/checks/whitespace/filetabcharacter.html --> + <module name="FileTabCharacter"/> + + <!-- Maximale Zeilenlänge --> + <!-- https://checkstyle.sourceforge.io/checks/sizes/linelength.html --> + <module name="LineLength"> + <property name="max" value="100"/> + </module> + + <module name="TreeWalker"> + <!-- Allows for emptylines BETWEEN methods and classes but DISALLOWS newlines after an opening bracket --> + <!-- https://checkstyle.sourceforge.io/checks/whitespace/emptylineseparator.html --> + <module name="EmptyLineSeparator"> + <property name="allowNoEmptyLineBetweenFields" value="true"/> + <property name="tokens" value=" + METHOD_DEF, + CTOR_DEF, + STATIC_INIT, + INSTANCE_INIT, + CLASS_DEF, + INTERFACE_DEF, + ENUM_DEF + "/> + </module> + + <!-- Enforce indentation of four whitespaces --> + <!-- https://checkstyle.sourceforge.io/checks/misc/indentation.html --> + <module name="Indentation"> + <property name="basicOffset" value="4"/> + <property name="caseIndent" value="4"/> + <property name="throwsIndent" value="4"/> + <property name="arrayInitIndent" value="4"/> + <property name="lineWrappingIndentation" value="8"/> + </module> + + <!-- No wildcard imports (import x.*) --> + <!-- https://checkstyle.sourceforge.io/checks/imports/avoidstarimport.html --> + <module name="AvoidStarImport"/> + + <!-- No unused imports --> + <!-- https://checkstyle.sourceforge.io/checks/imports/unusedimports.html --> + <module name="UnusedImports"/> + + <!-- Classes, enums, records, ... as PascalCase --> + <!-- https://checkstyle.sourceforge.io/checks/naming/typename.html --> + <module name="TypeName"/> + + <!-- Method names as camelCase --> + <!-- https://checkstyle.sourceforge.io/checks/naming/methodname.html --> + <module name="MethodName"/> + + <!-- Parameter names as camelCase --> + <!-- https://checkstyle.sourceforge.io/checks/naming/parametername.html --> + <module name="ParameterName"/> + + <!-- Lokal variables as camelCase --> + <!-- https://checkstyle.sourceforge.io/checks/naming/localvariablename.html --> + <module name="LocalVariableName"/> + + <!-- Constants as UPPER_SNAKE_CASE --> + <!-- https://checkstyle.sourceforge.io/checks/naming/constantname.html --> + <module name="ConstantName"/> + + <!-- Field names as camelCase --> + <!-- https://checkstyle.sourceforge.io/checks/naming/membername.html --> + <module name="MemberName"> + <property name="format" value="^[a-z][a-zA-Z0-9]*$"/> + </module> + + <!-- Restrict length of method name --> + <!-- https://checkstyle.sourceforge.io/checks/sizes/methodlength.html --> + <module name="MethodLength"> + <property name="max" value="40"/> + <property name="countEmpty" value="false"/> + </module> + + <!-- Restrict number of parameters --> + <!-- https://checkstyle.sourceforge.io/checks/sizes/parameternumber.html --> + <module name="ParameterNumber"> + <property name="max" value="5"/> + </module> + + <!-- Modifier-Reihenfolge: public static final ... --> + <!-- https://checkstyle.sourceforge.io/checks/modifier/modifierorder.html --> + <module name="ModifierOrder"/> + + <!-- Require braces arround code block (no single instruction after if/else/while/...)--> + <!-- https://checkstyle.sourceforge.io/checks/blocks/needbraces.html --> + <module name="NeedBraces"/> + + <!-- Require the left curly braces at the same line --> + <!-- https://checkstyle.sourceforge.io/checks/blocks/leftcurly.html --> + <module name="LeftCurly"/> + + <!-- Require right curly braces at the same line as following instructions --> + <!-- https://checkstyle.sourceforge.io/checks/blocks/rightcurly.html --> + <module name="RightCurly"/> + + <!-- Disallow empty code blocks --> + <!-- https://checkstyle.sourceforge.io/checks/blocks/emptyblock.html --> + <module name="EmptyBlock"> + <property name="option" value="text"/> + </module> + + <!-- Disallow empty code blocks after catch --> + <!-- https://checkstyle.sourceforge.io/checks/blocks/emptycatchblock.html --> + <module name="EmptyCatchBlock"> + <property name="exceptionVariableName" value="expected|ignored"/> + </module> + + <!-- Require string literals to be compared with .equals and not == --> + <!-- https://checkstyle.sourceforge.io/checks/coding/stringliteralequality.html --> + <module name="StringLiteralEquality"/> + + <!-- Disallow standalone numbers in code --> + <!-- https://checkstyle.sourceforge.io/checks/coding/magicnumber.html --> + <module name="MagicNumber"> + <property name="ignoreNumbers" value="-1, 0, 1, 2"/> + <property name="ignoreAnnotation" value="true"/> + </module> + + <!-- Disallow nested code blocks dangling in code --> + <!-- https://checkstyle.sourceforge.io/checks/blocks/avoidnestedblocks.html --> + <module name="AvoidNestedBlocks"/> + + <!-- Disallow System.out.println --> + <!-- https://checkstyle.sourceforge.io/checks/regexp/regexp.html --> + <module name="Regexp"> + <property name="format" value="System\.(out|err)\.print"/> + <property name="illegalPattern" value="true"/> + <property name="message" value="No `System.out.print(err)` in production code — use a logger."/> + </module> + + <!-- Disallow whitespace before semicolon or bracket --> + <!-- https://checkstyle.sourceforge.io/checks/whitespace/nowhitespacebefore.html --> + <module name="NoWhitespaceBefore"/> + + <!-- Disallow whitespace after code --> + <!-- https://checkstyle.sourceforge.io/checks/whitespace/whitespaceafter.html --> + <module name="WhitespaceAfter"/> + + <!-- No trailing whitespace at line ending --> + <!-- https://checkstyle.sourceforge.io/checks/regexp/regexp.html --> + <module name="Regexp"> + <property name="format" value=" +$"/> + <property name="illegalPattern" value="true"/> + <property name="message" value="Kein trailing Whitespace."/> + </module> + </module> +</module> From 18f17564d516aea598b5179258290249af622afc Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Fri, 13 Mar 2026 17:29:41 +0100 Subject: [PATCH 46/61] Ci: Configure GitLab ci pipeline to execute Checkstyle on manual trigger --- .gitlab-ci.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..b251ec5 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,10 @@ +stages: + - lint + +checkstyle: + stage: lint + image: gradle:8-jdk21 + script: + - gradle checkstyleMain checkstyleTest + rules: + - when: manual From e797470ca3545a48a7f5e2f4ae7da99a84163ec1 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Fri, 13 Mar 2026 17:35:42 +0100 Subject: [PATCH 47/61] Ci: Bump image for job to 'gradle:9.3.1-jdk25' --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b251ec5..d134405 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,7 +3,7 @@ stages: checkstyle: stage: lint - image: gradle:8-jdk21 + image: gradle:9.3.1-jdk25 script: - gradle checkstyleMain checkstyleTest rules: From d311553de7212e2f4b46e719e924028b405b96df Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Fri, 13 Mar 2026 22:34:41 +0100 Subject: [PATCH 48/61] Add: Suppressions config for tests --- build.gradle | 4 ++++ config/checkstyle/checkstyle.xml | 9 +++++++-- config/checkstyle/suppressions.xml | 24 ++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 config/checkstyle/suppressions.xml diff --git a/build.gradle b/build.gradle index 597dec4..9091ade 100644 --- a/build.gradle +++ b/build.gradle @@ -46,6 +46,10 @@ test { checkstyle { toolVersion = '10.21.0' + configFile = file('config/checkstyle/checkstyle.xml') + configProperties = [ + 'suppressionFile': file('config/checkstyle/suppressions.xml').absolutePath + ] } tasks.named('jar', Jar) { diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index 59645dd..0737055 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -12,6 +12,10 @@ <!-- https://checkstyle.sourceforge.io/config.html#Properties_1 --> <property name="fileExtensions" value="java"/> + <module name="SuppressionFilter"> + <property name="file" value="${suppressionFile}"/> + </module> + <!-- Only allow spaces --> <!-- https://checkstyle.sourceforge.io/checks/whitespace/filetabcharacter.html --> <module name="FileTabCharacter"/> @@ -141,9 +145,10 @@ <!-- Disallow System.out.println --> <!-- https://checkstyle.sourceforge.io/checks/regexp/regexp.html --> <module name="Regexp"> + <property name="id" value="SystemOutErr"/> <property name="format" value="System\.(out|err)\.print"/> <property name="illegalPattern" value="true"/> - <property name="message" value="No `System.out.print(err)` in production code — use a logger."/> + <property name="message" value="No `System.out.print(err)` allowed — use a logger."/> </module> <!-- Disallow whitespace before semicolon or bracket --> @@ -159,7 +164,7 @@ <module name="Regexp"> <property name="format" value=" +$"/> <property name="illegalPattern" value="true"/> - <property name="message" value="Kein trailing Whitespace."/> + <property name="message" value="No trailing whitespace."/> </module> </module> </module> diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml new file mode 100644 index 0000000..d532e42 --- /dev/null +++ b/config/checkstyle/suppressions.xml @@ -0,0 +1,24 @@ +<?xml version="1.0"?> +<!DOCTYPE suppressions PUBLIC + "-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN" + "https://checkstyle.org/dtds/suppressions_1_2.dtd"> + +<suppressions> + <!-- Allow literal, for example assertEquals(42, life.answer()) --> + <suppress checks="MagicNumber" files=".*Test\.java"/> + + <!-- Allow longer method names as the are usually descriptive of the test --> + <suppress checks="MethodLength" files=".*Test\.java"/> + + <!-- Allow methods with more parameters --> + <suppress checks="ParameterNumber" files=".*Test\.java"/> + + <!-- Allow for System.out.println in tests --> + <suppress id="SystemOutErr" files=".*Test\.java"/> + + <!-- Allow wildcard imports (import x.*) --> + <suppress checks="AvoidStarImport" files=".*Test\.java"/> + + <!-- Allow for compacter line seperators --> + <suppress checks="EmptyLineSeparator" files=".*Test\.java"/> +</suppressions> From 28f8d172371de237e64ef18ac63a14d08fa31f0d Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Fri, 13 Mar 2026 22:36:23 +0100 Subject: [PATCH 49/61] Ci: Attach checkstyle report as job artifact --- .gitlab-ci.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d134405..184e9e5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,5 +6,12 @@ checkstyle: image: gradle:9.3.1-jdk25 script: - gradle checkstyleMain checkstyleTest + artifacts: + when: always + paths: + - build/reports/checkstyle/main.html + - build/reports/checkstyle/test.html + expose_as: 'Checkstyle Report' + expire_in: 1 week rules: - when: manual From 815b60232cf9ab253a446d74f5dfb4d96f88063a Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Fri, 13 Mar 2026 22:56:36 +0100 Subject: [PATCH 50/61] Add: ImportOrder check to checkstyle config --- config/checkstyle/checkstyle.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index 0737055..746bba4 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -60,6 +60,10 @@ <!-- https://checkstyle.sourceforge.io/checks/imports/unusedimports.html --> <module name="UnusedImports"/> + <!-- No unordered / ungrouped imports --> + <!-- https://checkstyle.sourceforge.io/checks/imports/importorder.html --> + <module name="ImportOrder"/> + <!-- Classes, enums, records, ... as PascalCase --> <!-- https://checkstyle.sourceforge.io/checks/naming/typename.html --> <module name="TypeName"/> From fb02faffc985d997fb445c7867cbe4bc20a369e0 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Fri, 13 Mar 2026 22:57:34 +0100 Subject: [PATCH 51/61] Build: Add spotless plugin and add task --- build.gradle | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/build.gradle b/build.gradle index 9091ade..dbcd602 100644 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,7 @@ plugins { id 'java' id 'org.openjfx.javafxplugin' version '0.1.0' id 'checkstyle' + id 'com.diffplug.spotless' version '7.0.2' } group = 'ch.unibas.dmi.dbis' @@ -52,6 +53,14 @@ checkstyle { ] } +spotless { + java { + googleJavaFormat() + importOrder() + removeUnusedImports() + } +} + tasks.named('jar', Jar) { manifest { attributes('Main-Class': application.mainClass.get()) From cfeea5222d855910a8787d82587298eb4046a021 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Fri, 13 Mar 2026 23:05:23 +0100 Subject: [PATCH 52/61] Ci: Add job to check if project compiles --- .gitlab-ci.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 184e9e5..d49e9b1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,5 +1,6 @@ stages: - lint + - build checkstyle: stage: lint @@ -15,3 +16,11 @@ checkstyle: expire_in: 1 week rules: - when: manual + +compile-check: + stage: build + image: gradle:9.3.1-jdk25 + script: + - gradle compileTestJava + rules: + - when: manual From 8aa71a4430e957f60cb87a45762428772dd4d306 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Fri, 13 Mar 2026 23:11:28 +0100 Subject: [PATCH 53/61] Ci: Cache gradle for faster job execution --- .gitlab-ci.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d49e9b1..7bb625a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,7 +2,17 @@ stages: - lint - build +# Reusable definitions +.gradle-cache: &gradle-cache + cache: + key: '$CI_PROJECT_ID-gradle' + paths: + - .gradle/ + - ~/.gradle/caches/ + +# Jobs checkstyle: + <<: *gradle-cache stage: lint image: gradle:9.3.1-jdk25 script: @@ -18,6 +28,7 @@ checkstyle: - when: manual compile-check: + <<: *gradle-cache stage: build image: gradle:9.3.1-jdk25 script: From 147f84665ea0ec3831db9da96555e9b5e531a43b Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Fri, 13 Mar 2026 23:17:10 +0100 Subject: [PATCH 54/61] Ci: Make compile-check job run independent from other jobs --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7bb625a..67d3422 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -33,5 +33,6 @@ compile-check: image: gradle:9.3.1-jdk25 script: - gradle compileTestJava + needs: [] rules: - when: manual From a7bfb59e2f0b266ee555ee2bc8a5e42d8f3b2acf Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Sat, 14 Mar 2026 09:01:25 +0100 Subject: [PATCH 55/61] Add: Python script to convert findings of checkstyle to GitLab code report --- scripts/convert-checkstyle-gl-report.py | 29 +++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 scripts/convert-checkstyle-gl-report.py diff --git a/scripts/convert-checkstyle-gl-report.py b/scripts/convert-checkstyle-gl-report.py new file mode 100644 index 0000000..990cff6 --- /dev/null +++ b/scripts/convert-checkstyle-gl-report.py @@ -0,0 +1,29 @@ +# Skript to convert findings of checkstyle into code quality report for gitlab +import xml.etree.ElementTree as ET, json, hashlib + +findings = [] +for source in ["main", "test"]: + try: + tree = ET.parse(f"build/reports/checkstyle/{source}.xml") + for file in tree.findall("file"): + path = file.get("name") + for error in file.findall("error"): + line = int(error.get("line", 1)) + msg = error.get("message", "") + fingerprint = hashlib.md5(f"{path}{line}{msg}".encode()).hexdigest() + findings.append({ + "type": "issue", + "check_name": error.get("source", "checkstyle"), + "description": msg, + "severity": "minor", + "fingerprint": fingerprint, + "location": { + "path": "src/" + path.split("/src/")[1] if "/src/" in path else path, + "lines": {"begin": line} + } + }) + except FileNotFoundError: + pass + +with open("gl-code-quality-report.json", "w") as f: + json.dump(findings, f) From 0157102ae53db3db4963e6d66bea3aa4c97f163b Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Sat, 14 Mar 2026 09:09:29 +0100 Subject: [PATCH 56/61] Ci: Add job to execute python script and upload code quality report --- .gitlab-ci.yml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 67d3422..98d7899 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,5 +1,6 @@ stages: - lint + - report - build # Reusable definitions @@ -17,16 +18,34 @@ checkstyle: image: gradle:9.3.1-jdk25 script: - gradle checkstyleMain checkstyleTest + allow_failure: true artifacts: when: always paths: - - build/reports/checkstyle/main.html - - build/reports/checkstyle/test.html + - build/reports/checkstyle/main.html + - build/reports/checkstyle/main.xml + - build/reports/checkstyle/test.html + - build/reports/checkstyle/test.xml expose_as: 'Checkstyle Report' expire_in: 1 week rules: - when: manual +checkstyle-report: + stage: report + image: python:3.14 + needs: + - job: checkstyle + artifacts: true + optional: true + script: + - python3 scripts/convert-checkstyle-gl-report.py + artifacts: + when: always + reports: + codequality: gl-code-quality-report.json + expire_in: 1 week + compile-check: <<: *gradle-cache stage: build From 39b6c5e13e74bd0b94d53c0832bc7e1d46fbca92 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Sat, 14 Mar 2026 09:33:55 +0100 Subject: [PATCH 57/61] Ci: Run different jobs on push or mr --- .gitlab-ci.yml | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 98d7899..018d3d6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -11,9 +11,38 @@ stages: - .gradle/ - ~/.gradle/caches/ +.on-commit: &on-commit + rules: + - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' + when: never + - if: '$CI_COMMIT_BRANCH' + allow_failure: true + +.on-mr: &on-mr + rules: + - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' + allow_failure: true # deactivate after all issues (shown by checkstyle) have been resolved + # Jobs checkstyle: - <<: *gradle-cache + <<: [*gradle-cache, *on-commit] + stage: lint + image: gradle:9.3.1-jdk25 + script: + - gradle checkstyleMain checkstyleTest + allow_failure: true + artifacts: + when: always + paths: + - build/reports/checkstyle/main.html + - build/reports/checkstyle/main.xml + - build/reports/checkstyle/test.html + - build/reports/checkstyle/test.xml + expose_as: 'Checkstyle Report' + expire_in: 1 week + +checkstyle-mr: + <<: [*gradle-cache, *on-mr] stage: lint image: gradle:9.3.1-jdk25 script: @@ -28,16 +57,16 @@ checkstyle: - build/reports/checkstyle/test.xml expose_as: 'Checkstyle Report' expire_in: 1 week - rules: - - when: manual checkstyle-report: stage: report image: python:3.14 needs: - - job: checkstyle + - job: checkstyle-mr artifacts: true optional: true + rules: + - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' script: - python3 scripts/convert-checkstyle-gl-report.py artifacts: @@ -54,4 +83,7 @@ compile-check: - gradle compileTestJava needs: [] rules: - - when: manual + - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' + allow_failure: false + - if: '$CI_COMMIT_BRANCH' + allow_failure: false From 6e35c599da663038164104440f94b05fc255d273 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Sat, 14 Mar 2026 10:06:12 +0100 Subject: [PATCH 58/61] Ci: Add job to run test on push and mr --- .gitlab-ci.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 018d3d6..04bb8bd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,6 +2,7 @@ stages: - lint - report - build + - test # Reusable definitions .gradle-cache: &gradle-cache @@ -87,3 +88,23 @@ compile-check: allow_failure: false - if: '$CI_COMMIT_BRANCH' allow_failure: false + +test: + <<: *gradle-cache + stage: test + image: gradle:9.3.1-jdk25 + script: + - gradle test + artifacts: + when: always + reports: + junit: build/test-results/test/*.xml + paths: + - build/reports/tests/test/ + expose_as: 'Test Report' + expire_in: 1 week + rules: + - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' + allow_failure: false + - if: '$CI_COMMIT_BRANCH' + allow_failure: true From e22446bffcbbc38ebde0b6f86b5c37222b6b72a0 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Sat, 14 Mar 2026 13:24:05 +0100 Subject: [PATCH 59/61] Fix: Use 'Android Open Source Project' indentation (4 spaces) instead of 2 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index dbcd602..234b69f 100644 --- a/build.gradle +++ b/build.gradle @@ -55,7 +55,7 @@ checkstyle { spotless { java { - googleJavaFormat() + googleJavaFormat('1.25.2').aosp() importOrder() removeUnusedImports() } From 9a88cfc945b1cf29d8759e99b2f478a39cfcf0ea Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer <lars.winzer@stud.unibas.ch> Date: Sat, 14 Mar 2026 13:24:48 +0100 Subject: [PATCH 60/61] Add: trim trailing whitespace and end with newline for spotless task --- build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle b/build.gradle index 234b69f..e126d6d 100644 --- a/build.gradle +++ b/build.gradle @@ -58,6 +58,8 @@ spotless { googleJavaFormat('1.25.2').aosp() importOrder() removeUnusedImports() + trimTrailingWhitespace() + endWithNewline() } } From 09ebacc1e400d3d93eeafe53fee0883adfbc7862 Mon Sep 17 00:00:00 2001 From: Jona Walpert <jona.walpert@stud.unibas.ch> Date: Sat, 14 Mar 2026 13:36:27 +0100 Subject: [PATCH 61/61] chore: remove demo code from project --- .../dmi/dbis/cs108/example/HelloWorld.java | 12 ---- .../dbis/cs108/example/gui/javafx/GUI.java | 32 --------- .../dbis/cs108/example/gui/javafx/Main.java | 14 ---- .../dbis/cs108/example/HelloWorldTest.java | 67 ------------------- 4 files changed, 125 deletions(-) delete mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/example/HelloWorld.java delete mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/example/gui/javafx/GUI.java delete mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/example/gui/javafx/Main.java delete mode 100644 src/test/java/ch/unibas/dmi/dbis/cs108/example/HelloWorldTest.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/example/HelloWorld.java b/src/main/java/ch/unibas/dmi/dbis/cs108/example/HelloWorld.java deleted file mode 100644 index e7ab515..0000000 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/example/HelloWorld.java +++ /dev/null @@ -1,12 +0,0 @@ -package ch.unibas.dmi.dbis.cs108.example; - -/** - * A simple HelloWorld class. - */ -public class HelloWorld { - - public static void main(String[] args) { - System.out.println("Hello World"); - } - -} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/example/gui/javafx/GUI.java b/src/main/java/ch/unibas/dmi/dbis/cs108/example/gui/javafx/GUI.java deleted file mode 100644 index 3746cae..0000000 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/example/gui/javafx/GUI.java +++ /dev/null @@ -1,32 +0,0 @@ -package ch.unibas.dmi.dbis.cs108.example.gui.javafx; - -import javafx.application.Application; -import javafx.scene.Scene; -import javafx.scene.control.Label; -import javafx.scene.layout.StackPane; -import javafx.stage.Stage; - -/** - * This is an example JavaFX-Application. - */ -public class GUI extends Application { - - /** - * Launching this method will not work on some platforms. - * What you should do is to create a separate main class and launch the GUI class from there as is done in {@link Main} - */ - public static void main(String[] args) { - launch(args); - } - - @Override - public void start(Stage stage) { - String javaVersion = System.getProperty("java.version"); - String javafxVersion = System.getProperty("javafx.version"); - Label l = new Label("Hello, JavaFX " + javafxVersion + ", running on Java " + javaVersion + "."); - Scene scene = new Scene(new StackPane(l), 640, 480); - stage.setScene(scene); - stage.show(); - } - -} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/example/gui/javafx/Main.java b/src/main/java/ch/unibas/dmi/dbis/cs108/example/gui/javafx/Main.java deleted file mode 100644 index aac1f4d..0000000 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/example/gui/javafx/Main.java +++ /dev/null @@ -1,14 +0,0 @@ -package ch.unibas.dmi.dbis.cs108.example.gui.javafx; - -import javafx.application.Application; - -public class Main { - - /** - * This is simply a wrapper to launch the {@link GUI} class. - * The reason this class exists is documented in {@link GUI#main(String[])} - */ - public static void main(String[] args) { - Application.launch(GUI.class, args); - } -} diff --git a/src/test/java/ch/unibas/dmi/dbis/cs108/example/HelloWorldTest.java b/src/test/java/ch/unibas/dmi/dbis/cs108/example/HelloWorldTest.java deleted file mode 100644 index a91b85d..0000000 --- a/src/test/java/ch/unibas/dmi/dbis/cs108/example/HelloWorldTest.java +++ /dev/null @@ -1,67 +0,0 @@ -package ch.unibas.dmi.dbis.cs108.example; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * An example test class. - * Checks the output of the {@link HelloWorld} class and makes sure it contains "Hello World" - */ -public class HelloWorldTest { - - /* - * Streams to store system.out and system.err content - */ - private ByteArrayOutputStream outStream = new ByteArrayOutputStream(); - private ByteArrayOutputStream errStream = new ByteArrayOutputStream(); - - /* - * Here we store the previous pointers to system.out / system.err - */ - private PrintStream outBackup; - private PrintStream errBackup; - - /** - * This method is executed before each test. - * It redirects System.out and System.err to our variables {@link #outStream} and {@link #errStream}. - * This allows us to test their content later. - */ - @BeforeEach - public void redirectStdOutStdErr() { - outBackup = System.out; - errBackup = System.err; - System.setOut(new PrintStream(outStream)); - System.setErr(new PrintStream(errStream)); - } - - /** - * This method is run after each test. - * It redirects System.out / System.err back to the normal streams. - */ - @AfterEach - public void reestablishStdOutStdErr() { - System.setOut(outBackup); - System.setErr(errBackup); - } - - /** - * This is a normal JUnit-Test. It executes the HelloWorld-Method and verifies that it actually wrote "Hello World" to stdout - */ - @Test - public void testMain() { - HelloWorld.main(new String[0]); - String toTest = outStream.toString(); - toTest = removeNewline(toTest); - assertTrue(toTest.contains("Hello World")); - } - - private static String removeNewline(String str) { - return str.replace("\n", "").replace("\r", ""); - } -}