Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b082cbb5f2 |
@@ -92,14 +92,6 @@ compile-check:
|
||||
- if: '$CI_COMMIT_BRANCH'
|
||||
allow_failure: false
|
||||
|
||||
javadoc-check:
|
||||
<<: [*gradle-cache, *on-mr]
|
||||
stage: build
|
||||
image: gradle:9.3.1-jdk25
|
||||
script:
|
||||
- gradle javaDoc --configuration-cache --configuration-cache-problems=warn
|
||||
needs: []
|
||||
|
||||
test:
|
||||
<<: *gradle-cache
|
||||
stage: test
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
### Checklist
|
||||
- [ ] I reproduced the problem using the steps above
|
||||
- [ ] I searched documentation for relevant information
|
||||
- [ ] I searched documentation docs for relevant information
|
||||
- [ ] I added relevant labels
|
||||
|
||||
/label ~bug
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
<!-- Detailed description of the desired behavior -->
|
||||
|
||||
### Checklist
|
||||
- [ ] I have described the function in detail
|
||||
- [ ] I searched docs for alternative implementations matching my needs
|
||||
- [ ] I reproduced the problem using the steps above
|
||||
- [ ] I searched documentation docs for relevant information
|
||||
- [ ] I added relevant labels
|
||||
|
||||
/label ~enhancement
|
||||
|
||||
@@ -108,17 +108,3 @@ tasks.register('fatJar', Jar) {
|
||||
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
||||
})
|
||||
}
|
||||
|
||||
tasks.register('javadocJar', Jar) {
|
||||
group = 'build'
|
||||
description = 'Assembles a Javadoc JAR.'
|
||||
dependsOn tasks.named('javadoc')
|
||||
archiveClassifier = 'javadoc'
|
||||
from(tasks.javadoc.destinationDir)
|
||||
}
|
||||
|
||||
tasks.register('build-cs108') {
|
||||
group = 'build'
|
||||
description = 'Produces executable JAR and Javadoc JAR for CS108.'
|
||||
dependsOn tasks.named('fatJar'), tasks.named('javadocJar')
|
||||
}
|
||||
|
||||
@@ -16,20 +16,49 @@
|
||||
<property name="file" value="${suppressionFile}"/>
|
||||
</module>
|
||||
|
||||
|
||||
<!-- ERROR — Correctness & Bugs -->
|
||||
<!-- Violations that can cause runtime errors or directly affect correctness. -->
|
||||
|
||||
<!-- Only allow spaces -->
|
||||
<!-- https://checkstyle.sourceforge.io/checks/whitespace/filetabcharacter.html -->
|
||||
<module name="FileTabCharacter"/>
|
||||
|
||||
<!-- Maximale Zeilenlänge (AOSP enforces 100 lines, the Google Java Style Guide 80)-->
|
||||
<!-- https://checkstyle.sourceforge.io/checks/sizes/linelength.html -->
|
||||
<module name="LineLength">
|
||||
<property name="max" value="100"/>
|
||||
<module name="TreeWalker">
|
||||
<!-- 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"/>
|
||||
|
||||
<!-- Require string literals to be compared with .equals and not == -->
|
||||
<!-- https://checkstyle.sourceforge.io/checks/coding/stringliteralequality.html -->
|
||||
<module name="StringLiteralEquality"/>
|
||||
|
||||
<!-- Disallow empty code blocks after catch -->
|
||||
<!-- https://checkstyle.sourceforge.io/checks/blocks/emptycatchblock.html -->
|
||||
<module name="EmptyCatchBlock">
|
||||
<property name="exceptionVariableName" value="expected|ignored"/>
|
||||
</module>
|
||||
|
||||
<module name="TreeWalker">
|
||||
<!-- 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)` allowed — use a logger."/>
|
||||
</module>
|
||||
|
||||
|
||||
<!-- WARNING — Style & Conventions -->
|
||||
<!-- Violations against agreed conventions that reduce readability and maintainability. -->
|
||||
<module name="EmptyLineSeparator">
|
||||
<property name="severity" value="warning"/>
|
||||
<!-- 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,
|
||||
@@ -42,17 +71,10 @@
|
||||
"/>
|
||||
</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"/>
|
||||
|
||||
<!-- No unordered / ungrouped imports -->
|
||||
<!-- https://checkstyle.sourceforge.io/checks/imports/importorder.html -->
|
||||
<module name="ImportOrder">
|
||||
<property name="severity" value="warning"/>
|
||||
<property name="option" value="top"/>
|
||||
<property name="groups" value="/^import static\..+/,*"/>
|
||||
<property name="separated" value="true"/>
|
||||
@@ -62,33 +84,94 @@
|
||||
|
||||
<!-- Classes, enums, records, ... as PascalCase -->
|
||||
<!-- https://checkstyle.sourceforge.io/checks/naming/typename.html -->
|
||||
<module name="TypeName"/>
|
||||
<module name="TypeName">
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<!-- Method names as camelCase -->
|
||||
<!-- https://checkstyle.sourceforge.io/checks/naming/methodname.html -->
|
||||
<module name="MethodName"/>
|
||||
<module name="MethodName">
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<!-- Parameter names as camelCase -->
|
||||
<!-- https://checkstyle.sourceforge.io/checks/naming/parametername.html -->
|
||||
<module name="ParameterName"/>
|
||||
<module name="ParameterName">
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<!-- Lokal variables as camelCase -->
|
||||
<!-- https://checkstyle.sourceforge.io/checks/naming/localvariablename.html -->
|
||||
<module name="LocalVariableName"/>
|
||||
<module name="LocalVariableName">
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<!-- Constants as UPPER_SNAKE_CASE -->
|
||||
<!-- https://checkstyle.sourceforge.io/checks/naming/constantname.html -->
|
||||
<module name="ConstantName"/>
|
||||
<module name="ConstantName">
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<!-- Field names as camelCase -->
|
||||
<!-- https://checkstyle.sourceforge.io/checks/naming/membername.html -->
|
||||
<module name="MemberName">
|
||||
<property name="severity" value="warning"/>
|
||||
<property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
|
||||
</module>
|
||||
|
||||
<!-- Modifier-Reihenfolge: public static final ... -->
|
||||
<!-- https://checkstyle.sourceforge.io/checks/modifier/modifierorder.html -->
|
||||
<module name="ModifierOrder">
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<!-- Require braces arround code block (no single instruction after if/else/while/...)-->
|
||||
<!-- https://checkstyle.sourceforge.io/checks/blocks/needbraces.html -->
|
||||
<module name="NeedBraces">
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<!-- Require the left curly braces at the same line -->
|
||||
<!-- https://checkstyle.sourceforge.io/checks/blocks/leftcurly.html -->
|
||||
<module name="LeftCurly">
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<!-- Require right curly braces at the same line as following instructions -->
|
||||
<!-- https://checkstyle.sourceforge.io/checks/blocks/rightcurly.html -->
|
||||
<module name="RightCurly">
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<!-- Disallow whitespace before semicolon or bracket -->
|
||||
<!-- https://checkstyle.sourceforge.io/checks/whitespace/nowhitespacebefore.html -->
|
||||
<module name="NoWhitespaceBefore">
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<!-- Disallow whitespace after code -->
|
||||
<!-- https://checkstyle.sourceforge.io/checks/whitespace/whitespaceafter.html -->
|
||||
<module name="WhitespaceAfter">
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<!-- No trailing whitespace at line ending -->
|
||||
<!-- https://checkstyle.sourceforge.io/checks/regexp/regexp.html -->
|
||||
<module name="Regexp">
|
||||
<property name="severity" value="warning"/>
|
||||
<property name="format" value=" +$"/>
|
||||
<property name="illegalPattern" value="true"/>
|
||||
<property name="message" value="No trailing whitespace."/>
|
||||
</module>
|
||||
|
||||
|
||||
<!-- INFO — Complexity Limits -->
|
||||
<!-- Metrics that serve as guidance but may legitimately be exceeded depending on context. -->
|
||||
|
||||
<!-- Restrict length of method name -->
|
||||
<!-- https://checkstyle.sourceforge.io/checks/sizes/methodlength.html -->
|
||||
<module name="MethodLength">
|
||||
<property name="severity" value="info"/>
|
||||
<property name="max" value="60"/>
|
||||
<property name="countEmpty" value="false"/>
|
||||
</module>
|
||||
@@ -96,75 +179,36 @@
|
||||
<!-- Restrict number of parameters -->
|
||||
<!-- https://checkstyle.sourceforge.io/checks/sizes/parameternumber.html -->
|
||||
<module name="ParameterNumber">
|
||||
<property name="severity" value="info"/>
|
||||
<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="severity" value="info"/>
|
||||
<property name="ignoreNumbers" value="-1, 0, 1, 2"/>
|
||||
<property name="ignoreAnnotation" value="true"/>
|
||||
</module>
|
||||
|
||||
<!-- Disallow empty code blocks -->
|
||||
<!-- https://checkstyle.sourceforge.io/checks/blocks/emptyblock.html -->
|
||||
<module name="EmptyBlock">
|
||||
<property name="severity" value="info"/>
|
||||
<property name="option" value="text"/>
|
||||
</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="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)` allowed — 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="No trailing whitespace."/>
|
||||
<module name="AvoidNestedBlocks">
|
||||
<property name="severity" value="info"/>
|
||||
</module>
|
||||
</module>
|
||||
|
||||
<!-- Maximale Zeilenlänge (AOSP enforces 100 lines, the Google Java Style Guide 80)-->
|
||||
<!-- https://checkstyle.sourceforge.io/checks/sizes/linelength.html -->
|
||||
<module name="LineLength">
|
||||
<property name="severity" value="info"/>
|
||||
<property name="max" value="100"/>
|
||||
</module>
|
||||
</module>
|
||||
@@ -1,63 +0,0 @@
|
||||
This Document states the Network-Protocol as it is currently implemented
|
||||
|
||||
## GET_MESSAGE_COUNT
|
||||
This Command gets the number of messages, currently stored in the queue for the specific client.
|
||||
(Is used together with GET_NEXT_MESSAGE, to get all messages that are currently in the queue)
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
GET_MESSAGE_COUNT
|
||||
|
||||
1
|
||||
+OK
|
||||
```
|
||||
|
||||
## GET_NEXT_MESSAGE
|
||||
This Command gets the next Message that is being stored in the queue for the client.
|
||||
(Command is being sent the amount of times, the GET_MESSAGE_COUNT Command returns)
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
GET_NEXT_MESSAGE
|
||||
|
||||
TYPE=LOBBY GAME=1 USER=player1 TARGET=null TIME=9:30 TEXT="Guten Tag"
|
||||
+OK
|
||||
```
|
||||
|
||||
## SEND_MESSAGE
|
||||
This Command gets sent if the user of that client writes a message to one of the three possible chats
|
||||
|
||||
(The Arguments / Parameters of the Command describe all the parameters of the Object "Message" being used internally by both the Client and the Server)
|
||||
|
||||
Example:
|
||||
```
|
||||
SEND_MESSAGE TYPE=LOBBY GAME=1 USER=player1 TARGET=null TIME=10:30 TEXT="Hallo Welt"
|
||||
|
||||
+OK
|
||||
```
|
||||
|
||||
## LOG_IN
|
||||
This Command is used to create a user on the server and associate that user with a username
|
||||
Server returns the username, slightly changed if it is already used by someone else, and an ID to identify the client.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
LOG_IN USERNAME="Peter"
|
||||
|
||||
USERNAME="Peter" ID=<random UUID>
|
||||
+OK
|
||||
```
|
||||
|
||||
## LOG_OUT
|
||||
This Command is used to quit the connection between client and server.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
LOG_OUT
|
||||
|
||||
+OK
|
||||
```
|
||||
@@ -1,249 +0,0 @@
|
||||
# Client Nework Architecture
|
||||
<!-- vim-markdown-toc GFM -->
|
||||
|
||||
* [Architecture Overview](#architecture-overview)
|
||||
* [network/Card.java](#networkcardjava)
|
||||
* [network/GameState.java](#networkgamestatejava)
|
||||
* [network/Player.java](#networkplayerjava)
|
||||
* [network/ChatClient.java](#networkchatclientjava)
|
||||
* [ChatClient(ClientService clientService)](#chatclientclientservice-clientservice)
|
||||
* [sendMessage(Message message)](#sendmessagemessage-message)
|
||||
* [getMessages()](#getmessages)
|
||||
* [network/ClientService.java](#networkclientservicejava)
|
||||
* [ClientService(String ip, int port)](#clientservicestring-ip-int-port)
|
||||
* [processCommand(String message)](#processcommandstring-message)
|
||||
* [sendRequest(Runnable request)](#sendrequestrunnable-request)
|
||||
* [getRuntimeException(Exception e)](#getruntimeexceptionexception-e)
|
||||
* [closeSocket()](#closesocket)
|
||||
* [writeToTransport(String s) throws IOException](#writetotransportstring-s-throws-ioexception)
|
||||
* [network/CoreClient.java](#networkcoreclientjava)
|
||||
* [CoreClient(ClientService clientservice)](#coreclientclientservice-clientservice)
|
||||
* [ping()](#ping)
|
||||
* [login(String user)](#loginstring-user)
|
||||
* [network/GameClient.java](#networkgameclientjava)
|
||||
* [GameClient(ClientService client)](#gameclientclientservice-client)
|
||||
* [getGameState()](#getgamestate)
|
||||
* [parseGameState(String input)](#parsegamestatestring-input)
|
||||
* [Example server response](#example-server-response)
|
||||
* [network/LobbyClient.java](#networklobbyclientjava)
|
||||
* [LobbyClient(ClientService client)](#lobbyclientclientservice-client)
|
||||
* [fetchLobbyStatusString(int lobbyId)](#fetchlobbystatusstringint-lobbyid)
|
||||
* [createLobby()](#createlobby)
|
||||
* [getLobbyId()](#getlobbyid)
|
||||
* [joinLobby(int lobbyId)](#joinlobbyint-lobbyid)
|
||||
|
||||
<!-- vim-markdown-toc -->
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```text
|
||||
client/
|
||||
├── game/
|
||||
│ ├── Card.java
|
||||
│ ├── GameState.java
|
||||
│ └── Player.java
|
||||
│
|
||||
└── network/
|
||||
├── ChatClient.java
|
||||
├── ClientService.java
|
||||
├── CoreClient.java
|
||||
├── GameClient.java
|
||||
└── LobbyClient.java
|
||||
```
|
||||
|
||||
### game/Card.java
|
||||
|
||||
Represents a playing card with a value and suit.
|
||||
|
||||
### game/GameState.java
|
||||
|
||||
Represents the current state of the poker game, including the phase, pot size, current bet, dealer position, active player, community cards, and player information.
|
||||
|
||||
### game/Player.java
|
||||
|
||||
Represents a player in the poker game, including their name, chip count, current bet, state (e.g., `active`, `folded`), and their hole cards.
|
||||
|
||||
### network/ChatClient.java
|
||||
|
||||
The ChatClient class is responsible for sending messages to the server and retrieving messages from the server. It uses the ClientService to send commands and receive responses from the server.
|
||||
|
||||
#### ChatClient(ClientService clientService)
|
||||
|
||||
Constructs a ChatClient with the given ClientService for communication.
|
||||
|
||||
- **Parameter (`clientService`)**: The ClientService instance used to send commands and receive responses from the server.
|
||||
|
||||
#### sendMessage(Message message)
|
||||
|
||||
Send a Message to the server by converting it to a string format and sending a `SEND_MESSAGE` command with the message content as arguments.
|
||||
|
||||
- **Parameter (`message`)**: message The Message object to be sent to the server.
|
||||
|
||||
#### getMessages()
|
||||
|
||||
Retrieve messages from the server by first sending a `GET_MESSAGE_COUNT` command to determine how many messages are available and then sending `GET_NEXT_MESSAGE` commands in a loop to retrieve each message. The retrieved messages are parsed into Message objects and returned as a list.
|
||||
|
||||
- **Parameter (`A`)**: list of Message objects representing the messages retrieved from the server.
|
||||
|
||||
### network/ClientService.java
|
||||
|
||||
The ClientService class is responsible for managing the connection to the server, sending commands, and receiving responses. It uses a TcpTransport to
|
||||
communicate with the server and an ExecutorService to handle asynchronous requests.
|
||||
|
||||
#### ClientService(String ip, int port)
|
||||
|
||||
Constructs a ClientService with the given server IP and port. It establishes a socket connection to the server and initializes the TcpTransport and ExecutorService for communication.
|
||||
|
||||
- **Parameter (`ip`)**: The IP address of the server to connect to.
|
||||
- **Parameter (`port`)**: The port number of the server to connect to.
|
||||
|
||||
#### processCommand(String message)
|
||||
|
||||
Sends a command to the server and waits for the response. The command is sent using the TcpTransport, and the response is read in a loop until a valid response is received. The method handles `+OK` and `-ERROR` responses from the server and returns the actual response content.
|
||||
|
||||
- **Parameter (`message`)**: The command message to be sent to the server.
|
||||
- **Return**: The response from the server as a string.
|
||||
|
||||
#### sendRequest(Runnable request)
|
||||
|
||||
Helper method to send a request to the server using the ExecutorService. It submits the request as a Runnable task and waits for its completion. If the task is interrupted or encounters an execution exception, it throws a
|
||||
RuntimeException with the appropriate cause.
|
||||
|
||||
- **Parameter (`request`)**: The Runnable task representing the request to be sent to the server.
|
||||
|
||||
#### getRuntimeException(Exception e)
|
||||
|
||||
Helper method to extract the cause of an exception and return it as a RuntimeException. If the cause is null, it returns the original exception as a RuntimeException. If the cause is already a RuntimeException, it returns it directly. Otherwise, it wraps the cause in a new RuntimeException and returns it.
|
||||
|
||||
- **Parameter (`e`)**: The exception from which to extract the cause.
|
||||
- **Return**: A RuntimeException representing the cause of the original exception.
|
||||
|
||||
#### closeSocket()
|
||||
|
||||
Closes the socket connection to the server and shuts down the ExecutorService. It also closes the TcpTransport used for communication. If any IOException occurs during this process, it prints the exception to the console.
|
||||
|
||||
#### writeToTransport(String s) throws IOException
|
||||
|
||||
Helper method to write a command string to the TcpTransport. It generates a unique ID for the command using the idGenerator and sends a RawPacket containing the ID and the command string to the server. If an IOException occurs during this process, it throws a RuntimeException with the cause.
|
||||
|
||||
- **Parameter (`s`)**: The command string to be sent to the server.
|
||||
- **Throws IOException**: If an I/O error occurs while writing to the transport.
|
||||
|
||||
### network/CoreClient.java
|
||||
|
||||
The CoreClient class provides basic functionalities for communicating with the server, such as sending a ping command to check connectivity and logging in with a username. It uses the ClientService to send commands and receive responses from the server.
|
||||
|
||||
#### CoreClient(ClientService clientservice)
|
||||
|
||||
Constructs a CoreClient with the given ClientService for communication.
|
||||
|
||||
- **Parameter (`clientservice`)**: The ClientService instance used to send commands and receive responses from the server.
|
||||
|
||||
#### ping()
|
||||
|
||||
Sends a `PING` command to the server to check connectivity. The server should respond with a `PONG` message if the connection is successful.
|
||||
|
||||
#### login(String user)
|
||||
|
||||
Logs in to the server with the given username by sending a `LOGIN` command.
|
||||
|
||||
- **Parameter (`user`)**: The username to log in with.
|
||||
|
||||
### network/GameClient.java
|
||||
|
||||
The GameClient class is responsible for communicating with the server to retrieve the current game state. It sends a command to the server and parses the response into a structured GameState object.
|
||||
|
||||
#### GameClient(ClientService client)
|
||||
|
||||
Constructs a GameClient with the given ClientService for communication.
|
||||
|
||||
- **Parameter (`client`)**: The ClientService instance used to send commands and receive responses from the server.
|
||||
|
||||
#### getGameState()
|
||||
|
||||
Retrieves the current game state from the server by sending a command and parsing the response.
|
||||
|
||||
- **Return**: A GameState object representing the current state of the game.
|
||||
|
||||
#### parseGameState(String input)
|
||||
|
||||
Parses the raw response from the server into a structured GameState object.
|
||||
|
||||
- **Parameter (`input`)**: The raw response string from the server.
|
||||
- **Return**: A GameState object representing the current state of the game.
|
||||
|
||||
#### Example server response
|
||||
|
||||
```text
|
||||
+OK
|
||||
PHASE=FLO P
|
||||
POT=150
|
||||
CURRENT_BET=50
|
||||
DEALER=0
|
||||
ACTIVE_PLAYER=1
|
||||
CARDS
|
||||
CARD
|
||||
VALUE=10
|
||||
SUIT=H
|
||||
CARD
|
||||
VALUE=7
|
||||
SUIT=S
|
||||
CARD
|
||||
VALUE=A
|
||||
SUIT=D
|
||||
PLAYERS
|
||||
PLAYER
|
||||
NAME=Max
|
||||
CHIPS=1200
|
||||
BET=50
|
||||
STATE=ACTIVE
|
||||
CARDS
|
||||
CARD
|
||||
VALUE=K
|
||||
SUIT=H
|
||||
CARD
|
||||
VALUE=3
|
||||
SUIT=C
|
||||
PLAYER
|
||||
NAME=Anna
|
||||
CHIPS=800
|
||||
BET=0
|
||||
STATE=FOLDED
|
||||
CARDS
|
||||
END
|
||||
```
|
||||
|
||||
### network/LobbyClient.java
|
||||
|
||||
The LobbyClient class is responsible for communicating with the server to manage game lobbies. It provides methods to create a lobby, join a lobby, and fetch the current status of a lobby by sending appropriate commands to the server and processing the responses.
|
||||
|
||||
#### LobbyClient(ClientService client)
|
||||
|
||||
Constructs a LobbyClient with the given ClientService for communication.
|
||||
|
||||
- **Parameter (`client`)**: The ClientService instance used to send commands and receive responses from the server.
|
||||
|
||||
#### fetchLobbyStatusString(int lobbyId)
|
||||
|
||||
Fetch the current status of the lobby with the given id from the server.
|
||||
|
||||
- **Parameter (`lobbyId`)**: The id of the lobby to fetch the status for.
|
||||
- **Return**: A string representing the current status of the lobby, as returned by the server.
|
||||
|
||||
#### createLobby()
|
||||
|
||||
Request the server to create a new lobby and return the id of the newly created lobby.
|
||||
|
||||
- **Return**: The id of the newly created lobby, as returned by the server.
|
||||
|
||||
#### getLobbyId()
|
||||
|
||||
Request the server to return the id of the lobby that the client is currently in.
|
||||
|
||||
- **Return**: The id of the lobby that the client is currently in, as returned by the server.
|
||||
|
||||
#### joinLobby(int lobbyId)
|
||||
|
||||
Request the server to join the lobby with the given id.
|
||||
|
||||
- **Parameter (`lobbyId`)**: The id of the lobby to join.
|
||||
@@ -10,15 +10,15 @@ import org.apache.logging.log4j.Logger;
|
||||
/**
|
||||
* Entry point for the Casono client application. Handles client startup and connection parameters.
|
||||
*
|
||||
* <p>Default constructor for the application.
|
||||
* <p>Standardkonstruktor für die Anwendung.
|
||||
*/
|
||||
public class ClientApp {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(ClientApp.class);
|
||||
|
||||
/** Default constructor. */
|
||||
/** Standardkonstruktor. */
|
||||
public ClientApp() {
|
||||
// Default constructor
|
||||
// Standardkonstruktor
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,12 +36,6 @@ public class ClientApp {
|
||||
int port = Integer.parseInt(parts[1]);
|
||||
|
||||
LOGGER.info("You've selected the client. It will connect port {} at host {}", port, host);
|
||||
// Expose the chosen host/port to the UI via system properties so controllers
|
||||
// (which read System.getProperty("casono.server.host"/"casono.server.port"))
|
||||
// can obtain the correct connection information.
|
||||
System.setProperty("casono.server.host", host);
|
||||
System.setProperty("casono.server.port", Integer.toString(port));
|
||||
// Forward the original address argument to the launcher as well.
|
||||
Launcher.main(new String[] { arg });
|
||||
Launcher.main(new String[] {});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.chat;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.ChatClient;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* responsible for the transferring of messages from the server to the ChatModel
|
||||
* or from the ChatViewController to the server
|
||||
*/
|
||||
public class ChatController {
|
||||
|
||||
private final String username;
|
||||
private final ClientService clientService;
|
||||
private final ChatClient chatClient;
|
||||
|
||||
private ChatModel chatModel;
|
||||
|
||||
private int game_id;
|
||||
|
||||
public ChatController(String username, ClientService clientService) {
|
||||
this.username = username;
|
||||
this.clientService = clientService;
|
||||
this.chatClient = new ChatClient(this.clientService);
|
||||
}
|
||||
|
||||
public void createChat(int game_id) {
|
||||
chatModel = new ChatModel(ChatModel.ChatType.GLOBAL, username);
|
||||
this.game_id = game_id;
|
||||
}
|
||||
|
||||
public ChatModel getChatModel() {
|
||||
return chatModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* method to send a message, the ChatViewController received to the server
|
||||
*/
|
||||
public void sendMessage(String msg, String username) {
|
||||
Message message = new Message(Message.MessageType.GLOBAL, 0, username, null, msg);
|
||||
onSendToNetwork(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* method to get all messages from the server
|
||||
*/
|
||||
public Boolean receiveMessage() {
|
||||
List<Message> newMessages = chatClient.getMessages();
|
||||
if (!newMessages.isEmpty()) {
|
||||
for (Message msg : newMessages) {
|
||||
chatModel.addMessage(msg);
|
||||
}
|
||||
return true;
|
||||
} else { return false; }
|
||||
}
|
||||
|
||||
/**
|
||||
* method to send a message to the server
|
||||
* @param message
|
||||
*/
|
||||
public void onSendToNetwork(Message message) {
|
||||
chatClient.sendMessage(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.chat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* ChatModel, stores the data for a specific chat
|
||||
*
|
||||
* Holds the current state of a chat
|
||||
*/
|
||||
|
||||
public class ChatModel {
|
||||
|
||||
public ArrayList<Message> messages;
|
||||
|
||||
public ChatType chattype;
|
||||
|
||||
public String username;
|
||||
|
||||
public int count;
|
||||
|
||||
public enum ChatType {
|
||||
GLOBAL,
|
||||
LOBBY,
|
||||
WHISPER
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ChatModel, given a username of the client
|
||||
* @param chattype
|
||||
* @param username
|
||||
*/
|
||||
|
||||
public ChatModel(ChatType chattype, String username) {
|
||||
this.messages = new ArrayList<Message>();
|
||||
this.chattype = chattype;
|
||||
this.username = username;
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* method, used by the ChatViewController, to access all new messages, that are stored in the ChatModel
|
||||
*/
|
||||
public synchronized String viewNextMessage() {
|
||||
count--;
|
||||
Message msg = messages.getLast();
|
||||
return String.format("[%s] %s: %s", msg.timestamp, msg.user, msg.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new message
|
||||
* method used by the ChatController
|
||||
* @param msg
|
||||
*/
|
||||
public synchronized void addMessage(Message msg) {
|
||||
messages.add(msg);
|
||||
count++;
|
||||
}
|
||||
|
||||
/**
|
||||
* method to send all current messages to the ChatViewController, if needed
|
||||
*/
|
||||
public void addCompleteChat() {
|
||||
for (int i = 0; i < this.messages.size(); i++) {
|
||||
Message msg = this.messages.get(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.chat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Message Object for internal handling of Chat-Messages
|
||||
* TODO: Should be used on both sides of the network
|
||||
*/
|
||||
|
||||
public class Message {
|
||||
private final MessageType type;
|
||||
private final String message;
|
||||
public String user;
|
||||
public String timestamp;
|
||||
public int game_id = 0;
|
||||
public String target = null;
|
||||
public enum MessageType {
|
||||
GLOBAL, LOBBY, WHISPER
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor for creating Messages with all information given
|
||||
* @param type - Either global, local or whisper
|
||||
* @param game_id - lobby id, or null, if the type is global
|
||||
* @param user - username
|
||||
* @param target - username of the target user, for whisper chat
|
||||
* @param message
|
||||
*/
|
||||
|
||||
public Message(MessageType type, int game_id, String user, String target, String timestamp, String message) {
|
||||
this.type = type;
|
||||
this.game_id = game_id;
|
||||
this.user = user;
|
||||
this.target = target;
|
||||
this.timestamp = timestamp;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for creating the Messages of the current user, using this client -> time of writing is being recorded
|
||||
* @param type - Either global, local or whisper
|
||||
* @param game_id - lobby id, or null, if the type is global
|
||||
* @param user - username
|
||||
* @param target - username of the target user, for whisper chat
|
||||
* @param message
|
||||
*/
|
||||
|
||||
public Message(MessageType type, int game_id, String user, String target, String message) {
|
||||
this.type = type;
|
||||
this.game_id = game_id;
|
||||
this.user = user;
|
||||
this.target = target;
|
||||
this.message = message;
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");
|
||||
this.timestamp = now.format(formatter);
|
||||
}
|
||||
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public MessageType getMessageType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/*
|
||||
* Method to test the system
|
||||
* @return - String representation of the Message instance, as for example "player1: Hello World"
|
||||
|
||||
public String toString() {
|
||||
return String.format("%s: %s", this.user, this.message);
|
||||
}
|
||||
*/
|
||||
/**
|
||||
* Method to create the request representation of the message object, to be sent to the server
|
||||
* @return - request as specified in the network protocol, as String
|
||||
*/
|
||||
public String toArgsString() {
|
||||
return String.format("TYPE=%s GAME=%d USER=%s TARGET=%s TIME=%s TEXT=%s",
|
||||
this.type.toString(), this.game_id, this.user, this.target, this.timestamp, this.message);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pattern, to analyze the response String with the given parameters
|
||||
*/
|
||||
public static Pattern msgRex = Pattern.compile(
|
||||
"TYPE=(?<type>\\w+) GAME=(?<game>\\w+) USER=(?<user>\\w+) TARGET=(?<target>\\w+) TIME=(?<time>[0-9:.]+) TEXT=(?<text>.*)$");
|
||||
|
||||
/**
|
||||
* Method to create a Message Object, from the information given by the String
|
||||
* @param response - String that got sent as a response from the server
|
||||
* @return - New Message Object
|
||||
*/
|
||||
public static Message toMessage(String response) {
|
||||
Matcher m = msgRex.matcher(response);
|
||||
if (! m.matches()) {
|
||||
throw new RuntimeException("Can not parse message: '" + response+"'");
|
||||
}
|
||||
String typeString=m.group("type");
|
||||
|
||||
switch (typeString) {
|
||||
case "GLOBAL":
|
||||
return new Message(MessageType.GLOBAL,
|
||||
0,
|
||||
m.group("user"),
|
||||
null,
|
||||
m.group("time"),
|
||||
m.group("text"));
|
||||
|
||||
case "LOBBY":
|
||||
return new Message(MessageType.LOBBY,
|
||||
Integer.parseInt(m.group("game")),
|
||||
m.group("user"),
|
||||
null,
|
||||
m.group("time"),
|
||||
m.group("text"));
|
||||
|
||||
case "WHISPER":
|
||||
return new Message(MessageType.WHISPER,
|
||||
Integer.parseInt(m.group("game")),
|
||||
m.group("user"),
|
||||
m.group("target"),
|
||||
m.group("time"),
|
||||
m.group("text"));
|
||||
|
||||
default:
|
||||
throw new RuntimeException("Unknown message type " + typeString);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.game;
|
||||
|
||||
/**
|
||||
* Represents a playing card with a value and suit.
|
||||
*/
|
||||
public class Card {
|
||||
public String value;
|
||||
public String suit;
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.game;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.Card;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents the current state of the poker game, including the phase, pot
|
||||
* size, current bet, dealer position,
|
||||
* active player, community cards, and player information.
|
||||
*/
|
||||
public class GameState {
|
||||
public String phase;
|
||||
public int pot;
|
||||
public int currentBet;
|
||||
public int dealer;
|
||||
public int activePlayer;
|
||||
|
||||
public List<Card> communityCards = new ArrayList<>();
|
||||
public List<Player> players = new ArrayList<>();
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.game;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a player in the poker game, including their name, chip count,
|
||||
* current bet, state (e.g., "active", "folded"), and their hole cards.
|
||||
*/
|
||||
public class Player {
|
||||
public String name;
|
||||
public int chips;
|
||||
public int bet;
|
||||
public String state;
|
||||
|
||||
public List<Card> cards = new ArrayList<>();
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.network;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The ChatClient class is responsible for sending messages to the server and
|
||||
* retrieving messages from the server. It uses the ClientService to send
|
||||
* commands and receive responses from the server.
|
||||
*/
|
||||
public class ChatClient {
|
||||
|
||||
private ClientService clientService;
|
||||
|
||||
/**
|
||||
* Constructs a ChatClient with the given ClientService for communication.
|
||||
*
|
||||
* @param clientService The ClientService instance used to send commands and
|
||||
* receive responses from the server.
|
||||
*/
|
||||
public ChatClient(ClientService clientService) {
|
||||
this.clientService = clientService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a Message to the server by converting it to a string format and
|
||||
* sending a "SEND_MESSAGE" command with the message content as arguments.
|
||||
*
|
||||
* @param message The Message object to be sent to the server.
|
||||
*/
|
||||
public void sendMessage(Message message) {
|
||||
String request = "SEND_MESSAGE " + message.toArgsString();
|
||||
clientService.processCommand(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve messages from the server by first sending a "GET_MESSAGE_COUNT"
|
||||
* command to determine how many messages are available and then sending
|
||||
* "GET_NEXT_MESSAGE" commands in a loop to retrieve each message. The
|
||||
* retrieved messages are parsed into Message objects and returned as a list.
|
||||
*
|
||||
* @return A list of Message objects representing the messages retrieved from
|
||||
* the server.
|
||||
*/
|
||||
public List<Message> getMessages() {
|
||||
String countStr = clientService.processCommand("GET_MESSAGE_COUNT");
|
||||
int count = Integer.parseInt(countStr);
|
||||
System.out.println("Got " + count + " messages");
|
||||
List<Message> messages = new ArrayList<>();
|
||||
for (int i = 0; i < count; i++) {
|
||||
String message = clientService.processCommand("GET_NEXT_MESSAGE");
|
||||
if (message != null) {
|
||||
Message message1 = Message.toMessage(message);
|
||||
messages.add(message1);
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.network;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.RawPacket;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.TcpTransport;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* The ClientService class is responsible for managing the connection to the
|
||||
* server,
|
||||
* sending commands, and receiving responses. It uses a TcpTransport to
|
||||
* communicate
|
||||
* with the server and an ExecutorService to handle asynchronous requests.
|
||||
*/
|
||||
public class ClientService {
|
||||
|
||||
private final TcpTransport clienttcptransport;
|
||||
private final Socket socket;
|
||||
|
||||
private final ExecutorService executor;
|
||||
private final boolean offlineMode;
|
||||
|
||||
public static ArrayList<String> response;
|
||||
private final AtomicInteger idGenerator;
|
||||
|
||||
/**
|
||||
* Constructs a ClientService with the given server IP and port. It establishes
|
||||
* a socket connection to the server and initializes the TcpTransport and
|
||||
* ExecutorService for communication.
|
||||
*
|
||||
* @param ip The IP address of the server to connect to.
|
||||
* @param port The port number of the server to connect to.
|
||||
*/
|
||||
public ClientService(String ip, int port) {
|
||||
|
||||
this.idGenerator = new AtomicInteger(0);
|
||||
|
||||
this.offlineMode = false;
|
||||
try {
|
||||
socket = new Socket(ip, port);
|
||||
clienttcptransport = new TcpTransport(socket);
|
||||
} catch (IOException i) {
|
||||
throw new RuntimeException(i);
|
||||
}
|
||||
|
||||
executor = Executors.newSingleThreadExecutor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a ClientService in offline mode. No network connection will be
|
||||
* attempted and calls to processCommand will throw a RuntimeException.
|
||||
*
|
||||
* @param offline true to create an offline (no-network) client service
|
||||
*/
|
||||
public ClientService(boolean offline) {
|
||||
this.idGenerator = new AtomicInteger(0);
|
||||
this.offlineMode = offline;
|
||||
this.socket = null;
|
||||
this.clienttcptransport = null;
|
||||
this.executor = Executors.newSingleThreadExecutor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this ClientService is running in offline mode (no network).
|
||||
*/
|
||||
public boolean isOffline() {
|
||||
return offlineMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a command to the server and waits for the response. The command is
|
||||
* sent using the TcpTransport, and the response is read in a loop until a
|
||||
* valid response is received. The method handles "+OK" and "-ERROR" responses
|
||||
* from the server and returns the actual response content.
|
||||
*
|
||||
* @param message The command message to be sent to the server.
|
||||
* @return The response from the server as a string.
|
||||
*/
|
||||
protected String processCommand(String message) {
|
||||
if (offlineMode) {
|
||||
throw new RuntimeException("ClientService is offline: cannot process command");
|
||||
}
|
||||
AtomicReference<String> response = new AtomicReference<>();
|
||||
sendRequest(() -> {
|
||||
try {
|
||||
writeToTransport(message);
|
||||
String responseLine = null;
|
||||
do {
|
||||
responseLine = clienttcptransport.read().payload();
|
||||
System.out.println("Raw message '" + responseLine + "'");
|
||||
if ("+OK".equals(responseLine)) {
|
||||
return;
|
||||
} else if (("-ERROR").equals(responseLine)) {
|
||||
throw new RuntimeException(responseLine);
|
||||
}
|
||||
response.set(responseLine);
|
||||
} while (true);
|
||||
} catch (Exception e) {
|
||||
throw getRuntimeException(e);
|
||||
}
|
||||
});
|
||||
return response.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to send a request to the server using the ExecutorService. It
|
||||
* submits the request as a Runnable task and waits for its completion. If
|
||||
* the task is interrupted or encounters an execution exception, it throws a
|
||||
* RuntimeException with the appropriate cause.
|
||||
*
|
||||
* @param request The Runnable task representing the request to be sent to the
|
||||
* server.
|
||||
*/
|
||||
private void sendRequest(Runnable request) {
|
||||
Future<?> future = executor.submit(request);
|
||||
try {
|
||||
future.get();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (ExecutionException e) {
|
||||
throw getRuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to extract the cause of an exception and return it as a
|
||||
* RuntimeException. If the cause is null, it returns the original exception as
|
||||
* a RuntimeException. If the cause is already a RuntimeException, it returns
|
||||
* it directly. Otherwise, it wraps the cause in a new RuntimeException and
|
||||
* returns it.
|
||||
*
|
||||
* @param e The exception from which to extract the cause.
|
||||
* @return A RuntimeException representing the cause of the original exception.
|
||||
*/
|
||||
private static RuntimeException getRuntimeException(Exception e) {
|
||||
Throwable reason = e.getCause();
|
||||
RuntimeException re;
|
||||
if (reason == null) {
|
||||
reason = e;
|
||||
} else if (reason instanceof RuntimeException rte) {
|
||||
re = rte;
|
||||
}
|
||||
re = new RuntimeException(reason);
|
||||
return re;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the socket connection to the server and shuts down the
|
||||
* ExecutorService.
|
||||
* It also closes the TcpTransport used for communication. If any IOException
|
||||
* occurs during this process, it prints the exception to the console.
|
||||
*/
|
||||
public void closeSocket() {
|
||||
try {
|
||||
executor.shutdown();
|
||||
if (clienttcptransport != null) {
|
||||
clienttcptransport.close();
|
||||
}
|
||||
if (socket != null) {
|
||||
socket.close();
|
||||
}
|
||||
} catch (IOException j) {
|
||||
System.out.println(j);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to write a command string to the TcpTransport. It generates a
|
||||
* unique ID for the command using the idGenerator and sends a RawPacket
|
||||
* containing the ID and the command string to the server. If an IOException
|
||||
* occurs during this process, it throws a RuntimeException with the cause.
|
||||
*
|
||||
* @param s The command string to be sent to the server.
|
||||
* @throws IOException If an I/O error occurs while writing to the transport.
|
||||
*/
|
||||
private void writeToTransport(String s) throws IOException {
|
||||
int id = this.idGenerator.incrementAndGet();
|
||||
this.clienttcptransport.write(new RawPacket(id, s));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.network;
|
||||
|
||||
/**
|
||||
* The CoreClient class provides basic functionalities for communicating with
|
||||
* the
|
||||
* server, such as sending a ping command to check connectivity and logging in
|
||||
* with a username. It uses the ClientService to send commands and receive
|
||||
* responses from the server.
|
||||
*/
|
||||
public class CoreClient {
|
||||
private final ClientService clientService;
|
||||
|
||||
/**
|
||||
* Constructs a CoreClient with the given ClientService for communication.
|
||||
*
|
||||
* @param clientservice The ClientService instance used to send commands and
|
||||
* receive responses from the server.
|
||||
*/
|
||||
public CoreClient(ClientService clientservice) {
|
||||
this.clientService = clientservice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a "PING" command to the server to check connectivity. The server
|
||||
* should respond with a "PONG" message if the connection is successful.
|
||||
*/
|
||||
public void ping() {
|
||||
clientService.processCommand("PING");
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs in to the server with the given username by sending a "LOGIN" command.
|
||||
*
|
||||
* @param user The username to log in with.
|
||||
*/
|
||||
public void login(String user) {
|
||||
clientService.processCommand("LOGIN USERNAME=" + user);
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.network;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.Card;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.GameState;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The GameClient class is responsible for communicating with the server to
|
||||
* retrieve the current game state. It sends a command to the server and
|
||||
* parses the response into a structured GameState object.
|
||||
*/
|
||||
public class GameClient {
|
||||
|
||||
private final ClientService client;
|
||||
|
||||
/**
|
||||
* Constructs a GameClient with the given ClientService for communication.
|
||||
*
|
||||
* @param client The ClientService instance used to send commands and receive
|
||||
* responses from the server.
|
||||
*/
|
||||
public GameClient(ClientService client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current game state from the server by sending a command and
|
||||
* parsing the response.
|
||||
*
|
||||
* @return A GameState object representing the current state of the game.
|
||||
*/
|
||||
public GameState getGameState() {
|
||||
String response = client.processCommand("GET_GAME_STATE");
|
||||
return parseGameState(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the raw response from the server into a structured GameState object.
|
||||
*
|
||||
* @param input The raw response string from the server.
|
||||
* @return A GameState object representing the current state of the game.
|
||||
*/
|
||||
private GameState parseGameState(String input) {
|
||||
|
||||
GameState state = new GameState();
|
||||
|
||||
String[] lines = input.split("\n");
|
||||
|
||||
Player currentPlayer = null;
|
||||
Card currentCard = null;
|
||||
|
||||
for (String rawLine : lines) {
|
||||
|
||||
String line = rawLine.trim();
|
||||
|
||||
if (line.startsWith("+OK") || line.equals("END")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith("PHASE=")) {
|
||||
state.phase = line.split("=")[1];
|
||||
}
|
||||
|
||||
else if (line.startsWith("POT=")) {
|
||||
state.pot = Integer.parseInt(line.split("=")[1]);
|
||||
}
|
||||
|
||||
else if (line.startsWith("CURRENT_BET=")) {
|
||||
state.currentBet = Integer.parseInt(line.split("=")[1]);
|
||||
}
|
||||
|
||||
else if (line.startsWith("DEALER=")) {
|
||||
state.dealer = Integer.parseInt(line.split("=")[1]);
|
||||
}
|
||||
|
||||
else if (line.startsWith("ACTIVE_PLAYER=")) {
|
||||
state.activePlayer = Integer.parseInt(line.split("=")[1]);
|
||||
}
|
||||
|
||||
else if (line.startsWith("PLAYER")) {
|
||||
currentPlayer = new Player();
|
||||
state.players.add(currentPlayer);
|
||||
}
|
||||
|
||||
else if (line.startsWith("NAME=") && currentPlayer != null) {
|
||||
currentPlayer.name = line.split("=")[1];
|
||||
}
|
||||
|
||||
else if (line.startsWith("CHIPS=") && currentPlayer != null) {
|
||||
currentPlayer.chips = Integer.parseInt(line.split("=")[1]);
|
||||
}
|
||||
|
||||
else if (line.startsWith("BET=") && currentPlayer != null) {
|
||||
currentPlayer.bet = Integer.parseInt(line.split("=")[1]);
|
||||
}
|
||||
|
||||
else if (line.startsWith("STATE=") && currentPlayer != null) {
|
||||
currentPlayer.state = line.split("=")[1];
|
||||
}
|
||||
|
||||
else if (line.startsWith("CARD")) {
|
||||
currentCard = new Card();
|
||||
|
||||
if (currentPlayer != null) {
|
||||
currentPlayer.cards.add(currentCard);
|
||||
} else {
|
||||
state.communityCards.add(currentCard);
|
||||
}
|
||||
}
|
||||
|
||||
else if (line.startsWith("VALUE=") && currentCard != null) {
|
||||
currentCard.value = line.split("=")[1];
|
||||
}
|
||||
|
||||
else if (line.startsWith("SUIT=") && currentCard != null) {
|
||||
currentCard.suit = line.split("=")[1];
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.network;
|
||||
|
||||
/**
|
||||
* The LobbyClient class is responsible for communicating with the server to
|
||||
* manage game lobbies. It provides methods to create a lobby, join a lobby,
|
||||
* and fetch the current status of a lobby by sending appropriate commands to
|
||||
* the server and processing the responses.
|
||||
*/
|
||||
public class LobbyClient {
|
||||
private final ClientService client;
|
||||
|
||||
/**
|
||||
* Constructs a LobbyClient with the given ClientService for communication.
|
||||
*
|
||||
* @param client The ClientService instance used to send commands and receive
|
||||
* responses from the server.
|
||||
*/
|
||||
public LobbyClient(ClientService client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public ClientService getClientService() {
|
||||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the current status of the lobby with the given id from the server.
|
||||
*
|
||||
* @param lobbyId The id of the lobby to fetch the status for.
|
||||
* @return A string representing the current status of the lobby, as returned
|
||||
* by the server.
|
||||
*/
|
||||
public String fetchLobbyStatusString(int lobbyId) {
|
||||
return client.processCommand("GET_LOBBY_STATUS ID=" + lobbyId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the server to create a new lobby and return the id of the newly
|
||||
* created lobby.
|
||||
*
|
||||
* @return The id of the newly created lobby, as returned by the server.
|
||||
*/
|
||||
public int createLobby() {
|
||||
String response = client.processCommand("CREATE_LOBBY");
|
||||
return Integer.parseInt(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the server to return the id of the lobby that the client is
|
||||
* currently in.
|
||||
*
|
||||
* @return The id of the lobby that the client is currently in, as returned by
|
||||
* the server.
|
||||
*/
|
||||
public int getLobbyId() {
|
||||
String response = client.processCommand("GET_LOBBY_ID");
|
||||
return Integer.parseInt(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the server to join the lobby with the given id.
|
||||
*
|
||||
* @param lobbyId The id of the lobby to join.
|
||||
*/
|
||||
public void joinLobby(int lobbyId) {
|
||||
client.processCommand("JOIN_LOBBY ID=" + lobbyId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs in to the server with the given username by sending a "LOGIN" command.
|
||||
*
|
||||
* @param user The username to log in with.
|
||||
*/
|
||||
public void login(String user) {
|
||||
client.processCommand("LOGIN USERNAME=" + user);
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,13 @@ import javafx.application.Application;
|
||||
/**
|
||||
* Launcher for the Casono main UI.
|
||||
*
|
||||
* <p>Default constructor for the application.
|
||||
* <p>Standardkonstruktor für die Anwendung.
|
||||
*/
|
||||
public class Launcher {
|
||||
|
||||
/** Default constructor. */
|
||||
/** Standardkonstruktor. */
|
||||
public Launcher() {
|
||||
// Default constructor
|
||||
// Standardkonstruktor
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui;
|
||||
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
/**
|
||||
* Controller-Klasse für das Chat-System innerhalb der Spieloberfläche.
|
||||
*
|
||||
* <p>Verwaltet das Anzeigen von Chatnachrichten, das Eingabefeld für eigene Nachrichten sowie den
|
||||
* Senden-Button. Unterstützt drei Arten von Nachrichten: - Player-to-Player (Privat) - Lobby-Chat
|
||||
* (Raum) - Globaler Chat (Serverweit)
|
||||
*
|
||||
* <p>Nachrichten werden in einem {@link VBox}-Container als {@link Label} angezeigt. Eigene
|
||||
* Nachrichten werden über {@link #onSendToNetwork(String)} an das Netzwerkprotokoll weitergeleitet,
|
||||
* während eingehende Nachrichten über {@link #receiveMessage(String, String)} verarbeitet und
|
||||
* angezeigt werden.
|
||||
*
|
||||
* <p>Hinweis: Einige TODOs stehen in der zugehörigen FXML-Datei
|
||||
*/
|
||||
public class ChatController {
|
||||
|
||||
@FXML private VBox chatVBox;
|
||||
|
||||
@FXML private TextField inputField;
|
||||
|
||||
@FXML private Button sendButton;
|
||||
|
||||
@FXML private ScrollPane chatScrollPane;
|
||||
|
||||
private static final int CHAT_PADDING = 20;
|
||||
|
||||
/** Initialisiert den ChatController nach dem Laden der FXML. */
|
||||
public void initialize() {
|
||||
inputField.setOnAction(event -> sendMessage());
|
||||
chatScrollPane.vvalueProperty().bind(chatVBox.heightProperty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Diese Methode wird vom Senden-Button oder Enter ausgelöst. Sie gibt die eigene Nachricht an
|
||||
* das Netzwerkprotokoll weiter.
|
||||
*/
|
||||
@FXML
|
||||
private void sendMessage() {
|
||||
String message = inputField.getText().trim();
|
||||
if (!message.isEmpty()) {
|
||||
inputField.clear();
|
||||
|
||||
// Hier wird die eigene Nachricht ans Netzwerkprotokoll übergeben
|
||||
onSendToNetwork(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Diese Funktion muss vom Netzwerkprotokoll aufgerufen werden, wenn eine neue Nachricht von
|
||||
* einem anderen Spieler kommt.
|
||||
*
|
||||
* @param player Name des Spielers
|
||||
* @param message Nachricht des Spielers
|
||||
*/
|
||||
public void receiveMessage(String player, String message) {
|
||||
String time = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm"));
|
||||
Label label = new Label("[" + time + "] " + player + ": " + message);
|
||||
label.getStyleClass().add("info-text");
|
||||
label.setWrapText(true); // Zeilenumbruch aktivieren
|
||||
label.maxWidthProperty().bind(chatVBox.widthProperty().subtract(CHAT_PADDING));
|
||||
chatVBox.getChildren().add(label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schnittstelle zum Netzwerkprotokoll. Diese Funktion wird automatisch aufgerufen, wenn der
|
||||
* Benutzer eine eigene Nachricht sendet.
|
||||
*
|
||||
* @param message Nachricht, die der Benutzer abgeschickt hat
|
||||
*/
|
||||
public void onSendToNetwork(String message) {
|
||||
// TODO: Netzwerkcode einfügen
|
||||
receiveMessage("Du", message);
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatModel;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
/**
|
||||
* Responsible for the presentation of the ChatModel to the Client
|
||||
*/
|
||||
|
||||
public class ChatViewController {
|
||||
|
||||
private final ChatModel chatmodel;
|
||||
private final String username;
|
||||
private final ChatController controller;
|
||||
private final Timer timer;
|
||||
|
||||
@FXML
|
||||
private VBox chatVBox;
|
||||
|
||||
@FXML
|
||||
private TextField inputField;
|
||||
|
||||
@FXML
|
||||
private ScrollPane chatScrollPane;
|
||||
|
||||
@FXML
|
||||
private Button sendButton;
|
||||
|
||||
@FXML
|
||||
private Button refreshButton;
|
||||
|
||||
private static final int CHAT_PADDING = 20;
|
||||
|
||||
public ChatViewController() {
|
||||
this.username = null;
|
||||
this.chatmodel = null;
|
||||
this.controller = null;
|
||||
this.timer = new Timer();
|
||||
}
|
||||
|
||||
public ChatViewController(String username, ChatModel chatmodel, ChatController controller) {
|
||||
this.username = username;
|
||||
this.chatmodel = chatmodel;
|
||||
this.controller = controller;
|
||||
this.timer = new Timer();
|
||||
if (this.controller != null) {
|
||||
timer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
System.err.println("tick");
|
||||
if (ChatViewController.this.controller.receiveMessage()) {
|
||||
showMessage();
|
||||
}
|
||||
}
|
||||
}, 0, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
if (inputField != null) {
|
||||
inputField.setOnAction(event -> sendMessage());
|
||||
}
|
||||
if (sendButton != null) {
|
||||
sendButton.setOnAction(event -> sendMessage());
|
||||
}
|
||||
if (refreshButton != null) {
|
||||
refreshButton.setOnAction(event -> showMessage());
|
||||
}
|
||||
if (chatScrollPane != null && chatVBox != null) {
|
||||
chatScrollPane.vvalueProperty().bind(chatVBox.heightProperty());
|
||||
}
|
||||
}
|
||||
|
||||
public void showMessage() {
|
||||
while (chatmodel.count > 0) {
|
||||
String msg = chatmodel.viewNextMessage();
|
||||
Label label = new Label(msg);
|
||||
label.getStyleClass().add("info-text");
|
||||
label.setWrapText(true);
|
||||
label.maxWidthProperty().bind(chatVBox.widthProperty().subtract(CHAT_PADDING));
|
||||
chatVBox.getChildren().add(label);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void sendMessage() {
|
||||
String message = inputField.getText().trim();
|
||||
if (!message.isEmpty()) {
|
||||
inputField.clear();
|
||||
this.controller.sendMessage(message, username);
|
||||
}
|
||||
}
|
||||
|
||||
public void endController() {
|
||||
timer.cancel();
|
||||
}
|
||||
}
|
||||
@@ -15,22 +15,11 @@ import javafx.scene.layout.VBox;
|
||||
*/
|
||||
public class CasinoGameController {
|
||||
|
||||
/** Standardkonstruktor. Wird von FXML verwendet. */
|
||||
public CasinoGameController() {
|
||||
// default constructor for FXML
|
||||
}
|
||||
|
||||
@FXML private Label welcomeText;
|
||||
@FXML private VBox casinoTable;
|
||||
|
||||
// TODO: Test-Logik: wird durch echte Spielinteraktionen ersetzt,
|
||||
// sobald die GameEngine fertig ist
|
||||
|
||||
/**
|
||||
* Temporäre Test-Methode, die bei Klick auf den Tisch eine Platzhalteraktion ausführt.
|
||||
*
|
||||
* <p>Wird in der finalen Implementierung durch die Spiel-Logik ersetzt.
|
||||
*/
|
||||
@FXML
|
||||
public void onTableClick() {
|
||||
welcomeText.setText("Einsatz akzeptiert!");
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -8,48 +7,29 @@ import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
/**
|
||||
* Main class for the casino game UI.
|
||||
* Hauptklasse für das Casino-Spiel-UI.
|
||||
*
|
||||
* <p>Starts the JavaFX application, loads the graphical interface from the FXML file,
|
||||
* and initializes the main stage for the game.
|
||||
* <p>Startet die JavaFX-Anwendung, lädt die grafische Oberfläche aus der FXML-Datei und
|
||||
* initialisiert die Haupt-Stage für das Spiel.
|
||||
*
|
||||
* <p>Responsibilities:
|
||||
* - Loads the FXML interface "/ui-structure/Casinogameui.fxml".
|
||||
* - Loads the application icon from "/images/logoinverted.png".
|
||||
* - Starts the application in fullscreen mode.
|
||||
* <p>Aufgaben: - Lädt die FXML-Oberfläche "/ui-structure/Casinogameui.fxml". - Lädt das
|
||||
* Anwendungs-Icon aus "/images/logoinverted.png". - Startet die Anwendung im Vollbildmodus.
|
||||
*/
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService;
|
||||
|
||||
public class CasinoGameUI extends Application {
|
||||
|
||||
// Static field for ClientService (workaround for JavaFX Application launch)
|
||||
private static ClientService staticClientService;
|
||||
|
||||
public static void setClientService(ClientService clientService) {
|
||||
staticClientService = clientService;
|
||||
}
|
||||
|
||||
public static ClientService getClientService() {
|
||||
return staticClientService;
|
||||
}
|
||||
|
||||
/** Default no-arg constructor. */
|
||||
public CasinoGameUI() {
|
||||
// default no-arg constructor
|
||||
}
|
||||
|
||||
private static final int DEFAULT_WIDTH = 1200;
|
||||
private static final int DEFAULT_HEIGHT = 800;
|
||||
|
||||
/**
|
||||
* Starts the main stage of the application.
|
||||
* Startet die Haupt-Stage der Anwendung.
|
||||
*
|
||||
* @param stage The main stage provided by the system.
|
||||
* @throws IOException If the FXML file or resources cannot be loaded.
|
||||
* @param stage Die vom System bereitgestellte Haupt-Stage.
|
||||
* @throws IOException Wenn die FXML-Datei oder Ressourcen nicht geladen werden können.
|
||||
*/
|
||||
@Override
|
||||
public void start(Stage stage) throws IOException {
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(CasinoGameUI.class.getResource("/ui-structure/Casinogameui.fxml"));
|
||||
FXMLLoader fxmlLoader =
|
||||
new FXMLLoader(CasinoGameUI.class.getResource("/ui-structure/casinogameui.fxml"));
|
||||
Scene scene = new Scene(fxmlLoader.load(), DEFAULT_WIDTH, DEFAULT_HEIGHT);
|
||||
stage.setTitle("Casono (GAME)");
|
||||
|
||||
@@ -61,9 +41,9 @@ public class CasinoGameUI extends Application {
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point of the application.
|
||||
* Startpunkt der Anwendung.
|
||||
*
|
||||
* @param args Command line arguments.
|
||||
* @param args Befehlszeilenargumente.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
launch();
|
||||
|
||||
@@ -27,8 +27,6 @@ import javafx.scene.shape.Rectangle;
|
||||
import javafx.scene.web.WebEngine;
|
||||
import javafx.scene.web.WebView;
|
||||
import javafx.stage.Stage;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Experimenteller integrierter Browser für Casono.
|
||||
@@ -52,17 +50,13 @@ import org.apache.logging.log4j.Logger;
|
||||
*/
|
||||
public class CasinoBrowserController {
|
||||
|
||||
/** Standardkonstruktor. Initialisiert den CasinoBrowserController. */
|
||||
public CasinoBrowserController() {
|
||||
// Intentionally left blank; controller initialization is FXML-driven.
|
||||
}
|
||||
|
||||
private static final Set<String> TRUSTED_DOMAINS = new HashSet<>();
|
||||
|
||||
private static final CookieManager COOKIE_MANAGER =
|
||||
new CookieManager(null, CookiePolicy.ACCEPT_ORIGINAL_SERVER);
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(CasinoBrowserController.class);
|
||||
private static final org.apache.logging.log4j.Logger LOGGER =
|
||||
org.apache.logging.log4j.LogManager.getLogger(CasinoBrowserController.class);
|
||||
|
||||
private static final int LOGO_HEIGHT = 40;
|
||||
private static final int CORNER_RADIUS = 40;
|
||||
@@ -360,9 +354,8 @@ public class CasinoBrowserController {
|
||||
fwdBtn.getStyleClass().add("gray-button");
|
||||
fwdBtn.setOnAction(
|
||||
e -> {
|
||||
int currentIndex = engine.getHistory().getCurrentIndex();
|
||||
int lastIndex = engine.getHistory().getEntries().size() - 1;
|
||||
if (currentIndex < lastIndex) {
|
||||
if (engine.getHistory().getCurrentIndex()
|
||||
< engine.getHistory().getEntries().size() - 1) {
|
||||
engine.getHistory().go(1);
|
||||
}
|
||||
});
|
||||
@@ -430,7 +423,7 @@ public class CasinoBrowserController {
|
||||
|
||||
Scene scene = new Scene(root, WINDOW_WIDTH, WINDOW_HEIGHT);
|
||||
|
||||
var css = CasinoBrowserController.class.getResource("/ui-structure/Casinogameui.css");
|
||||
var css = CasinoBrowserController.class.getResource("/ui-structure/casinogameui.css");
|
||||
|
||||
if (css != null) {
|
||||
scene.getStylesheets().add(css.toExternalForm());
|
||||
@@ -519,11 +512,10 @@ public class CasinoBrowserController {
|
||||
|
||||
alert.setTitle("Unbekannte Website");
|
||||
alert.setHeaderText("Diese Website ist nicht bekannt");
|
||||
String content =
|
||||
alert.setContentText(
|
||||
host
|
||||
+ "\n\nDiese Seite ist nicht vom Casono Browser verifiziert.\n"
|
||||
+ "Möchten Sie sie trotzdem öffnen?";
|
||||
alert.setContentText(content);
|
||||
+ "\n\nDiese Seite ist nicht vom "
|
||||
+ "Casono Browser verifiziert.\nMöchten Sie sie trotzdem öffnen?");
|
||||
|
||||
var stream = CasinoBrowserController.class.getResourceAsStream(LOGO_PATH);
|
||||
Image logo = new Image(stream);
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui.Casinomainui;
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
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 org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Controller für die interaktive Taskleiste innerhalb der Poker-UI.
|
||||
@@ -18,12 +16,8 @@ import org.apache.logging.log4j.Logger;
|
||||
*/
|
||||
public class TaskbarController {
|
||||
|
||||
/** Standardkonstruktor. Wird von FXML verwendet. */
|
||||
public TaskbarController() {
|
||||
// default constructor for FXML
|
||||
}
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(CasinoBrowserController.class);
|
||||
private static final org.apache.logging.log4j.Logger LOGGER =
|
||||
org.apache.logging.log4j.LogManager.getLogger(CasinoBrowserController.class);
|
||||
|
||||
@FXML private HBox taskbar;
|
||||
@FXML private TextField taskbarInput;
|
||||
@@ -98,21 +92,15 @@ public class TaskbarController {
|
||||
processBet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wird aufgerufen, wenn der Exit-Button in der Taskleiste gedrückt wird.
|
||||
*
|
||||
* <p>TODO: Logik implementieren, um zur Lobby zurückzukehren, ohne die gesamte Anwendung zu
|
||||
* schließen (kein System.exit/Platform.exit).
|
||||
*/
|
||||
@FXML
|
||||
private void onExitButtonClick() {
|
||||
javafx.application.Platform.runLater(
|
||||
() -> {
|
||||
// Close game stage
|
||||
javafx.stage.Stage currentStage =
|
||||
(javafx.stage.Stage) taskbar.getScene().getWindow();
|
||||
currentStage.close();
|
||||
// Lobby-UI starten
|
||||
try {
|
||||
new Casinomainui().start(new javafx.stage.Stage());
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Fehler beim Starten der Lobby-UI: {}", e.getMessage());
|
||||
}
|
||||
});
|
||||
Platform.exit();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,13 +10,13 @@ import javafx.stage.Stage;
|
||||
/**
|
||||
* JavaFX Application class for the Casono main UI.
|
||||
*
|
||||
* <p>Default constructor for the application.
|
||||
* <p>Standardkonstruktor für die Anwendung.
|
||||
*/
|
||||
public class Casinomainui extends Application {
|
||||
|
||||
/** Default constructor. */
|
||||
/** Standardkonstruktor. */
|
||||
public Casinomainui() {
|
||||
// Default constructor
|
||||
// Standardkonstruktor
|
||||
}
|
||||
|
||||
private static final int SCENE_WIDTH = 1200;
|
||||
@@ -30,21 +30,12 @@ public class Casinomainui extends Application {
|
||||
* @throws IOException If loading the FXML fails.
|
||||
*/
|
||||
public void start(Stage stage) throws IOException {
|
||||
// If the launcher passed an address argument (ip:port), expose it as
|
||||
// system properties so controllers can read it without embedding defaults.
|
||||
var raw = getParameters().getRaw();
|
||||
if (raw != null && raw.size() > 0) {
|
||||
String arg = raw.get(0);
|
||||
String[] parts = arg.split(":", 2);
|
||||
if (parts.length == 2) {
|
||||
System.setProperty("casono.server.host", parts[0]);
|
||||
System.setProperty("casono.server.port", parts[1]);
|
||||
}
|
||||
}
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/ui-structure/Casinomainui.fxml"));
|
||||
FXMLLoader fxmlLoader =
|
||||
new FXMLLoader(getClass().getResource("/ui-structure/Casinomainui.fxml"));
|
||||
Scene scene = new Scene(fxmlLoader.load(), SCENE_WIDTH, SCENE_HEIGHT);
|
||||
stage.setTitle("Casono");
|
||||
javafx.scene.image.Image icon = new javafx.scene.image.Image(
|
||||
javafx.scene.image.Image icon =
|
||||
new javafx.scene.image.Image(
|
||||
getClass().getResource("/images/logoinverted.png").toExternalForm());
|
||||
stage.getIcons().add(icon);
|
||||
stage.setScene(scene);
|
||||
|
||||
@@ -3,9 +3,6 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui;
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
@@ -14,41 +11,23 @@ import javafx.scene.layout.VBox;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.LobbyClient;
|
||||
|
||||
/**
|
||||
* 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. */
|
||||
public class CasinomainuiController {
|
||||
private static final Logger LOGGER = LogManager.getLogger(CasinomainuiController.class);
|
||||
|
||||
@FXML
|
||||
private AnchorPane rootPane;
|
||||
@FXML
|
||||
private Label titleLabel;
|
||||
@FXML
|
||||
private Label subtitleLabel;
|
||||
@FXML
|
||||
private ImageView logoView;
|
||||
@FXML
|
||||
private Rectangle greenBox;
|
||||
@FXML
|
||||
private Button exitbutton;
|
||||
@FXML
|
||||
private VBox casinoTable;
|
||||
@FXML
|
||||
private TextField usernameField;
|
||||
@FXML
|
||||
private Button loginButton;
|
||||
@FXML private AnchorPane rootPane;
|
||||
@FXML private Label titleLabel;
|
||||
@FXML private Label subtitleLabel;
|
||||
@FXML private ImageView logoView;
|
||||
@FXML private Rectangle greenBox;
|
||||
@FXML private Button exitbutton;
|
||||
@FXML private VBox casinoTable;
|
||||
|
||||
private LobbyButtonTranslationManager translationManager;
|
||||
private LobbyButtonGridManager gridManager;
|
||||
private int nextButtonId = 1;
|
||||
private LobbyClient lobbyClient;
|
||||
|
||||
/** Default constructor for dependency injection by FXMLLoader. */
|
||||
public CasinomainuiController() {
|
||||
// Default constructor
|
||||
}
|
||||
@@ -60,63 +39,9 @@ public class CasinomainuiController {
|
||||
subtitleLabel.setText("Texas Hold'em Poker");
|
||||
logoView.setImage(new Image(getClass().getResource("/images/logo.png").toExternalForm()));
|
||||
|
||||
translationManager = LobbyButtonTranslationManager.getInstance();
|
||||
String host = System.getProperty("casono.server.host");
|
||||
int port = Integer.parseInt(System.getProperty("casono.server.port"));
|
||||
ClientService clientService;
|
||||
try {
|
||||
clientService = new ClientService(host, port);
|
||||
} catch (RuntimeException e) {
|
||||
LOGGER.warn("Could not connect to server {}:{} — starting in offline mode: {}", host, port, e.getMessage());
|
||||
clientService = new ClientService(true); // offline mode
|
||||
}
|
||||
gridManager = new LobbyButtonGridManager(new javafx.scene.layout.GridPane(), translationManager, clientService);
|
||||
// LobbyClient will use the provided ClientService; in offline mode calls will
|
||||
// fail with RuntimeException
|
||||
lobbyClient = new LobbyClient(clientService);
|
||||
casinoTable.getChildren().clear();
|
||||
casinoTable.getChildren().add(gridManager.getGridPane());
|
||||
gridManager.renderLobbyButtons();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the login button action. Validates input and calls
|
||||
* LobbyClient.login().
|
||||
*/
|
||||
@FXML
|
||||
public void handleLoginButton() {
|
||||
String username = usernameField.getText();
|
||||
if (username == null || username.isBlank()) {
|
||||
showAlert("Please enter a username.");
|
||||
return;
|
||||
}
|
||||
// Only allow alphanumeric, _ and -
|
||||
if (!username.matches("[a-zA-Z0-9_-]+")) {
|
||||
showAlert("Only letters, numbers, '_' and '-' are allowed!");
|
||||
return;
|
||||
}
|
||||
if (lobbyClient.getClientService().isOffline()) {
|
||||
showAlert("Offline mode: cannot send login to server.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
lobbyClient.login(username);
|
||||
showAlert("Login sent: " + username);
|
||||
} catch (RuntimeException e) {
|
||||
LOGGER.error("Login failed: {}", e.getMessage());
|
||||
showAlert("Login failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows an alert dialog with the given message.
|
||||
*/
|
||||
private void showAlert(String message) {
|
||||
Alert alert = new Alert(AlertType.INFORMATION);
|
||||
alert.setTitle("Info");
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText(message);
|
||||
alert.showAndWait();
|
||||
translationManager = new LobbyButtonTranslationManager();
|
||||
gridManager =
|
||||
new LobbyButtonGridManager(new javafx.scene.layout.GridPane(), translationManager);
|
||||
casinoTable.getChildren().clear();
|
||||
casinoTable.getChildren().add(gridManager.getGridPane());
|
||||
gridManager.renderLobbyButtons();
|
||||
@@ -132,25 +57,17 @@ public class CasinomainuiController {
|
||||
@FXML
|
||||
public void handleCreateLobbyButton() {
|
||||
if (translationManager.isFull()) {
|
||||
LOGGER.warn("Grid is full! No more lobbies available.");
|
||||
LOGGER.warn("Grid voll! Keine weiteren Lobbys moeglich.");
|
||||
return;
|
||||
}
|
||||
int buttonId = nextButtonId++;
|
||||
try {
|
||||
String username = usernameField != null ? usernameField.getText() : "<unknown>";
|
||||
LOGGER.info("Creating lobby for user: {}", username);
|
||||
// avoid attempting to create a lobby when offline
|
||||
if (lobbyClient.getClientService().isOffline()) {
|
||||
LOGGER.warn("Cannot create lobby while offline");
|
||||
showAlert("Offline mode: cannot create lobby.");
|
||||
return;
|
||||
}
|
||||
int lobbyId = gridManager.createLobby();
|
||||
try {
|
||||
translationManager.addLobbyButton(buttonId, lobbyId);
|
||||
LOGGER.info("ButtonID: {}, LobbyID: {}", buttonId, lobbyId);
|
||||
gridManager.renderLobbyButtons();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to create or add lobby: {}", e.getMessage());
|
||||
LOGGER.error("Fehler beim Hinzufügen: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,18 +5,6 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui;
|
||||
* ButtonID to LobbyID.
|
||||
*/
|
||||
import java.util.Map;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.LobbyClient;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
@@ -25,13 +13,10 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Manages the grid for lobby buttons and rendering. Uses
|
||||
* LobbyButtonTranslationManager for mapping
|
||||
* Manages the grid for lobby buttons and rendering. Uses LobbyButtonTranslationManager for mapping
|
||||
* ButtonID to LobbyID.
|
||||
*/
|
||||
public class LobbyButtonGridManager {
|
||||
private static final double BUTTON_WIDTH_MARGIN = 20.0;
|
||||
private static final double BUTTON_MIN_SIZE = 10.0;
|
||||
private static final Logger LOGGER = LogManager.getLogger(LobbyButtonGridManager.class);
|
||||
|
||||
/** GridPane for the button grid. */
|
||||
@@ -40,29 +25,17 @@ public class LobbyButtonGridManager {
|
||||
/** Manager for mapping ButtonID to LobbyID. */
|
||||
private final LobbyButtonTranslationManager translationManager;
|
||||
|
||||
/** Number of rows in the grid. */
|
||||
private static final int ROWS = 2;
|
||||
|
||||
/** Number of columns in the grid. */
|
||||
private static final int COLS = 4;
|
||||
|
||||
/** Image for a lobby in CREATED state. */
|
||||
/** Default fallback image. */
|
||||
private static final String BUTTON_FALLBACK_IMAGE = "/images/lobbypictures/error.png";
|
||||
/** Path to the button image. */
|
||||
private static final String BUTTON_IMAGE_PATH = "/images/logo.png";
|
||||
|
||||
/**
|
||||
* Template for per-button images. Use: button index and status
|
||||
* (created|running). Example:
|
||||
* /images/lobby_1_created.png
|
||||
*/
|
||||
private static final String BUTTON_IMAGE_TEMPLATE = "/images/lobbypictures/lobby_%d_%s.png";
|
||||
|
||||
/** Cache for loaded Images keyed by resource path. */
|
||||
private final ConcurrentHashMap<String, Image> imageCache = new ConcurrentHashMap<>();
|
||||
|
||||
private final LobbyClient lobbyClient;
|
||||
|
||||
/** Executor for background status/network tasks. */
|
||||
private final ExecutorService executor = Executors.newCachedThreadPool();
|
||||
/** Scheduler for periodic refresh of lobby mappings. */
|
||||
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
/** Max random lobby id. */
|
||||
private static final int MAX_RANDOM_LOBBY_ID = 10000;
|
||||
|
||||
/**
|
||||
* Constructor for the GridManager.
|
||||
@@ -71,127 +44,36 @@ public class LobbyButtonGridManager {
|
||||
* @param translationManager the manager for mapping ButtonID to LobbyID
|
||||
*/
|
||||
public LobbyButtonGridManager(
|
||||
GridPane gridPane, LobbyButtonTranslationManager translationManager, LobbyClient lobbyClient) {
|
||||
GridPane gridPane, LobbyButtonTranslationManager translationManager) {
|
||||
this.gridPane = gridPane;
|
||||
// Always use the singleton
|
||||
this.translationManager = LobbyButtonTranslationManager.getInstance();
|
||||
this.lobbyClient = lobbyClient;
|
||||
// Start periodic refresh to keep mapping in sync with server
|
||||
startPeriodicRefresh(5, 5);
|
||||
this.translationManager = translationManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience constructor: accept a {@link ClientService} and build a
|
||||
* {@link LobbyClient} from it. This avoids any host/port System.getProperty
|
||||
* lookups elsewhere — caller controls the ClientService.
|
||||
*/
|
||||
public LobbyButtonGridManager(
|
||||
GridPane gridPane, LobbyButtonTranslationManager translationManager, ClientService clientService) {
|
||||
this(gridPane, translationManager, new LobbyClient(clientService));
|
||||
}
|
||||
|
||||
/**
|
||||
* Start periodic refresh of lobby mappings.
|
||||
*
|
||||
* @param initialDelay initial delay in seconds
|
||||
* @param period period in seconds
|
||||
*/
|
||||
private void startPeriodicRefresh(long initialDelay, long period) {
|
||||
scheduler.scheduleAtFixedRate(this::refreshMappings, initialDelay, period, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh mappings by checking each stored lobby id on the server. If a lobby
|
||||
* no longer exists (or an error occurs), remove it from the translation map
|
||||
* and update the UI.
|
||||
*/
|
||||
private void refreshMappings() {
|
||||
Map<Integer, Integer> mapping = translationManager.getButtonIdToLobbyId();
|
||||
if (mapping.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// Make a copy of entries to avoid concurrent modification
|
||||
List<Map.Entry<Integer, Integer>> entries = new ArrayList<>(mapping.entrySet());
|
||||
for (Map.Entry<Integer, Integer> e : entries) {
|
||||
int buttonId = e.getKey();
|
||||
int lobbyId = e.getValue();
|
||||
CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
String status = lobbyClient.fetchLobbyStatusString(lobbyId);
|
||||
return status;
|
||||
} catch (Exception ex) {
|
||||
LOGGER.info("Lobby {} appears missing or error: {}", lobbyId, ex.getMessage());
|
||||
return null;
|
||||
}
|
||||
}, executor).thenAccept(status -> {
|
||||
if (status == null) {
|
||||
// remove mapping and update UI
|
||||
translationManager.removeLobbyButton(buttonId);
|
||||
javafx.application.Platform.runLater(() -> {
|
||||
updateLobbyButtonImages();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Default client creation removed to avoid implicit IP/port configuration.
|
||||
// Applications must construct and provide a LobbyClient or ClientService
|
||||
// explicitly.
|
||||
|
||||
/**
|
||||
* Renders all lobby buttons in the grid. Creates a button for each mapping with
|
||||
* image and event
|
||||
* 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;
|
||||
}
|
||||
List<Integer> buttonIds = new ArrayList<>(mapping.keySet());
|
||||
Collections.sort(buttonIds);
|
||||
int index = 0;
|
||||
for (Integer buttonId : buttonIds) {
|
||||
int lobbyId = mapping.get(buttonId);
|
||||
for (Map.Entry<Integer, Integer> entry : mapping.entrySet()) {
|
||||
int buttonId = entry.getKey();
|
||||
Button btn = new Button();
|
||||
btn.setId("lobbyBtn-" + buttonId);
|
||||
// placeholder image so UI remains responsive
|
||||
Image placeholder = safeLoadImage(BUTTON_FALLBACK_IMAGE);
|
||||
ImageView imageView = new ImageView(placeholder);
|
||||
imageView.setPreserveRatio(true);
|
||||
imageView.fitWidthProperty().bind(gridPane.widthProperty().divide(COLS).subtract(BUTTON_WIDTH_MARGIN));
|
||||
imageView.setSmooth(true);
|
||||
btn.setGraphic(imageView);
|
||||
btn.setMaxWidth(Double.MAX_VALUE);
|
||||
btn.setMaxHeight(Double.MAX_VALUE);
|
||||
btn.setMinWidth(BUTTON_MIN_SIZE);
|
||||
btn.setMinHeight(BUTTON_MIN_SIZE);
|
||||
GridPane.setHgrow(btn, javafx.scene.layout.Priority.ALWAYS);
|
||||
GridPane.setVgrow(btn, javafx.scene.layout.Priority.ALWAYS);
|
||||
final int bId = buttonId;
|
||||
btn.setOnAction(e -> {
|
||||
Integer targetLobbyId = translationManager.getLobbyIdForButton(bId);
|
||||
if (targetLobbyId != null) {
|
||||
joinLobby(targetLobbyId);
|
||||
btn.setGraphic(
|
||||
new ImageView(new Image(getClass().getResourceAsStream(BUTTON_IMAGE_PATH))));
|
||||
btn.setOnAction(
|
||||
e -> {
|
||||
Integer lobbyId = translationManager.getLobbyIdForButton(buttonId);
|
||||
if (lobbyId != null) {
|
||||
joinLobby(lobbyId);
|
||||
}
|
||||
});
|
||||
// async fetch status and update image
|
||||
CompletableFuture.supplyAsync(() -> lobbyClient.fetchLobbyStatusString(lobbyId), executor)
|
||||
.thenAccept(statusStr -> {
|
||||
LobbyStatus status = parseLobbyStatus(statusStr);
|
||||
String path = getImagePathForButton(buttonId, status == null ? LobbyStatus.CREATED : status);
|
||||
Image img = safeLoadImage(path);
|
||||
javafx.application.Platform.runLater(() -> {
|
||||
ImageView iv = new ImageView(img);
|
||||
iv.setPreserveRatio(true);
|
||||
iv.fitWidthProperty()
|
||||
.bind(gridPane.widthProperty().divide(COLS).subtract(BUTTON_WIDTH_MARGIN));
|
||||
iv.setSmooth(true);
|
||||
btn.setGraphic(iv);
|
||||
});
|
||||
});
|
||||
int row = index / COLS;
|
||||
int col = index % COLS;
|
||||
gridPane.add(btn, col, row);
|
||||
@@ -199,138 +81,16 @@ public class LobbyButtonGridManager {
|
||||
}
|
||||
}
|
||||
|
||||
/** Possible lobby statuses. */
|
||||
private enum LobbyStatus {
|
||||
CREATED,
|
||||
RUNNING
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current status of a lobby.
|
||||
*
|
||||
* @param lobbyId the lobby id to query
|
||||
* @return the lobby status (mapped from server string; CREATED or RUNNING)
|
||||
*/
|
||||
public LobbyStatus getLobbyStatus(int lobbyId) {
|
||||
String serverStatus = lobbyClient.fetchLobbyStatusString(lobbyId);
|
||||
LobbyStatus parsed = parseLobbyStatus(serverStatus);
|
||||
if (parsed == null) {
|
||||
// Defensive fallback
|
||||
LOGGER.error("Unrecognized lobby status '{}' for lobby {}. Defaulting to CREATED.", serverStatus, lobbyId);
|
||||
return LobbyStatus.CREATED;
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a status string returned by the server into the local enum.
|
||||
* Accepts case-insensitive values like "created" / "CREATED" / "running".
|
||||
* Returns null if the string is not recognized.
|
||||
*/
|
||||
private LobbyStatus parseLobbyStatus(String statusStr) {
|
||||
if (statusStr == null)
|
||||
return null;
|
||||
try {
|
||||
return LobbyStatus.valueOf(statusStr.trim().toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String getImagePathForButton(int buttonId, LobbyStatus status) {
|
||||
String statusStr = status == LobbyStatus.CREATED ? "created" : "running";
|
||||
return String.format(BUTTON_IMAGE_TEMPLATE, buttonId, statusStr);
|
||||
}
|
||||
|
||||
private Image safeLoadImage(String path) {
|
||||
// Return cached image if present
|
||||
Image cached = imageCache.get(path);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
// Attempt to load the requested resource
|
||||
java.io.InputStream is = getClass().getResourceAsStream(path);
|
||||
if (is == null) {
|
||||
LOGGER.debug(
|
||||
"Image resource not found: {}. Falling back to {}",
|
||||
path,
|
||||
BUTTON_FALLBACK_IMAGE);
|
||||
is = getClass().getResourceAsStream(BUTTON_FALLBACK_IMAGE);
|
||||
}
|
||||
Image loaded = null;
|
||||
try {
|
||||
if (is != null) {
|
||||
loaded = new Image(is);
|
||||
} else {
|
||||
LOGGER.error(
|
||||
"Both requested image '{}' and fallback '{}' are missing. No image will be set.",
|
||||
path,
|
||||
BUTTON_FALLBACK_IMAGE);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to load image '{}'", path, e);
|
||||
}
|
||||
if (loaded == null) {
|
||||
// leave
|
||||
// null
|
||||
}
|
||||
if (loaded != null) {
|
||||
imageCache.put(path, loaded);
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
|
||||
/** Update all lobby buttons' images according to the current lobby statuses. */
|
||||
public void updateLobbyButtonImages() {
|
||||
Map<Integer, Integer> mapping = translationManager.getButtonIdToLobbyId();
|
||||
if (mapping.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<Integer> buttonIds = new ArrayList<>(mapping.keySet());
|
||||
Collections.sort(buttonIds);
|
||||
for (Integer buttonId : buttonIds) {
|
||||
int lobbyId = mapping.get(buttonId);
|
||||
CompletableFuture.supplyAsync(() -> getLobbyStatus(lobbyId), executor)
|
||||
.thenAccept(status -> {
|
||||
String path = getImagePathForButton(buttonId, status);
|
||||
javafx.application.Platform.runLater(() -> {
|
||||
for (Node node : gridPane.getChildren()) {
|
||||
if (node instanceof Button && ("lobbyBtn-" + buttonId).equals(node.getId())) {
|
||||
Button btn = (Button) node;
|
||||
ImageView iv = new ImageView(safeLoadImage(path));
|
||||
iv.setPreserveRatio(true);
|
||||
iv.fitWidthProperty()
|
||||
.bind(
|
||||
gridPane.widthProperty()
|
||||
.divide(COLS)
|
||||
.subtract(BUTTON_WIDTH_MARGIN));
|
||||
iv.setSmooth(true);
|
||||
btn.setGraphic(iv);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new lobby via the LobbyClient.
|
||||
* Placeholder for lobby creation logic. Returns a generated lobbyId.
|
||||
*
|
||||
* @return The generated lobbyId
|
||||
*/
|
||||
public int createLobby() {
|
||||
try {
|
||||
int lobbyId = lobbyClient.createLobby();
|
||||
LOGGER.info("Lobby created via LobbyClient: {}", lobbyId);
|
||||
if (lobbyId <= 0) {
|
||||
throw new RuntimeException("LobbyClient returned invalid lobby id: " + lobbyId);
|
||||
}
|
||||
// TODO: Replace with actual lobby creation logic
|
||||
int lobbyId = (int) (Math.random() * MAX_RANDOM_LOBBY_ID + 1);
|
||||
LOGGER.info("Lobby created: {}", lobbyId);
|
||||
return lobbyId;
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to create lobby via LobbyClient: {}", e.getMessage());
|
||||
throw new RuntimeException("Failed to create lobby", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -339,47 +99,8 @@ public class LobbyButtonGridManager {
|
||||
* @param lobbyId The lobbyId to join
|
||||
*/
|
||||
public void joinLobby(int lobbyId) {
|
||||
// Request server to join the lobby (blackbox client may throw on failure)
|
||||
// TODO: Replace with actual join logic
|
||||
LOGGER.info("Joining lobby: {}", lobbyId);
|
||||
try {
|
||||
lobbyClient.joinLobby(lobbyId);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("LobbyClient failed to join lobby {}: {}", lobbyId, e.getMessage());
|
||||
return;
|
||||
}
|
||||
javafx.application.Platform.runLater(
|
||||
() -> {
|
||||
// Hide lobby stage (do not close) so we can return later
|
||||
javafx.scene.Scene scene = gridPane.getScene();
|
||||
javafx.stage.Stage currentStage = (javafx.stage.Stage) scene.getWindow();
|
||||
currentStage.hide();
|
||||
// Prepare game stage and set a handler so that when it is closed the lobby is
|
||||
// shown and updated
|
||||
javafx.stage.Stage gameStage = new javafx.stage.Stage();
|
||||
gameStage.setOnHidden(
|
||||
ev -> {
|
||||
try {
|
||||
currentStage.show();
|
||||
// refresh mappings immediately when returning from game
|
||||
refreshMappings();
|
||||
updateLobbyButtonImages();
|
||||
} catch (Exception ex) {
|
||||
LOGGER.error(
|
||||
"Error while returning to lobby: {}", ex.getMessage());
|
||||
}
|
||||
});
|
||||
// Start the Game UI using the prepared stage
|
||||
try {
|
||||
// ClientService an GameUI übergeben
|
||||
ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameUI
|
||||
.setClientService(lobbyClient.getClientService());
|
||||
new ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameUI().start(gameStage);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Error starting Game UI: {}", e.getMessage());
|
||||
// If starting fails, show the lobby again
|
||||
currentStage.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -390,21 +111,4 @@ public class LobbyButtonGridManager {
|
||||
public javafx.scene.layout.GridPane getGridPane() {
|
||||
return gridPane;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose the configured LobbyClient so callers can invoke its methods
|
||||
* directly (createLobby, fetchLobbyStatusString, joinLobby, ...).
|
||||
*/
|
||||
public LobbyClient getLobbyClient() {
|
||||
return lobbyClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger an immediate refresh of mappings (poll server and remove missing
|
||||
* lobbies). Public so callers can force a refresh when UI focus returns.
|
||||
*/
|
||||
public void refreshNow() {
|
||||
refreshMappings();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,53 +4,36 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Manages the mapping between Button IDs and Lobby IDs in memory only. No file operations,
|
||||
* runtime-only data structure.
|
||||
* Verwaltet das Mapping zwischen Button-IDs und Lobby-IDs rein im Speicher. Keine Dateioperationen,
|
||||
* nur Laufzeitdatenstruktur.
|
||||
*/
|
||||
public class LobbyButtonTranslationManager {
|
||||
|
||||
// Singleton instance
|
||||
private static LobbyButtonTranslationManager instance;
|
||||
|
||||
// Singleton access
|
||||
/**
|
||||
* Returns the singleton instance of the manager.
|
||||
*
|
||||
* @return the single instance of {@code LobbyButtonTranslationManager}
|
||||
*/
|
||||
public static LobbyButtonTranslationManager getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new LobbyButtonTranslationManager();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/** Maximum number of buttons/lobbies */
|
||||
/** Maximale Anzahl an Buttons/Lobbys */
|
||||
private static final int MAX_BUTTONS = 8;
|
||||
|
||||
/** Mapping ButtonID → LobbyID */
|
||||
/** Zuordnung ButtonID → LobbyID */
|
||||
private final Map<Integer, Integer> buttonIdToLobbyId = new HashMap<>();
|
||||
|
||||
/** Private constructor for the singleton pattern */
|
||||
private LobbyButtonTranslationManager() {
|
||||
// Mapping is empty at startup
|
||||
/** Konstruktor: initialisiert die Zuordnung leer. */
|
||||
public LobbyButtonTranslationManager() {
|
||||
// Zuordnung bleibt leer beim Start
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the grid is full (MAX_BUTTONS reached).
|
||||
* Prüft, ob das Grid voll ist (MAX_BUTTONS erreicht).
|
||||
*
|
||||
* @return true if the grid is full; otherwise false
|
||||
* @return true, wenn Grid voll; sonst false
|
||||
*/
|
||||
public boolean isFull() {
|
||||
return buttonIdToLobbyId.size() >= MAX_BUTTONS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a mapping ButtonID → LobbyID.
|
||||
* Fügt eine Zuordnung ButtonID → LobbyID hinzu.
|
||||
*
|
||||
* @param buttonId the ID of the button
|
||||
* @param lobbyId the ID of the lobby
|
||||
* @throws Exception when the grid is full
|
||||
* @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()) {
|
||||
@@ -60,28 +43,28 @@ public class LobbyButtonTranslationManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the mapping for the given ButtonID.
|
||||
* Entfernt eine Zuordnung für die gegebene ButtonID.
|
||||
*
|
||||
* @param buttonId the ID of the button to remove
|
||||
* @param buttonId Die ID des zu entfernenden Buttons
|
||||
*/
|
||||
public void removeLobbyButton(int buttonId) {
|
||||
buttonIdToLobbyId.remove(buttonId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the LobbyID for a given ButtonID.
|
||||
* Gibt die LobbyID für eine gegebene ButtonID zurück.
|
||||
*
|
||||
* @param buttonId the ButtonID
|
||||
* @return the associated LobbyID or null if not present
|
||||
* @param buttonId Die ButtonID
|
||||
* @return Die zugehoerige LobbyID oder null, falls nicht vorhanden
|
||||
*/
|
||||
public Integer getLobbyIdForButton(int buttonId) {
|
||||
return buttonIdToLobbyId.get(buttonId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full mapping ButtonID → LobbyID.
|
||||
* Gibt die gesamte Zuordnung ButtonID → LobbyID zurück.
|
||||
*
|
||||
* @return Map of all mappings
|
||||
* @return Map aller Zuordnungen
|
||||
*/
|
||||
public Map<Integer, Integer> getButtonIdToLobbyId() {
|
||||
return buttonIdToLobbyId;
|
||||
|
||||
@@ -3,10 +3,10 @@ package ch.unibas.dmi.dbis.cs108.casono.server;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserCleanupJob;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserRegistry;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.NetworkManager;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandRouter;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParserDispatcher;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.CommandRouter;
|
||||
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.parser.CommandParserDispatcher;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionDisconnectJob;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionManager;
|
||||
import java.time.Duration;
|
||||
|
||||
@@ -58,6 +58,7 @@ public class UserRegistry {
|
||||
*
|
||||
* @param sessionId the session ID of the disconnected client
|
||||
*/
|
||||
// TODO: Add to EventRegistry with DisconnectEvent
|
||||
public synchronized void onDisconnect(SessionId sessionId) {
|
||||
User user = bySessionId.remove(sessionId);
|
||||
if (user == null) {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.command;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.parser.Request;
|
||||
|
||||
public interface CommandHandler<T extends Request> {
|
||||
void execute(T request);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.command;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.parser.Request;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.command;
|
||||
|
||||
public class UnknownRequestException extends RuntimeException {
|
||||
private final String requestName;
|
||||
@@ -1,7 +0,0 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request;
|
||||
|
||||
public interface CommandHandler<T extends Request> {
|
||||
void execute(T request);
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.PrimitiveRequest;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request;
|
||||
|
||||
/**
|
||||
* Parser to convert the PrimitiveRequest to a Request and performing checks for required fields and
|
||||
* data types
|
||||
*/
|
||||
public interface CommandParser<T extends Request> {
|
||||
/**
|
||||
* Parses the provided PrimitiveRequest into a command-specific request
|
||||
*
|
||||
* @param primitiveRequest
|
||||
* @return
|
||||
*/
|
||||
T parse(PrimitiveRequest primitiveRequest);
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing;
|
||||
|
||||
/** Used in the PrimitiveRequest class to store the key of a parameter with its respective value */
|
||||
public record RequestParameter(String key, String value) {}
|
||||
@@ -0,0 +1,15 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.parser;
|
||||
|
||||
/**
|
||||
* Parser to convert the PrimitiveRequest to a Request and performing checks for required fields and
|
||||
* data types
|
||||
*/
|
||||
public interface CommandParser {
|
||||
/**
|
||||
* Parses the provided PrimitiveRequest into a command-specific request
|
||||
*
|
||||
* @param primitiveRequest
|
||||
* @return
|
||||
*/
|
||||
Request parse(PrimitiveRequest primitiveRequest);
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.parser;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.PrimitiveRequest;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.accessor;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.parser;
|
||||
|
||||
/** Exception thrown when a required parameter key is not found. */
|
||||
public class MissingParameterException extends RuntimeException {
|
||||
@@ -0,0 +1,4 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.parser;
|
||||
|
||||
/** Used in the PrimitiveRequest class to store the key of a parameter with its respective value */
|
||||
public record Parameter(String key, String value) {}
|
||||
@@ -1,4 +1,4 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.accessor;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.parser;
|
||||
|
||||
/** Exception thrown when a parameter value cannot be converted to the requested type. */
|
||||
public class ParameterParseException extends RuntimeException {
|
||||
@@ -0,0 +1,7 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.parser;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** Created by the ProtocolParser to allow easy access to the request contents */
|
||||
public record PrimitiveRequest(
|
||||
RequestContext context, String command, List<Parameter> parameters) {}
|
||||
@@ -1,12 +1,10 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.parser;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.parser;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.RequestParameter;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.parser.tokenizer.RawToken;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.parser.tokenizer.Token;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.parser.tokenizer.TokenClassifier;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.parser.tokenizer.TokenType;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.parser.tokenizer.Tokenizer;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RawRequest;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.tokenizer.RawToken;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.tokenizer.Token;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.tokenizer.TokenClassifier;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.tokenizer.TokenType;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.tokenizer.Tokenizer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -17,7 +15,7 @@ public class ProtocolParser {
|
||||
/**
|
||||
* Parses the payload of the provided RawPacket
|
||||
*
|
||||
* @param payload the payload to parse and check for syntax
|
||||
* @param packet the RawPacket containing the recieved data
|
||||
* @return created PrimitiveRequest
|
||||
*/
|
||||
public static RawRequest parse(String payload) {
|
||||
@@ -26,7 +24,7 @@ public class ProtocolParser {
|
||||
|
||||
Iterator<Token> iterator = tokens.iterator();
|
||||
String command = readCommand(iterator);
|
||||
List<RequestParameter> parameters = readParameters(iterator);
|
||||
List<Parameter> parameters = readParameters(iterator);
|
||||
|
||||
return new RawRequest(command, parameters);
|
||||
}
|
||||
@@ -52,8 +50,8 @@ public class ProtocolParser {
|
||||
* @param iterator
|
||||
* @return list containing all parsed parameters
|
||||
*/
|
||||
private static List<RequestParameter> readParameters(Iterator<Token> iterator) {
|
||||
List<RequestParameter> parameters = new ArrayList<>();
|
||||
private static List<Parameter> readParameters(Iterator<Token> iterator) {
|
||||
List<Parameter> parameters = new ArrayList<>();
|
||||
|
||||
try {
|
||||
while (iterator.hasNext()) {
|
||||
@@ -67,7 +65,7 @@ public class ProtocolParser {
|
||||
readSeperator(iterator.next());
|
||||
String value = readValue(iterator.next());
|
||||
|
||||
parameters.add(new RequestParameter(key, value));
|
||||
parameters.add(new Parameter(key, value));
|
||||
}
|
||||
} catch (NoSuchElementException e) {
|
||||
throw new ProtocolParserException("Ran out of tokens while reading parameter");
|
||||
@@ -1,4 +1,4 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.parser;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.parser;
|
||||
|
||||
public class ProtocolParserException extends RuntimeException {
|
||||
public ProtocolParserException(String message) {
|
||||
@@ -0,0 +1,5 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.parser;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record RawRequest(String command, List<Parameter> parameters) {}
|
||||
@@ -1,4 +1,4 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.parser;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.parser;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.accessor;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.parser;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.RequestParameter;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -19,12 +18,10 @@ public class RequestParameterAccessor {
|
||||
*
|
||||
* @param parameters to use
|
||||
*/
|
||||
public RequestParameterAccessor(List<RequestParameter> parameters) {
|
||||
public RequestParameterAccessor(List<Parameter> parameters) {
|
||||
this.index =
|
||||
parameters.stream()
|
||||
.collect(
|
||||
Collectors.toUnmodifiableMap(
|
||||
RequestParameter::key, RequestParameter::value));
|
||||
.collect(Collectors.toUnmodifiableMap(Parameter::key, Parameter::value));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1,4 +1,4 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.accessor;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.parser;
|
||||
|
||||
/**
|
||||
* Functional parser interface used to convert a raw string parameter into a target type.
|
||||
@@ -1,4 +1,4 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.parser;
|
||||
|
||||
/**
|
||||
* Exception thrown when the CommandParserDispatcher has no registered handler for the provided
|
||||
@@ -1,8 +0,0 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.RequestParameter;
|
||||
import java.util.List;
|
||||
|
||||
/** Created by the ProtocolParser to allow easy access to the request contents */
|
||||
public record PrimitiveRequest(
|
||||
RequestContext context, String command, List<RequestParameter> parameters) {}
|
||||
@@ -1,6 +0,0 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.RequestParameter;
|
||||
import java.util.List;
|
||||
|
||||
public record RawRequest(String command, List<RequestParameter> parameters) {}
|
||||
@@ -1,20 +0,0 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseBody;
|
||||
|
||||
/**
|
||||
* A simple success response with an empty body.
|
||||
*
|
||||
* <p>Use this to acknowledge successful requests that do not carry additional payload data.
|
||||
*/
|
||||
public class OkResponse extends SuccessResponse {
|
||||
/**
|
||||
* Create a minimal successful response (no body content).
|
||||
*
|
||||
* @param context the RequestContext of the request
|
||||
*/
|
||||
public OkResponse(RequestContext context) {
|
||||
super(context, ResponseBody.builder().build());
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher;
|
||||
|
||||
public class ResponseDispatchException extends RuntimeException {
|
||||
public ResponseDispatchException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,22 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.response;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseBody;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId;
|
||||
|
||||
/** Response representing an error outcome for a client's request. */
|
||||
public class ErrorResponse extends Response {
|
||||
/**
|
||||
* Construct an error response with a code and message.
|
||||
*
|
||||
* @param context the RequestContext of the request
|
||||
* @param sessionId the target session id
|
||||
* @param requestId the originating request id
|
||||
* @param errorCode a short error code identifying the failure
|
||||
* @param errorMessage a human readable error message
|
||||
*/
|
||||
public ErrorResponse(RequestContext context, String errorCode, String errorMessage) {
|
||||
public ErrorResponse(
|
||||
SessionId sessionId, int requestId, String errorCode, String errorMessage) {
|
||||
super(
|
||||
context,
|
||||
sessionId,
|
||||
requestId,
|
||||
ResponseBody.builder().param("CODE", errorCode).param("MSG", errorMessage).build());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.response;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId;
|
||||
|
||||
/**
|
||||
* A simple success response with an empty body.
|
||||
*
|
||||
* <p>Use this to acknowledge successful requests that do not carry additional payload data.
|
||||
*/
|
||||
public class OkResponse extends SuccessResponse {
|
||||
/**
|
||||
* Create a minimal successful response (no body content).
|
||||
*
|
||||
* @param sessionId the target session id
|
||||
* @param requestId the originating request id
|
||||
*/
|
||||
public OkResponse(SessionId sessionId, int requestId) {
|
||||
super(sessionId, requestId, ResponseBody.builder().build());
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.response;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId;
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.response;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseBody;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId;
|
||||
|
||||
/** Abstract base class for all server responses sent to clients. */
|
||||
public abstract class Response {
|
||||
private final RequestContext context;
|
||||
private final SessionId sessionId;
|
||||
private final int requestId;
|
||||
private final ResponseBody body;
|
||||
|
||||
/**
|
||||
* Create a new {@code Response}.
|
||||
*
|
||||
* @param context the RequestContext of the request
|
||||
* @param sessionId the id of the session this response targets
|
||||
* @param requestId the request identifier this response corresponds to
|
||||
* @param body the structured response body
|
||||
*/
|
||||
protected Response(RequestContext context, ResponseBody body) {
|
||||
this.context = context;
|
||||
protected Response(SessionId sessionId, int requestId, ResponseBody body) {
|
||||
this.sessionId = sessionId;
|
||||
this.requestId = requestId;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@@ -28,12 +29,12 @@ public abstract class Response {
|
||||
public abstract String prefix();
|
||||
|
||||
/**
|
||||
* Returns the session id of the session that should receive this response.
|
||||
* Returns the session id that should receive this response.
|
||||
*
|
||||
* @return the target {@link SessionId}
|
||||
*/
|
||||
public SessionId getSessionId() {
|
||||
return context.sessionId();
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,7 +43,7 @@ public abstract class Response {
|
||||
* @return the numeric request id
|
||||
*/
|
||||
public int getRequestId() {
|
||||
return context.requestId();
|
||||
return requestId;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1,4 +1,4 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.response;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.response;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.Response;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -1,4 +1,4 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.response;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -1,7 +1,5 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.response;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.PrimitiveResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.Response;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.Session;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionManager;
|
||||
|
||||
@@ -27,17 +25,12 @@ public class ResponseDispatcher {
|
||||
* the target session's response queue.
|
||||
*
|
||||
* @param response the response to dispatch
|
||||
* @throws ResponseDispatchException wraps any exceptions that occur during dispatching, such as
|
||||
* the {@link InterruptedException}
|
||||
* @throws InterruptedException if the thread is interrupted while waiting to enqueue the
|
||||
* primitive response
|
||||
*/
|
||||
public void dispatch(Response response) {
|
||||
public void dispatch(Response response) throws InterruptedException {
|
||||
PrimitiveResponse primitiveResponse = ResponseEncoder.encode(response);
|
||||
Session session = sessionManager.getSessionById(response.getSessionId());
|
||||
try {
|
||||
session.getResponseQueue().put(primitiveResponse);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new ResponseDispatchException("Interrupted while dispatching response", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,4 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.PrimitiveResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.Response;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseBlock;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseNode;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseParameter;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.response;
|
||||
|
||||
/**
|
||||
* Utility responsible for encoding a {@link Response} into a protocol payload string and wrapping
|
||||
@@ -1,4 +1,4 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.response;
|
||||
|
||||
/**
|
||||
* Marker interface for elements that may appear in a {@link ResponseBody}.
|
||||
@@ -1,4 +1,4 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.response;
|
||||
|
||||
/**
|
||||
* A parameter node stored in a {@link ResponseBody}.
|
||||
@@ -1,7 +1,6 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.response;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseBody;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId;
|
||||
|
||||
/**
|
||||
* Abstract {@link Response} specialization indicating a successful outcome.
|
||||
@@ -13,11 +12,12 @@ public abstract class SuccessResponse extends Response {
|
||||
/**
|
||||
* Create a successful response with the provided body.
|
||||
*
|
||||
* @param context the RequestContext of the request
|
||||
* @param sessionId the session id this response targets
|
||||
* @param requestId the originating request id
|
||||
* @param body the response body
|
||||
*/
|
||||
protected SuccessResponse(RequestContext context, ResponseBody body) {
|
||||
super(context, body);
|
||||
protected SuccessResponse(SessionId sessionId, int requestId, ResponseBody body) {
|
||||
super(sessionId, requestId, body);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1,10 +1,11 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.sessions;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandRouter;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParserDispatcher;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.CommandRouter;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.events.EventBus;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.PrimitiveResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.parser.CommandParserDispatcher;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.response.PrimitiveResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.TransportLayer;
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
@@ -24,6 +25,7 @@ public class Session {
|
||||
*
|
||||
* @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,
|
||||
|
||||
@@ -14,7 +14,7 @@ public class SessionId {
|
||||
/**
|
||||
* Creates a new SessionId with the specified UUID.
|
||||
*
|
||||
* @param value UUID to use for this SessionId
|
||||
* @param UUID to use for this SessionId
|
||||
*/
|
||||
public SessionId(UUID value) {
|
||||
this.value = value;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.sessions;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandRouter;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParserDispatcher;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.CommandRouter;
|
||||
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.parser.CommandParserDispatcher;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.TransportLayer;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
@@ -88,7 +88,7 @@ public class SessionManager {
|
||||
/**
|
||||
* Handler for the DisconnectEvent
|
||||
*
|
||||
* @param event the DisconnectEvent to handle
|
||||
* @param id of the session that disconnected
|
||||
*/
|
||||
public void onDisconnect(DisconnectEvent event) {
|
||||
logger.debug("Recieved DisconnectEvent event for session {}", event.sessionId().value());
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.sessions;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandRouter;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParserDispatcher;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.UnknownCommandException;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.CommandRouter;
|
||||
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.protocol.parser.ProtocolParser;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.parser.ProtocolParserException;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.parser.tokenizer.TokenizerException;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.PrimitiveRequest;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RawRequest;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.ErrorResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.PrimitiveResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatchException;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseEncoder;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.parser.CommandParserDispatcher;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.parser.PrimitiveRequest;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.parser.ProtocolParser;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.parser.ProtocolParserException;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.parser.RawRequest;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.parser.Request;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.parser.RequestContext;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.RawPacket;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.TransportLayer;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.tokenizer.TokenizerException;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@@ -46,79 +41,35 @@ public class SessionReader implements Runnable {
|
||||
public void run() {
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
RawPacket rawPacket = null;
|
||||
RawRequest rawRequest = null;
|
||||
RequestContext requestContext = null;
|
||||
try {
|
||||
// Step 1: Read from transport
|
||||
rawPacket = transport.read();
|
||||
session.updateLastInboundActivity();
|
||||
logger.debug("Recieved: {}", rawPacket);
|
||||
requestContext = new RequestContext(session.getId(), rawPacket.requestId());
|
||||
|
||||
// Step 2: Syntax validation and conversion into transport object
|
||||
rawRequest = ProtocolParser.parse(rawPacket.payload());
|
||||
RawRequest rawRequest = ProtocolParser.parse(rawPacket.payload());
|
||||
logger.debug("Parsed request to {}", rawRequest);
|
||||
|
||||
RequestContext requestContext =
|
||||
new RequestContext(session.getId(), rawPacket.requestId());
|
||||
PrimitiveRequest primitiveRequest =
|
||||
new PrimitiveRequest(
|
||||
requestContext, rawRequest.command(), rawRequest.parameters());
|
||||
logger.debug("Converted to {}", primitiveRequest);
|
||||
|
||||
// Step 3: Parse into Request and execute Request
|
||||
Request request = dispatcher.parse(primitiveRequest);
|
||||
|
||||
router.execute(request);
|
||||
} catch (EOFException e) {
|
||||
logger.info("Client disconnected");
|
||||
eventBus.publish(new DisconnectEvent(session.getId()));
|
||||
break;
|
||||
|
||||
} catch (TokenizerException | ProtocolParserException e) {
|
||||
logger.error("Error occured while parsing request. RawPacket: {}", rawPacket, e);
|
||||
|
||||
sendErrorResponse(
|
||||
new ErrorResponse(
|
||||
requestContext,
|
||||
"PARSING_ERROR",
|
||||
"Error occured during parsing. Likely due to malformed payload."));
|
||||
|
||||
} catch (UnknownCommandException e) {
|
||||
logger.error("Recieved unknown command '{}' from client", rawRequest.command(), e);
|
||||
sendErrorResponse(
|
||||
new ErrorResponse(
|
||||
requestContext,
|
||||
"UNKNOWN_COMMAND",
|
||||
"This command is unknown to the server."));
|
||||
|
||||
} catch (ResponseDispatchException e) {
|
||||
logger.error(
|
||||
"Unexpected ResponseDispatchException exception while dispatching request",
|
||||
e);
|
||||
|
||||
// TODO: Send error response to client
|
||||
} catch (IOException e) {
|
||||
logger.error("Unexpected IO exception while reading from transport", e);
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
logger.error("Unexpected RuntimeException occured", e);
|
||||
sendErrorResponse(
|
||||
new ErrorResponse(
|
||||
requestContext,
|
||||
"INTERNAL_ERROR",
|
||||
"Unexpected internal server error occured."));
|
||||
logger.error("Unexpected exception while reading from transport", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helperfunction to send ErrorResponse to client
|
||||
*
|
||||
* @param response to send to the client
|
||||
*/
|
||||
private void sendErrorResponse(ErrorResponse response) {
|
||||
PrimitiveResponse primitiveResponse = ResponseEncoder.encode(response);
|
||||
try {
|
||||
session.getResponseQueue().put(primitiveResponse);
|
||||
} catch (InterruptedException e) {
|
||||
logger.error("Got interrupted while sending ErrorResponse to client.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.sessions;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.PrimitiveResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.response.PrimitiveResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.RawPacket;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.TransportLayer;
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -50,8 +50,8 @@ public class TcpTransport implements TransportLayer {
|
||||
int requestId = data.requestId();
|
||||
byte[] rawPayload = data.payload().getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
out.writeInt(rawPayload.length);
|
||||
out.writeInt(requestId);
|
||||
out.writeInt(rawPayload.length);
|
||||
out.write(rawPayload);
|
||||
out.flush();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.parser.tokenizer;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.tokenizer;
|
||||
|
||||
/** Represents a raw (unclassified) token in the tokenizer. */
|
||||
public record RawToken(RawTokenType type, String value, int line, int column) {}
|
||||
@@ -1,4 +1,4 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.parser.tokenizer;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.tokenizer;
|
||||
|
||||
public enum RawTokenType {
|
||||
WORD,
|
||||
@@ -1,4 +1,4 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.parser.tokenizer;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.tokenizer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -1,4 +1,4 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.parser.tokenizer;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.tokenizer;
|
||||
|
||||
/** Represents a token in the tokenizer. */
|
||||
public record Token(TokenType type, String value, int line, int column) {}
|
||||
@@ -1,4 +1,4 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.parser.tokenizer;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.tokenizer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -1,4 +1,4 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.parser.tokenizer;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.tokenizer;
|
||||
|
||||
/** Enumeration of token types used in the tokenizer. */
|
||||
public enum TokenType {
|
||||
@@ -1,4 +1,4 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.parser.tokenizer;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.tokenizer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.parser.tokenizer;
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.tokenizer;
|
||||
|
||||
/** Exception thrown during tokenization. */
|
||||
public class TokenizerException extends RuntimeException {
|
||||
|
Before Width: | Height: | Size: 6.3 MiB |
|
Before Width: | Height: | Size: 6.2 MiB |
|
Before Width: | Height: | Size: 902 KiB |
|
Before Width: | Height: | Size: 701 KiB |
|
Before Width: | Height: | Size: 651 KiB |
|
Before Width: | Height: | Size: 665 KiB |
|
Before Width: | Height: | Size: 659 KiB |
|
Before Width: | Height: | Size: 660 KiB |
|
Before Width: | Height: | Size: 657 KiB |
|
Before Width: | Height: | Size: 659 KiB |
|
Before Width: | Height: | Size: 661 KiB |
|
Before Width: | Height: | Size: 658 KiB |
|
Before Width: | Height: | Size: 663 KiB |
|
Before Width: | Height: | Size: 662 KiB |
|
Before Width: | Height: | Size: 663 KiB |
|
Before Width: | Height: | Size: 657 KiB |
|
Before Width: | Height: | Size: 664 KiB |
|
Before Width: | Height: | Size: 665 KiB |