diff --git a/.gitignore b/.gitignore index a1b3b23..c29286e 100644 --- a/.gitignore +++ b/.gitignore @@ -104,5 +104,21 @@ $RECYCLE.BIN/ ## Jenv .java-version -## VSCode -.vscode \ No newline at end of file +## diagrams.net / draw.io +# Draw.io temporary files +*.drawio.bkp +*.drawio.tmp + +# Auto-save files +*.drawio.autosave + +# Exported images (optional – nur wenn du Exporte nicht versionieren willst) +*.drawio.png +*.drawio.svg +*.drawio.pdf + +## IntelliJ +.idea + +## bin +bin/ \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..6fb7f20 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "vscjava.vscode-java-pack", + "mhutchie.git-graph", + "gitlab.gitlab-workflow", + ] +} \ No newline at end of file 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/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..5c91f72 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,17 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Export drawio to SVG", + "type": "shell", + "command": "./scripts/export-drawio.sh", + "problemMatcher": [] + }, + { + "label": "Export puml to SVG", + "type": "shell", + "command": "./scripts/export-plantuml.sh", + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 01ffb96..bd9fc2c 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,8 @@ This protocol handles game state synchronization, player actions, lobby and glob │ └── test/ # Unit tests ├── documents/ # Project resources and documentation │ ├── images/ # Images for README and documentation +│ ├── diary # Diary files for each organizational meetup +│ ├── blog # Blog files covering project-related topics │ ├── docs # Documentation │ ├── milestones/ # Milestone deliverables (6 milestones) │ └── (Other resources) # Additional project materials diff --git a/bin/main/images/backround.png b/bin/main/images/backround.png new file mode 100644 index 0000000..e5e1841 Binary files /dev/null and b/bin/main/images/backround.png differ diff --git a/bin/main/images/logo.png b/bin/main/images/logo.png new file mode 100644 index 0000000..e906bd4 Binary files /dev/null and b/bin/main/images/logo.png differ diff --git a/bin/main/images/logoinverted.png b/bin/main/images/logoinverted.png new file mode 100644 index 0000000..73053b6 Binary files /dev/null and b/bin/main/images/logoinverted.png differ diff --git a/build.gradle b/build.gradle index ca3f72c..8196bdb 100644 --- a/build.gradle +++ b/build.gradle @@ -23,10 +23,17 @@ 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 { + // 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") + + // 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") } diff --git a/documents/blog/README.md b/documents/blog/README.md new file mode 100644 index 0000000..aa92b15 --- /dev/null +++ b/documents/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)* diff --git a/documents/docs/diary/24-02.md b/documents/diary/24-02.md similarity index 100% rename from documents/docs/diary/24-02.md rename to documents/diary/24-02.md diff --git a/documents/docs/diary/27-02.md b/documents/diary/27-02.md similarity index 100% rename from documents/docs/diary/27-02.md rename to documents/diary/27-02.md diff --git a/documents/docs/diary/README.md b/documents/diary/README.md similarity index 100% rename from documents/docs/diary/README.md rename to documents/diary/README.md diff --git a/documents/docs/networking/server-architecture.md b/documents/docs/networking/server-architecture.md new file mode 100644 index 0000000..8537382 --- /dev/null +++ b/documents/docs/networking/server-architecture.md @@ -0,0 +1,86 @@ +# 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 + +![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` +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. + +### `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. + +### `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. 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 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 diff --git a/scripts/export-drawio.sh b/scripts/export-drawio.sh new file mode 100755 index 0000000..f075d01 --- /dev/null +++ b/scripts/export-drawio.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +# Check for draw.io installation +DRAWIO_PATH="" +check_drawio_installed() { + if command -v drawio &>/dev/null; then + DRAWIO_PATH=$(command -v drawio) + return 0 # drawio via PATH + elif [[ -x "/Applications/draw.io.app/Contents/MacOS/draw.io" ]]; then + DRAWIO_PATH="/Applications/draw.io.app/Contents/MacOS/draw.io" + return 0 # For macOS check at default path + else + return 1 # not found + fi +} + +if ! check_drawio_installed; then + echo "Error: 'drawio' was not found. Make sure, that the draw.io cli is installed." + echo "https://www.diagrams.net/" + exit 1 +else + echo "Draw.io installation found at: $DRAWIO_PATH" +fi + +# Compare source (draw.io document) and exported file svg on mtime +needs_export() { + local drawio_file="$1" + local svg_file="$2" + + # If no file found, export + if [[ ! -f "$svg_file" ]]; then + return 0 + fi + + if [[ "$drawio_file" -nt "$svg_file" ]]; then + return 0 + else + return 1 + fi +} + +# If (re-)export is necessary export with drawio cli +export_file() { + local drawio_file="$1" + local svg_file="${drawio_file%.drawio}.svg" + + if needs_export "$drawio_file" "$svg_file"; then + echo "↻ Exporting: $drawio_file" + echo "$svg_file" + echo "$drawio_file" + + "$DRAWIO_PATH" --export --format svg --transparent --output "$svg_file" "$drawio_file" + else + echo "✓ Up-to-date: $drawio_file" + fi +} + +# Iterate over all .drawio files +main() { + # Source - https://stackoverflow.com/a/9612232 + # Posted by Kevin, modified by community. See post 'Timeline' for change history + # Retrieved 2026-03-09, License - CC BY-SA 4.0 + find . -name '*.drawio' -print0 | + while IFS= read -r -d '' line; do + export_file "$line" + done + + echo "✓ Exported all files" +} + +# Start script at entry point +main diff --git a/scripts/export-plantuml.sh b/scripts/export-plantuml.sh new file mode 100755 index 0000000..59404e9 --- /dev/null +++ b/scripts/export-plantuml.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +# Check for plantuml installation +PLANTUML_PATH="" +check_plantuml_installed() { + if command -v plantuml &>/dev/null; then + PLANTUML_PATH=$(command -v plantuml) + return 0 # plantuml via PATH + else + return 1 # not found + fi +} + +if ! check_plantuml_installed; then + echo "Error: 'plantuml' was not found. Make sure, that plantuml is installed." + echo "https://plantuml.com/starting" + exit 1 +else + echo "PlantUML installation found at: $PLANTUML_PATH" +fi + +# Compare source (.puml document) and exported file svg on mtime +needs_export() { + local puml_file="$1" + local svg_file="$2" + + # If no file found, export + if [[ ! -f "$svg_file" ]]; then + return 0 + fi + + if [[ "$puml_file" -nt "$svg_file" ]]; then + return 0 + else + return 1 + fi +} + +# If (re-)export is necessary export with plantuml cli +export_file() { + local puml_file="$1" + local svg_file="${puml_file%.puml}.svg" + + if needs_export "$puml_file" "$svg_file"; then + echo "↻ Exporting: $puml_file" + "$PLANTUML_PATH" -tsvg "$puml_file" + else + echo "✓ Up-to-date: $puml_file" + fi +} + +# Iterate over all .puml files +main() { + # Source - https://stackoverflow.com/a/9612232 + # Posted by Kevin, modified by community. See post 'Timeline' for change history + # Retrieved 2026-03-09, License - CC BY-SA 4.0 + find . -name '*.puml' -print0 | + while IFS= read -r -d '' line; do + export_file "$line" + done + + echo "✓ Exported all files" +} + +# Start script at entry point +main \ No newline at end of file diff --git a/src/main/resources/images/backround.png b/src/main/resources/images/backround.png new file mode 100644 index 0000000..e5e1841 Binary files /dev/null and b/src/main/resources/images/backround.png differ diff --git a/src/main/resources/images/logo.png b/src/main/resources/images/logo.png new file mode 100644 index 0000000..e906bd4 Binary files /dev/null and b/src/main/resources/images/logo.png differ diff --git a/src/main/resources/images/logoinverted.png b/src/main/resources/images/logoinverted.png new file mode 100644 index 0000000..73053b6 Binary files /dev/null and b/src/main/resources/images/logoinverted.png differ diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml new file mode 100644 index 0000000..01d74ca --- /dev/null +++ b/src/main/resources/log4j2.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration status="warn"> + + <Appenders> + <Console name="Console" target="SYSTEM_OUT"> + <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="debug"> + <AppenderRef ref="Console"/> + </Root> + </Loggers> + +</Configuration> \ No newline at end of file