Compare commits
19 Commits
disabling_
...
main
Author | SHA1 | Date |
---|---|---|
Marcela Ruiz | e437b35529 | |
schrom01 | 7a5cd0afa8 | |
schrom01 | fed29883bc | |
schrom01 | fc6a91d3a8 | |
schrom01 | e460487550 | |
schrom01 | 9b993c3b93 | |
Andrin Fassbind | 87782ae967 | |
Andrin Fassbind | 6a0a394ccc | |
schrom01 | 9f3345c48c | |
schrom01 | a0ceb0ad1c | |
schrom01 | 049bd399bd | |
Andrin Fassbind | daf8e2bf95 | |
Roman Schenk | a603f05135 | |
fassband | bfe703c7d8 | |
Andrin Fassbind | 40e3e14666 | |
Andrin Fassbind | a189fedd76 | |
schrom01 | af94f7ae00 | |
Andrin Fassbind | 6498b8ab33 | |
Andrin Fassbind | 77e9cf6163 |
Binary file not shown.
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 133 KiB |
81
README.md
81
README.md
|
@ -1,2 +1,79 @@
|
||||||
# Uebung-hk1-Schrom01-Fassband-Brandleo
|
# Übung – Multichat
|
||||||
Übung – Multichat
|
Uebung-hk1-Schrom01-Fassband-Brandleo
|
||||||
|
|
||||||
|
|
||||||
|
# Most important Problems
|
||||||
|
## Our 5 most important functional Errors
|
||||||
|
### 1. Multiple Server Connections not possible (Issue [#4](https://github.zhaw.ch/PM2-IT21bWIN-ruiz-mach-krea/Uebung-hk1-Schrom01-Fassband-Brandleo/issues/4))
|
||||||
|
If a client was connected to the server, it was not possible to connect another client. The Server created a new instance of ServerConnectionHandler which was in a endless loop to receive data.
|
||||||
|
<br>
|
||||||
|
Now we changed the ServerConnectionHandler to implement Interface runnable. If a new client connects to the Server a new Instance of ServerConnectionHandler is created and Method start is called in a new Thread. This makes it possible to connect multiple Clients to the server. The new Thread is alive as long as the client is connected.
|
||||||
|
|
||||||
|
### 2. Private Message not visible for Sender (Issue [#28](https://github.zhaw.ch/PM2-IT21bWIN-ruiz-mach-krea/Uebung-hk1-Schrom01-Fassband-Brandleo/issues/28))
|
||||||
|
If a Client had sent a Message it was only sent to the server but not displayed directly in the chat window. So only if the message was sent to all clients or if the sender was also the receiver, the message was visible for the sender.
|
||||||
|
<br>
|
||||||
|
We discussed two different solutions:
|
||||||
|
<br>
|
||||||
|
- Send Messages also to the sender if it's not sent to all clients and if the sender is not the receiver.
|
||||||
|
- Client checks if a private message is sent and when yes the client adds the message to it's message List.
|
||||||
|
Both solutions would have the same result. The message would be in the Message List of the sender.
|
||||||
|
We decided to realise the first solution, because in case of saving the message Lists on server side (as possible extension to restore messages of a user after reconnecting) the messages which are sent by a user have to be added also to the message list of the sender. So it keeps easy to implement this extension by adding those messages to the list, which are sent to a client.
|
||||||
|
|
||||||
|
### 3. Empty Messages can be sent(Issue [#7](https://github.zhaw.ch/PM2-IT21bWIN-ruiz-mach-krea/Uebung-hk1-Schrom01-Fassband-Brandleo/issues/7))
|
||||||
|
It was possible for a Client to send empty messages and they were also delivered and displayed.
|
||||||
|
<br>
|
||||||
|
We discussed two different solutions:
|
||||||
|
<br>
|
||||||
|
- If method message in chat window controller is called, only call method message of connection handler if message field is not empty.
|
||||||
|
- If method message in connection handler is called check if message is empty and return false as boolean like it's already implemented if format isn't correct.
|
||||||
|
- Do not forward empty messages on server site.
|
||||||
|
We decided to realise the second solution, because if a Client could have multiple different windows (as possible extension) it would be necessary to implement this check in each window controller with the first solution. With the third solution the server has to process invalid messages and uses resources for that. Also if a user only hets the enter button by mistake. With the second solution the connection handler returns false as feedback if the format of the message was invalid and the user sees that he did a unwanted action.
|
||||||
|
|
||||||
|
### 4. Direct Messages not possible when username has special characters (Issues [#19](https://github.zhaw.ch/PM2-IT21bWIN-ruiz-mach-krea/Uebung-hk1-Schrom01-Fassband-Brandleo/issues/19), [#29](https://github.zhaw.ch/PM2-IT21bWIN-ruiz-mach-krea/Uebung-hk1-Schrom01-Fassband-Brandleo/issues/29))
|
||||||
|
If a user wants to send a private message to a user with special characters or spaces in the username, it's not delivered because the user is not found.
|
||||||
|
We were able to solve this problem by changing the regex of the "messagePattern". Instead of using "\w" as characters (contains only letters, numbers and underscores) for the username we used "\S" (contains all characters excepting spaces). During the registration process we check if a username contains any spaces don't accept it in this case. Accepting spaces in a Username would not be possible becuase it's used to separate the username and the message when a user types a private message.
|
||||||
|
|
||||||
|
### 5. Message stays in input field after being sent (Issue [#8](https://github.zhaw.ch/PM2-IT21bWIN-ruiz-mach-krea/Uebung-hk1-Schrom01-Fassband-Brandleo/issues/8))
|
||||||
|
If a User had sent a message, the Message wasn't removed from the message field.
|
||||||
|
We decided to remove the message only if the message was sent sucessfully. So if there is a typing mistake in the username of the receiver, the user can do a correction without having to rewrite the whole message.
|
||||||
|
The message field is cleared if the method message of the connection handler returns true.
|
||||||
|
|
||||||
|
## Our 5 most important structural problems
|
||||||
|
### 1. Data Class Message (Issues [#14](https://github.zhaw.ch/PM2-IT21bWIN-ruiz-mach-krea/Uebung-hk1-Schrom01-Fassband-Brandleo/issues/14), [#49](https://github.zhaw.ch/PM2-IT21bWIN-ruiz-mach-krea/Uebung-hk1-Schrom01-Fassband-Brandleo/issues/49))
|
||||||
|
The class MessageList had multiple List objects to save the different informations about a message. We created a Message Class which will be stored in a Array List in the MessageList instance and can also be sent from the server to a client and from a client to the server.
|
||||||
|
<br>
|
||||||
|
The new class message contains the datafields type, sender, receiver, text and also the enum with the different data types. We also added a method to check if a message matches a filter, which can be used in the method getFilteredMessages in MessageList.
|
||||||
|
### 2. ClientConnectionHandler and ServerConnectionHandler have a lot in common (Issue [#11](https://github.zhaw.ch/PM2-IT21bWIN-ruiz-mach-krea/Uebung-hk1-Schrom01-Fassband-Brandleo/issues/11))
|
||||||
|
We created a superclass Connectionhandler to remove code dupplication. Common datafields and method "sendData" are now in superclass and can be used in ServerConnectionHandler and ClientConnectionHandler.
|
||||||
|
|
||||||
|
### 3. Creation of instances of ClientMessageList and ClientConnectionHandler (Issues [#18](https://github.zhaw.ch/PM2-IT21bWIN-ruiz-mach-krea/Uebung-hk1-Schrom01-Fassband-Brandleo/issues/18)[#24](https://github.zhaw.ch/PM2-IT21bWIN-ruiz-mach-krea/Uebung-hk1-Schrom01-Fassband-Brandleo/issues/24)), [#24](https://github.zhaw.ch/PM2-IT21bWIN-ruiz-mach-krea/Uebung-hk1-Schrom01-Fassband-Brandleo/issues/24))
|
||||||
|
The instances of ClientMessageList and ClientConnectionHandler had been created and stored only in ChatwindowController. Those Classes are acting as model in our application. The should not be created in a window controller. So we created methods to set those objects in chat window controller and call those methods from clientUI Class.
|
||||||
|
|
||||||
|
### 4. ClientConnectionHandler calls Methods on ChatWindowController to add new Messages (Issue [#23](https://github.zhaw.ch/PM2-IT21bWIN-ruiz-mach-krea/Uebung-hk1-Schrom01-Fassband-Brandleo/issues/23))
|
||||||
|
A Object of ChatWindowController was saved in ClientConnectionHandler and methods of window controller were called to add messages in the window. A Model should not call any methods of a window controller and should work without any controller object. We removed those method calls and added listeners to those properties in ChatWindowController to update the view if there are any changes in the models.
|
||||||
|
|
||||||
|
### 5. ChatWindowController MessagePattern and method message (Issue [#17](https://github.zhaw.ch/PM2-IT21bWIN-ruiz-mach-krea/Uebung-hk1-Schrom01-Fassband-Brandleo/issues/17))
|
||||||
|
The Method "message" in ChatWindowController had a lot of logic in it and a MessagePattern object was saved in this class for that. We moved the logic to get the receiver and the message text into class ConnectionHandler, because the ConnectionHandler has to send the message to the server and it returns a boolean to give feedback if it was successful.
|
||||||
|
|
||||||
|
# Description of our solution
|
||||||
|
|
||||||
|
### Classdiagram of our new structure
|
||||||
|
![Classdiagram of this program](./classdiagramm.svg)
|
||||||
|
|
||||||
|
|
||||||
|
### Structure of Client:
|
||||||
|
The instances of ClientConnectionHandler and ClientMessageList are not created in the ChatWindowControllerClass anymore. It's created in the ClientUI Class and the ChatWindowController Class has access to them. This makes it possible for a client to have multiple windows open or close all windows without losing the message list or closing the connection.
|
||||||
|
The ClientConnectionHandlder does't need a ChatWindowController Object now. Received Messages are added directly into the message list. This makes it possible to receive and store new messages without having a Window open.
|
||||||
|
The ChatWindowController adds listeners in the clientConnectionHandler and in ClientMessageList to refresh the view if there are new Messages or if there are any changes in the connection handler.
|
||||||
|
|
||||||
|
|
||||||
|
### Structure of protocol:
|
||||||
|
#### Datatype:
|
||||||
|
A new Class Message represents a Message which can be sent from a Client to the Server and from the Server to a Client and can also directly be stored in ClientMessageList. This makes it much easier to process the received data and to send a message because no String parsing and String building is needed.
|
||||||
|
#### Class ConnectionHandler
|
||||||
|
A new Abstract Class ConnectionHandler contains Code which is used in ClientConnectionHandler and ServerConnectionHandler. For Example Method send Data. This removes a lot of duplicated methods and data fields.
|
||||||
|
|
||||||
|
### Ideas to extend the application:
|
||||||
|
- It would be easyer to use for a User if it was possible to open a separate Window for a private Chat with a specific user.
|
||||||
|
- Users could be divided into groups, and it would be nice if a group name could be used as receiver.
|
||||||
|
- registration and login with username and password and store credentials in a Database in Server.
|
||||||
|
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 135 KiB |
|
@ -1 +0,0 @@
|
||||||
<mxfile host="Electron" modified="2022-04-08T11:09:07.435Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/16.5.1 Chrome/96.0.4664.110 Electron/16.0.7 Safari/537.36" etag="9tAKDfXeZvsv2s69LJ1W" version="16.5.1" type="device"><diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">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</diagram></mxfile>
|
|
|
@ -1,7 +1,9 @@
|
||||||
package ch.zhaw.pm2.multichat.client;
|
package ch.zhaw.pm2.multichat.client;
|
||||||
|
|
||||||
|
import ch.zhaw.pm2.multichat.protocol.ConnectionHandler;
|
||||||
import ch.zhaw.pm2.multichat.protocol.ConnectionHandler.State;
|
import ch.zhaw.pm2.multichat.protocol.ConnectionHandler.State;
|
||||||
import ch.zhaw.pm2.multichat.protocol.ChatProtocolException;
|
import ch.zhaw.pm2.multichat.protocol.ChatProtocolException;
|
||||||
|
import ch.zhaw.pm2.multichat.protocol.Message;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
|
@ -239,7 +241,7 @@ public class ChatWindowController {
|
||||||
* @param message String to be added as Error
|
* @param message String to be added as Error
|
||||||
*/
|
*/
|
||||||
public void addError(String message) {
|
public void addError(String message) {
|
||||||
messages.addMessage(new Message(Message.MessageType.ERROR, null, null, message));
|
messages.addMessage(new Message(ConnectionHandler.DATA_TYPE.DATA_TYPE_ERROR, null, null, message));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -248,7 +250,6 @@ public class ChatWindowController {
|
||||||
class WindowCloseHandler implements EventHandler<WindowEvent> {
|
class WindowCloseHandler implements EventHandler<WindowEvent> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @param event the event which occurred when Windows is closed
|
* @param event the event which occurred when Windows is closed
|
||||||
*/
|
*/
|
||||||
public void handle(WindowEvent event) {
|
public void handle(WindowEvent event) {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package ch.zhaw.pm2.multichat.client;
|
||||||
|
|
||||||
import ch.zhaw.pm2.multichat.protocol.ChatProtocolException;
|
import ch.zhaw.pm2.multichat.protocol.ChatProtocolException;
|
||||||
import ch.zhaw.pm2.multichat.protocol.ConnectionHandler;
|
import ch.zhaw.pm2.multichat.protocol.ConnectionHandler;
|
||||||
|
import ch.zhaw.pm2.multichat.protocol.Message;
|
||||||
import ch.zhaw.pm2.multichat.protocol.NetworkHandler;
|
import ch.zhaw.pm2.multichat.protocol.NetworkHandler;
|
||||||
import javafx.beans.property.SimpleIntegerProperty;
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
@ -122,7 +123,7 @@ public class ClientConnectionHandler extends ConnectionHandler implements Runnab
|
||||||
try {
|
try {
|
||||||
System.out.println("Start receiving data...");
|
System.out.println("Start receiving data...");
|
||||||
while (getConnection().isAvailable()) {
|
while (getConnection().isAvailable()) {
|
||||||
String data = getConnection().receive();
|
Message data = getConnection().receive();
|
||||||
processData(data);
|
processData(data);
|
||||||
}
|
}
|
||||||
System.out.println("Stopped receiving data");
|
System.out.println("Stopped receiving data");
|
||||||
|
@ -162,95 +163,77 @@ public class ClientConnectionHandler extends ConnectionHandler implements Runnab
|
||||||
*
|
*
|
||||||
* @param data that is received in a form of a String and then used depending on its determined cause.
|
* @param data that is received in a form of a String and then used depending on its determined cause.
|
||||||
*/
|
*/
|
||||||
private void processData(String data) {
|
private void processData(Message data) {
|
||||||
try {
|
|
||||||
Scanner scanner = new Scanner(data);
|
|
||||||
StringBuilder sender = new StringBuilder();
|
|
||||||
StringBuilder receiver = new StringBuilder();
|
|
||||||
StringBuilder type = new StringBuilder();
|
|
||||||
StringBuilder payload = new StringBuilder();
|
|
||||||
super.processData(scanner, sender, receiver, type, payload);
|
|
||||||
|
|
||||||
// dispatch operation based on type parameter
|
// dispatch operation based on type parameter
|
||||||
if (type.toString().equals(getDataTypeConnect())) {
|
if (data.getType() == DATA_TYPE.DATA_TYPE_CONNECT) {
|
||||||
System.err.println("Illegal connect request from server");
|
System.err.println("Illegal connect request from server");
|
||||||
} else if (type.toString().equals(getDataTypeConfirm())) {
|
} else if (data.getType() == DATA_TYPE.DATA_TYPE_CONFIRM) {
|
||||||
caseConfirm(sender.toString(), receiver.toString(), payload.toString());
|
caseConfirm(data);
|
||||||
} else if (type.toString().equals(getDataTypeDisconnect())) {
|
} else if (data.getType() == DATA_TYPE.DATA_TYPE_DISCONNECT) {
|
||||||
caseDisconnect(sender.toString(), receiver.toString(), payload.toString());
|
caseDisconnect(data);
|
||||||
} else if (type.toString().equals(getDataTypeMessage())) {
|
} else if (data.getType() == DATA_TYPE.DATA_TYPE_MESSAGE) {
|
||||||
caseMessage(sender.toString(), receiver.toString(), payload.toString());
|
caseMessage(data);
|
||||||
} else if (type.toString().equals(getDataTypeError())) {
|
} else if (data.getType() == DATA_TYPE.DATA_TYPE_ERROR) {
|
||||||
caseError(sender.toString(), receiver.toString(), payload.toString());
|
caseError(data);
|
||||||
} else {
|
} else {
|
||||||
System.out.println("Unknown data type received: " + type);
|
System.out.println("Unknown data type received: " + data.getType());
|
||||||
}
|
|
||||||
} catch (ChatProtocolException e) {
|
|
||||||
System.err.println("Error while processing data: " + e.getMessage());
|
|
||||||
sendData(USER_NONE, userName.get(), getDataTypeError(), e.getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void caseConfirm(String sender, String receiver, String payload) {
|
private void caseConfirm(Message data) {
|
||||||
if (state.get() == CONFIRM_CONNECT) {
|
if (state.get() == CONFIRM_CONNECT) {
|
||||||
this.userName.set(receiver);
|
this.userName.set(data.getReceiver());
|
||||||
this.serverPort.set(getConnection().getRemotePort());
|
this.serverPort.set(getConnection().getRemotePort());
|
||||||
this.serverAddress.set(getConnection().getRemoteHost());
|
this.serverAddress.set(getConnection().getRemoteHost());
|
||||||
messages.addMessage(new Message(Message.MessageType.INFO, sender, receiver, payload));
|
messages.addMessage(data);
|
||||||
System.out.println("CONFIRM: " + payload);
|
System.out.println("CONFIRM: " + data.getText());
|
||||||
this.setState(CONNECTED);
|
this.setState(CONNECTED);
|
||||||
} else if (state.get() == CONFIRM_DISCONNECT) {
|
} else if (state.get() == CONFIRM_DISCONNECT) {
|
||||||
messages.addMessage(new Message(Message.MessageType.INFO, sender, receiver, payload));
|
messages.addMessage(data);
|
||||||
System.out.println("CONFIRM: " + payload);
|
System.out.println("CONFIRM: " + data.getText());
|
||||||
this.setState(DISCONNECTED);
|
this.setState(DISCONNECTED);
|
||||||
} else {
|
} else {
|
||||||
System.err.println("Got unexpected confirm message: " + payload);
|
System.err.println("Got unexpected confirm message: " + data.getText());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initiates the disconnect sequence and sends the message with all its info.
|
* Initiates the disconnect sequence and sends the message with all its info.
|
||||||
*
|
*
|
||||||
* @param sender the sender name of the message
|
* @param data Data which has been transmitted
|
||||||
* @param receiver the receiver name of the message
|
|
||||||
* @param payload the added payload that corresponds to the message
|
|
||||||
*/
|
*/
|
||||||
private void caseDisconnect(String sender, String receiver, String payload) {
|
private void caseDisconnect(Message data) {
|
||||||
if (state.get() == DISCONNECTED) {
|
if (state.get() == DISCONNECTED) {
|
||||||
System.out.println("DISCONNECT: Already in disconnected: " + payload);
|
System.out.println("DISCONNECT: Already in disconnected: " + data.getText());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
messages.addMessage(new Message(Message.MessageType.INFO, sender, receiver, payload));
|
messages.addMessage(data);
|
||||||
System.out.println("DISCONNECT: " + payload);
|
System.out.println("DISCONNECT: " + data.getText());
|
||||||
this.setState(DISCONNECTED);
|
this.setState(DISCONNECTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initiates the procedure to send a new message and sends one as such.
|
* Initiates the procedure to send a new message and sends one as such.
|
||||||
*
|
*
|
||||||
* @param sender the sender name of the message
|
* @param data Data which has been transmitted
|
||||||
* @param receiver the receiver name of the message
|
|
||||||
* @param payload the added payload that corresponds to the message
|
|
||||||
*/
|
*/
|
||||||
private void caseMessage(String sender, String receiver, String payload) {
|
private void caseMessage(Message data) {
|
||||||
if (state.get() != CONNECTED) {
|
if (state.get() != CONNECTED) {
|
||||||
System.out.println("MESSAGE: Illegal state " + state + " for message: " + payload);
|
System.out.println("MESSAGE: Illegal state " + state + " for message: " + data.getText());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
messages.addMessage(new Message(Message.MessageType.MESSAGE, sender, receiver, payload));
|
messages.addMessage(data);
|
||||||
System.out.println("MESSAGE: From " + sender + " to " + receiver + ": " + payload);
|
System.out.println("MESSAGE: From " + data.getSender() + " to " + data.getReceiver() + ": " + data.getText());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the message as an error message and displays it as such as well.
|
* Stores the message as an error message and displays it as such as well.
|
||||||
*
|
*
|
||||||
* @param sender the sender name of the message
|
* @param data Data which has been transmitted
|
||||||
* @param receiver the receiver name of the message
|
|
||||||
* @param payload the added payload that corresponds to the message
|
|
||||||
*/
|
*/
|
||||||
private void caseError(String sender, String receiver, String payload) {
|
private void caseError(Message data) {
|
||||||
messages.addMessage(new Message(Message.MessageType.ERROR, sender, receiver, payload));
|
messages.addMessage(data);
|
||||||
System.out.println("ERROR: " + payload);
|
System.out.println("ERROR: " + data.getText());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package ch.zhaw.pm2.multichat.client;
|
package ch.zhaw.pm2.multichat.client;
|
||||||
|
|
||||||
|
import ch.zhaw.pm2.multichat.protocol.ConnectionHandler;
|
||||||
|
import ch.zhaw.pm2.multichat.protocol.Message;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -15,6 +17,7 @@ public class ClientMessageList {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a new message to ArrayList and also informs Listener.
|
* Adds a new message to ArrayList and also informs Listener.
|
||||||
|
*
|
||||||
* @param message that should be added
|
* @param message that should be added
|
||||||
*/
|
*/
|
||||||
public void addMessage(Message message) {
|
public void addMessage(Message message) {
|
||||||
|
@ -35,12 +38,10 @@ public class ClientMessageList {
|
||||||
for (Message message : messages) {
|
for (Message message : messages) {
|
||||||
if (showAll || message.matchesFilter(filter)) {
|
if (showAll || message.matchesFilter(filter)) {
|
||||||
switch (message.getType()) {
|
switch (message.getType()) {
|
||||||
case MESSAGE ->
|
case DATA_TYPE_MESSAGE -> result.append(String.format("[%s -> %s] %s\n", message.getSender(), message.getReceiver(), message.getText()));
|
||||||
result.append(String.format("[%s -> %s] %s\n", message.getSender(), message.getReceiver(), message.getText()));
|
case DATA_TYPE_ERROR -> result.append(String.format("[ERROR] %s\n", message.getText()));
|
||||||
case ERROR -> result.append(String.format("[ERROR] %s\n", message.getText()));
|
case DATA_TYPE_CONFIRM, DATA_TYPE_DISCONNECT, DATA_TYPE_CONNECT -> result.append(String.format("[INFO] %s\n", message.getText()));
|
||||||
case INFO -> result.append(String.format("[INFO] %s\n", message.getText()));
|
default -> result.append(String.format("[ERROR] %s\n", "Unexpected message type: " + message.getType()));
|
||||||
default ->
|
|
||||||
result.append(String.format("[ERROR] %s\n", "Unexpected message type: " + message.getType()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
<mxfile host="Electron" modified="2022-04-08T10:11:45.562Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/17.2.1 Chrome/96.0.4664.174 Electron/16.1.0 Safari/537.36" etag="zznHm94x3InQRhTWOwwy" version="17.2.1" type="device"><diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">7Ztvc6o4FMY/jTPdF+4I+K8vRWvbu91uW/R2391JIWrWQFxIa+2nvycQQCRWqhfdbZnptOQQCJzfc0J4qjWj775e+mgx+5M5mNb0hvNaMwY1Xe90NPgtAqsooHVbzSgy9YkjY2nAIm9YBhsy+kwcHGQ6csYoJ4ts0Gaeh22eiSHfZ8tstwmj2VEXaIpzActGNB99JA6fRdGu3knjV5hMZ/HIWvs82uOiuLO8k2CGHLZcCxkXNaPvM8ajLfe1j6lIXpyXx+vVI72Zty+/3Qf/orH5x+j2ez062fAjhyS34GOP733qt/lkePW9+c/fi7vh/bJx2Xv6VpeHNF4QfZb5usV8yfz5FfIcin1553wVpzNYEpciD1rmhHncknsgGSaiZOrBtg1XCUca5gv2OQESPbmDswVE7Rmhzg1asWdxLwFH9jxumTPmkzc4LaKwS4MA7Pa5FJXezvSwxJEQbkDUxwH0uYsTpCWhGxRw2cdmlKJFQJ7CCxZdXORPiWcyzpkbn4g9ew52ZCshHja4z+aJhsTxBbFIfCIb+HVNlBLTJWYu5v4KusRFJ6nIkjNiAS5T/TYbMjZb066utWXdyJqZJqdORnuAGkPeFHKQDKcb2fG0Zn48ra0ab2M4RIG7hzg2RRaDdTXCxtqdpqFQox/Qq5bTa003BxfD3vhm9KM3GDxcWFbN6F17mPccBzQQ1DrmhHggqM4gJ2bgwdeES/GEb5VtsEA28aY3YZ9BM408yISIEINjJzSUzIw4DvZCSXHEUaQ6oaMFIx4PM9Yy4Qfy2m/83qq14IL60NbSNvyI7j7vMw/Uh0goMwySXmIh60wFNvNqfLfid6txlaW8U33viC8jg48y199jfvfXwwiAi9R8YtDvTEsz7lK5eRQ5tPQTy6GpkMMGbkrCB5RMjaacvHdowQWq4nQx/JHQxqCu5QRi5AViKMRA0ROmdywgnDBxfj/quyGSXTooC2qnWwxqtySmLVWJ2z6GZ4mFfbiPM5GVqM5/gz9ykRLtq+ltKpIzgo1pmqVPVP5lYe8ecWofz/SuaQ0pHd+a4/H1feeHO1csP2PuEvuXhZ2DWJT/VtiadsSZW0lbuXhjC+z1o3c/mBnPkFy1Zddwel9Z/+lxlQqKqsAoONWXpgLlcm5DBTMWcA+5sL9ncR9IVAr4dQpoN0+sAKOAAirGBzE+L2oZlLVK1zo5yBvP8WLGUqMyln6dsdRq7naWdKVMYkvqY85Sq/U/cpa6OcHWg1CqFrPnWDx6rPXmJ3Yb9vERkno/xFdSa680X0n1HKqchAOxKvwhJdayrAQ97w/VM0+ejJXw2Uq3NKztok+K0qo17xFtcK1oHmL8lEZT+QqgMnHNJSJ8yPzqLaDwW0BE/kOWz3E5K41dEvReEKEimSHhJ8YoRhXX4lwVJs5xubbVXPuUBdipoO4FVeHLHBdq/pUdoE4xv2IBv4OUVFz34qrwYo7LNf9mm3KVDnuINrZaK7LFyCaftzkZ2XMVWVtMwiHQFwZHVziL4myeerWk5/9Dtnv1Wxmnm8bpDm1s91K3GqfJHB6bHN0vaZyqRavnRFsPEsv0K5qlO/T3Tun/J8xS9fVVZmkJWI9olqovb7tZuubCbFTzZ/XZSuB7TNdUfX1KAybAnnPmQDaB6ehrLxT3oXpy91RX2i8+tjF5iVb+o4pmUZqnN0l1pfFSuaSHgj25S6ornZfKJj2I6sltUl3pukwxf8Au4zhxSkVOKqxFsZ7cJY2/BbcFqzBLv7xLuter68ldUkP5wfPKJt2TZ4k2KTTT71NHBlX6rXTj4ic=</diagram></mxfile>
|
|
|
@ -3,23 +3,15 @@ package ch.zhaw.pm2.multichat.protocol;
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
import java.util.Scanner;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This abstract class is the superclass for ClientConnectionHandler and ServerConnectionHandler
|
* This abstract class is the superclass for ClientConnectionHandler and ServerConnectionHandler
|
||||||
* It offers the DATA_TYPE Strings and a {@link State} enum for all valid connection states.
|
* It offers the DATA_TYPE for message and a {@link State} enum for all valid connection states.
|
||||||
* Shared methods are implemented in this class as well {@link ConnectionHandler#sendData(String, String, String, String)} {@link ConnectionHandler#processData(Scanner, StringBuilder, StringBuilder, StringBuilder, StringBuilder)}
|
* Shared methods are implemented in this class as well {@link ConnectionHandler#sendData(String, String, DATA_TYPE, String)}
|
||||||
*/
|
*/
|
||||||
public abstract class ConnectionHandler {
|
public abstract class ConnectionHandler {
|
||||||
private NetworkHandler.NetworkConnection<String> connection;
|
private NetworkHandler.NetworkConnection<String> connection;
|
||||||
|
|
||||||
// Data types used for the Chat Protocol
|
|
||||||
private static final String DATA_TYPE_CONNECT = "CONNECT";
|
|
||||||
private static final String DATA_TYPE_CONFIRM = "CONFIRM";
|
|
||||||
private static final String DATA_TYPE_DISCONNECT = "DISCONNECT";
|
|
||||||
private static final String DATA_TYPE_MESSAGE = "MESSAGE";
|
|
||||||
private static final String DATA_TYPE_ERROR = "ERROR";
|
|
||||||
|
|
||||||
public static final String USER_NONE = "";
|
public static final String USER_NONE = "";
|
||||||
public static final String USER_ALL = "*";
|
public static final String USER_ALL = "*";
|
||||||
|
|
||||||
|
@ -28,39 +20,44 @@ public abstract class ConnectionHandler {
|
||||||
NEW, CONFIRM_CONNECT, CONNECTED, CONFIRM_DISCONNECT, DISCONNECTED, ERROR;
|
NEW, CONFIRM_CONNECT, CONNECTED, CONFIRM_DISCONNECT, DISCONNECTED, ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// DATA_TYPE of the messages
|
||||||
* @return {@link ConnectionHandler#DATA_TYPE_CONNECT}
|
public enum DATA_TYPE {
|
||||||
*/
|
DATA_TYPE_CONNECT, DATA_TYPE_CONFIRM, DATA_TYPE_DISCONNECT, DATA_TYPE_MESSAGE, DATA_TYPE_ERROR
|
||||||
public static String getDataTypeConnect() {
|
|
||||||
return DATA_TYPE_CONNECT;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {@link ConnectionHandler#DATA_TYPE_CONFIRM}
|
* @return {@link DATA_TYPE}
|
||||||
*/
|
*/
|
||||||
public static String getDataTypeConfirm() {
|
public static DATA_TYPE getDataTypeConnect() {
|
||||||
return DATA_TYPE_CONFIRM;
|
return DATA_TYPE.DATA_TYPE_CONNECT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {@link ConnectionHandler#DATA_TYPE_DISCONNECT}
|
* @return {@link DATA_TYPE}
|
||||||
*/
|
*/
|
||||||
public static String getDataTypeDisconnect() {
|
public static DATA_TYPE getDataTypeConfirm() {
|
||||||
return DATA_TYPE_DISCONNECT;
|
return DATA_TYPE.DATA_TYPE_CONFIRM;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {@link ConnectionHandler#DATA_TYPE_MESSAGE
|
* @return {@link DATA_TYPE}
|
||||||
*/
|
*/
|
||||||
public static String getDataTypeMessage() {
|
public static DATA_TYPE getDataTypeDisconnect() {
|
||||||
return DATA_TYPE_MESSAGE;
|
return DATA_TYPE.DATA_TYPE_DISCONNECT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {@link ConnectionHandler#DATA_TYPE_ERROR}
|
* @return {@link DATA_TYPE}
|
||||||
*/
|
*/
|
||||||
public static String getDataTypeError() {
|
public static DATA_TYPE getDataTypeMessage() {
|
||||||
return DATA_TYPE_ERROR;
|
return DATA_TYPE.DATA_TYPE_MESSAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@link DATA_TYPE}
|
||||||
|
*/
|
||||||
|
public static DATA_TYPE getDataTypeError() {
|
||||||
|
return DATA_TYPE.DATA_TYPE_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -79,40 +76,6 @@ public abstract class ConnectionHandler {
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This method reads the data when a ConnectionHandler receives it. It tries to read out the sender, receiver, type and payload.
|
|
||||||
* If the data does not contain the expected number of lines, it throws a {@link ChatProtocolException}
|
|
||||||
*
|
|
||||||
* @param scanner to read data
|
|
||||||
* @param sender of the data
|
|
||||||
* @param receiver for the data
|
|
||||||
* @param type of data
|
|
||||||
* @param payload the data sent
|
|
||||||
* @throws ChatProtocolException if the data does not contain the expected number of lines
|
|
||||||
*/
|
|
||||||
protected void processData(Scanner scanner, StringBuilder sender, StringBuilder receiver, StringBuilder type, StringBuilder payload) throws ChatProtocolException {
|
|
||||||
// parse data content
|
|
||||||
if (scanner.hasNextLine()) {
|
|
||||||
sender.append(scanner.nextLine());
|
|
||||||
} else if (scanner.hasNextLine()) {
|
|
||||||
receiver.append(scanner.nextLine());
|
|
||||||
} else {
|
|
||||||
throw new ChatProtocolException("No Sender found");
|
|
||||||
}
|
|
||||||
if (scanner.hasNextLine()) {
|
|
||||||
receiver.append(scanner.nextLine());
|
|
||||||
} else {
|
|
||||||
throw new ChatProtocolException("No Reciever found");
|
|
||||||
}
|
|
||||||
if (scanner.hasNextLine()) {
|
|
||||||
type.append(scanner.nextLine());
|
|
||||||
} else {
|
|
||||||
throw new ChatProtocolException("No Type found");
|
|
||||||
}
|
|
||||||
if (scanner.hasNextLine()) {
|
|
||||||
payload.append(scanner.nextLine());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method gets called to send data via the socket defined in the {@link NetworkHandler.NetworkConnection}
|
* This method gets called to send data via the socket defined in the {@link NetworkHandler.NetworkConnection}
|
||||||
|
@ -122,14 +85,10 @@ public abstract class ConnectionHandler {
|
||||||
* @param type of the data
|
* @param type of the data
|
||||||
* @param payload of the data
|
* @param payload of the data
|
||||||
*/
|
*/
|
||||||
protected void sendData(String sender, String receiver, String type, String payload) {
|
protected void sendData(String sender, String receiver, DATA_TYPE type, String payload) {
|
||||||
if (connection.isAvailable()) {
|
if (connection.isAvailable()) {
|
||||||
String data = sender + "\n" +
|
|
||||||
receiver + "\n" +
|
|
||||||
type + "\n" +
|
|
||||||
payload + "\n";
|
|
||||||
try {
|
try {
|
||||||
connection.send(data);
|
connection.send(new Message(type, sender, receiver, payload));
|
||||||
} catch (SocketException e) {
|
} catch (SocketException e) {
|
||||||
System.err.println("Connection closed: " + e.getMessage());
|
System.err.println("Connection closed: " + e.getMessage());
|
||||||
} catch (EOFException e) {
|
} catch (EOFException e) {
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package ch.zhaw.pm2.multichat.client;
|
package ch.zhaw.pm2.multichat.protocol;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Message object represents one Message of a client. Can be stored in ClientMessageList.
|
* A Message object represents one Message of a client. Can be stored in ClientMessageList.
|
||||||
*/
|
*/
|
||||||
public class Message {
|
public class Message implements Serializable {
|
||||||
private final MessageType type;
|
private final ConnectionHandler.DATA_TYPE type;
|
||||||
private final String sender;
|
private final String sender;
|
||||||
private final String receiver;
|
private final String receiver;
|
||||||
private final String text;
|
private final String text;
|
||||||
|
@ -17,7 +19,7 @@ public class Message {
|
||||||
* @param receiver The User who should receive the message.
|
* @param receiver The User who should receive the message.
|
||||||
* @param text The Text of the message.
|
* @param text The Text of the message.
|
||||||
*/
|
*/
|
||||||
public Message(MessageType type, String sender, String receiver, String text) {
|
public Message(ConnectionHandler.DATA_TYPE type, String sender, String receiver, String text) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.sender = sender;
|
this.sender = sender;
|
||||||
this.receiver = receiver;
|
this.receiver = receiver;
|
||||||
|
@ -39,7 +41,7 @@ public class Message {
|
||||||
/**
|
/**
|
||||||
* @return The type of the message.
|
* @return The type of the message.
|
||||||
*/
|
*/
|
||||||
public MessageType getType() {
|
public ConnectionHandler.DATA_TYPE getType() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,10 +66,4 @@ public class Message {
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Enumeration of Message Types.
|
|
||||||
*/
|
|
||||||
public enum MessageType {
|
|
||||||
INFO, MESSAGE, ERROR;
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -38,7 +38,7 @@ import java.util.Objects;
|
||||||
* on the client side, usually the result is displayed to the user). After processing is finished the
|
* on the client side, usually the result is displayed to the user). After processing is finished the
|
||||||
* process calls {@link NetworkConnection#receive()} again to wait for the next request.
|
* process calls {@link NetworkConnection#receive()} again to wait for the next request.
|
||||||
* </li>
|
* </li>
|
||||||
* <li>sending data: call {@link NetworkConnection#send(Serializable data)}, which sends the given data
|
* <li>sending data: call {@link NetworkConnection#send(Message)}, which sends the given data
|
||||||
* object to the remote side. The method returns as soon the object has been transmitted.
|
* object to the remote side. The method returns as soon the object has been transmitted.
|
||||||
* <b>Important: {@link NetworkConnection} is not thread safe</b>, therefore make sure that only one thread
|
* <b>Important: {@link NetworkConnection} is not thread safe</b>, therefore make sure that only one thread
|
||||||
* at a time is sending data.</li>
|
* at a time is sending data.</li>
|
||||||
|
@ -270,7 +270,7 @@ public class NetworkHandler {
|
||||||
* on the client side, usually the result is displayed to the user). After processing is finished the
|
* on the client side, usually the result is displayed to the user). After processing is finished the
|
||||||
* process calls {@link NetworkConnection#receive()} again to wait for the next request.
|
* process calls {@link NetworkConnection#receive()} again to wait for the next request.
|
||||||
* </li>
|
* </li>
|
||||||
* <li>sending data: call {@link NetworkConnection#send(Serializable data)}, which sends the given data
|
* <li>sending data: call {@link NetworkConnection#send(Message)}, which sends the given data
|
||||||
* object to the remote side. The method returns as soon the object has been transmitted.
|
* object to the remote side. The method returns as soon the object has been transmitted.
|
||||||
* <b>Important: {@link NetworkConnection} is not thread safe</b>, therefore make sure that only one thread
|
* <b>Important: {@link NetworkConnection} is not thread safe</b>, therefore make sure that only one thread
|
||||||
* at a time is sending data.
|
* at a time is sending data.
|
||||||
|
@ -305,7 +305,7 @@ public class NetworkHandler {
|
||||||
* @param data data object of type T to be submitted through the connection.
|
* @param data data object of type T to be submitted through the connection.
|
||||||
* @throws IOException if an error occurs (e.g. connection interrupted while sending, ...)
|
* @throws IOException if an error occurs (e.g. connection interrupted while sending, ...)
|
||||||
*/
|
*/
|
||||||
public synchronized void send(T data) throws IOException {
|
public synchronized void send(Message data) throws IOException {
|
||||||
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
|
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
|
||||||
outputStream.writeObject(data);
|
outputStream.writeObject(data);
|
||||||
}
|
}
|
||||||
|
@ -320,9 +320,9 @@ public class NetworkHandler {
|
||||||
* @throws IOException if an error occours. (e.g. terminated locally/remotely) see above.
|
* @throws IOException if an error occours. (e.g. terminated locally/remotely) see above.
|
||||||
* @throws ClassNotFoundException if the data object received does not match any class in the local classpath
|
* @throws ClassNotFoundException if the data object received does not match any class in the local classpath
|
||||||
*/
|
*/
|
||||||
public T receive() throws IOException, ClassNotFoundException {
|
public Message receive() throws IOException, ClassNotFoundException {
|
||||||
ObjectInputStream inputStream = new ObjectInputStream(this.socket.getInputStream());
|
ObjectInputStream inputStream = new ObjectInputStream(this.socket.getInputStream());
|
||||||
return (T) inputStream.readObject();
|
return (Message) inputStream.readObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -85,7 +85,7 @@ public class Server {
|
||||||
private void start() {
|
private void start() {
|
||||||
System.out.println("Server started.");
|
System.out.println("Server started.");
|
||||||
try {
|
try {
|
||||||
while (true) {
|
while (networkServer.isAvailable()) {
|
||||||
NetworkHandler.NetworkConnection<String> connection = networkServer.waitForConnection();
|
NetworkHandler.NetworkConnection<String> connection = networkServer.waitForConnection();
|
||||||
ServerConnectionHandler connectionHandler = new ServerConnectionHandler(connection, connections);
|
ServerConnectionHandler connectionHandler = new ServerConnectionHandler(connection, connections);
|
||||||
new Thread(connectionHandler).start();
|
new Thread(connectionHandler).start();
|
||||||
|
|
|
@ -5,6 +5,7 @@ import ch.zhaw.pm2.multichat.protocol.ConnectionHandler;
|
||||||
|
|
||||||
import static ch.zhaw.pm2.multichat.protocol.ConnectionHandler.State.*;
|
import static ch.zhaw.pm2.multichat.protocol.ConnectionHandler.State.*;
|
||||||
|
|
||||||
|
import ch.zhaw.pm2.multichat.protocol.Message;
|
||||||
import ch.zhaw.pm2.multichat.protocol.NetworkHandler;
|
import ch.zhaw.pm2.multichat.protocol.NetworkHandler;
|
||||||
|
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
|
@ -63,8 +64,6 @@ public class ServerConnectionHandler extends ConnectionHandler implements Runnab
|
||||||
*
|
*
|
||||||
* @param connection representing the socket connection between server and client
|
* @param connection representing the socket connection between server and client
|
||||||
* @param registry map containing all active connections between server and clients
|
* @param registry map containing all active connections between server and clients
|
||||||
|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
public ServerConnectionHandler(NetworkHandler.NetworkConnection<String> connection,
|
public ServerConnectionHandler(NetworkHandler.NetworkConnection<String> connection,
|
||||||
Map<String, ServerConnectionHandler> registry) {
|
Map<String, ServerConnectionHandler> registry) {
|
||||||
|
@ -84,7 +83,7 @@ public class ServerConnectionHandler extends ConnectionHandler implements Runnab
|
||||||
try {
|
try {
|
||||||
System.out.println("Start receiving data...");
|
System.out.println("Start receiving data...");
|
||||||
while (getConnection().isAvailable() && !(state == ERROR)) {
|
while (getConnection().isAvailable() && !(state == ERROR)) {
|
||||||
String data = getConnection().receive();
|
Message data = getConnection().receive();
|
||||||
processData(data);
|
processData(data);
|
||||||
}
|
}
|
||||||
System.out.println("Stopped receiving data");
|
System.out.println("Stopped receiving data");
|
||||||
|
@ -136,28 +135,21 @@ public class ServerConnectionHandler extends ConnectionHandler implements Runnab
|
||||||
*
|
*
|
||||||
* @param data received by the server
|
* @param data received by the server
|
||||||
*/
|
*/
|
||||||
private void processData(String data) {
|
private void processData(Message data) {
|
||||||
try {
|
try {
|
||||||
Scanner scanner = new Scanner(data);
|
|
||||||
StringBuilder sender = new StringBuilder();
|
|
||||||
StringBuilder receiver = new StringBuilder();
|
|
||||||
StringBuilder type = new StringBuilder();
|
|
||||||
StringBuilder payload = new StringBuilder();
|
|
||||||
super.processData(scanner, sender, receiver, type, payload);
|
|
||||||
|
|
||||||
// dispatch operation based on type parameter
|
// dispatch operation based on type parameter
|
||||||
if (type.toString().equals(getDataTypeConnect())) {
|
if (data.getType() == DATA_TYPE.DATA_TYPE_CONNECT) {
|
||||||
caseConnect(sender.toString());
|
caseConnect(data);
|
||||||
} else if (type.toString().equals(getDataTypeConfirm())) {
|
} else if (data.getType() == DATA_TYPE.DATA_TYPE_CONFIRM) {
|
||||||
System.out.println("Not expecting to receive a CONFIRM request from client");
|
System.out.println("Not expecting to receive a CONFIRM request from client");
|
||||||
} else if (type.toString().equals(getDataTypeDisconnect())) {
|
} else if (data.getType() == DATA_TYPE.DATA_TYPE_DISCONNECT) {
|
||||||
caseDisconnect();
|
caseDisconnect();
|
||||||
} else if (type.toString().equals(getDataTypeMessage())) {
|
} else if (data.getType() == DATA_TYPE.DATA_TYPE_MESSAGE) {
|
||||||
caseMessage(sender.toString(), receiver.toString(), type.toString(), payload.toString());
|
caseMessage(data);
|
||||||
} else if (type.toString().equals(getDataTypeError())) {
|
} else if (data.getType() == DATA_TYPE.DATA_TYPE_ERROR) {
|
||||||
System.err.println("Received error from client (" + sender + "): " + payload);
|
System.err.println("Received error from client (" + data.getSender() + "): " + data.getText());
|
||||||
} else {
|
} else {
|
||||||
System.err.println("Unknown data type received: " + type);
|
System.err.println("Unknown data type received: " + data.getType());
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (ChatProtocolException e) {
|
} catch (ChatProtocolException e) {
|
||||||
|
@ -167,25 +159,26 @@ public class ServerConnectionHandler extends ConnectionHandler implements Runnab
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is called by method {@link ServerConnectionHandler#processData(String)}
|
* This method is called by method {@link ServerConnectionHandler#processData(Message)}
|
||||||
* Checks if username is valid. if valid sends response to client with confirmation.
|
* Checks if username is valid. if valid sends response to client with confirmation.
|
||||||
*
|
*
|
||||||
* @param sender of the payload
|
* @param data sent
|
||||||
* @throws ChatProtocolException if username not valid
|
* @throws ChatProtocolException if username not valid
|
||||||
*/
|
*/
|
||||||
private void caseConnect(String sender) throws ChatProtocolException {
|
private void caseConnect(Message data) throws ChatProtocolException {
|
||||||
if (this.state != NEW) throw new ChatProtocolException("Illegal state for connect request: " + state);
|
if (this.state != NEW) throw new ChatProtocolException("Illegal state for connect request: " + state);
|
||||||
if (sender.isBlank()) sender = this.userName;
|
|
||||||
//if username not valid
|
//if username not valid
|
||||||
if (connectionRegistry.containsKey(sender)) {
|
if (connectionRegistry.containsKey(data.getSender())) {
|
||||||
state = ERROR;
|
state = ERROR;
|
||||||
System.out.println(String.format("Connecting failed for new Client with IP:Port <%s:%d>.\nReason: Name already taken.",
|
System.out.println(String.format("Connecting failed for new Client with IP:Port <%s:%d>.\nReason: Name already taken.",
|
||||||
getConnection().getRemoteHost(),
|
getConnection().getRemoteHost(),
|
||||||
getConnection().getRemotePort()));
|
getConnection().getRemotePort()));
|
||||||
throw new ChatProtocolException("User name already taken: " + sender);
|
throw new ChatProtocolException("User name already taken: " + data.getSender());
|
||||||
}
|
}
|
||||||
//if username valid
|
//if username valid
|
||||||
this.userName = sender;
|
if (!data.getSender().isBlank()) {
|
||||||
|
this.userName = data.getSender();
|
||||||
|
}
|
||||||
connectionRegistry.put(userName, this);
|
connectionRegistry.put(userName, this);
|
||||||
sendData(USER_NONE, userName, getDataTypeConfirm(), "Registration successful for " + userName);
|
sendData(USER_NONE, userName, getDataTypeConfirm(), "Registration successful for " + userName);
|
||||||
this.state = CONNECTED;
|
this.state = CONNECTED;
|
||||||
|
@ -196,7 +189,7 @@ public class ServerConnectionHandler extends ConnectionHandler implements Runnab
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is called by method {@link ServerConnectionHandler#processData(String)}
|
* This method is called by method {@link ServerConnectionHandler#processData(Message)}
|
||||||
* Disconnects connection by removing connection from registry and calling method {@link ServerConnectionHandler#stopReceiving()} to terminate socket.
|
* Disconnects connection by removing connection from registry and calling method {@link ServerConnectionHandler#stopReceiving()} to terminate socket.
|
||||||
*
|
*
|
||||||
* @throws ChatProtocolException if state already DISCONNECTED.
|
* @throws ChatProtocolException if state already DISCONNECTED.
|
||||||
|
@ -213,30 +206,27 @@ public class ServerConnectionHandler extends ConnectionHandler implements Runnab
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is called by method {@link ServerConnectionHandler#processData(String)}
|
* This method is called by method {@link ServerConnectionHandler#processData(Message)}
|
||||||
* Checks if broadcast or unicast. Sends data accordingly
|
* Checks if broadcast or unicast. Sends data accordingly
|
||||||
*
|
*
|
||||||
* @param sender who sent data
|
* @param data to transmit
|
||||||
* @param receiver to receive data
|
|
||||||
* @param type of message
|
|
||||||
* @param payload data to transmit
|
|
||||||
* @throws ChatProtocolException if state not equal to CONNECT
|
* @throws ChatProtocolException if state not equal to CONNECT
|
||||||
*/
|
*/
|
||||||
private void caseMessage(String sender, String receiver, String type, String payload) throws ChatProtocolException {
|
private void caseMessage(Message data) throws ChatProtocolException {
|
||||||
if (state != CONNECTED) throw new ChatProtocolException("Illegal state for message request: " + state);
|
if (state != CONNECTED) throw new ChatProtocolException("Illegal state for message request: " + state);
|
||||||
if (USER_ALL.equals(receiver)) {
|
if (USER_ALL.equals(data.getReceiver())) {
|
||||||
for (ServerConnectionHandler handler : connectionRegistry.values()) {
|
for (ServerConnectionHandler handler : connectionRegistry.values()) {
|
||||||
handler.sendData(sender, receiver, type, payload);
|
handler.sendData(data.getSender(), data.getReceiver(), data.getType(), data.getText());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ServerConnectionHandler handler = connectionRegistry.get(receiver);
|
ServerConnectionHandler handler = connectionRegistry.get(data.getReceiver());
|
||||||
if (handler != null) {
|
if (handler != null) {
|
||||||
handler.sendData(sender, receiver, type, payload);
|
handler.sendData(data.getSender(), data.getReceiver(), data.getType(), data.getText());
|
||||||
if (!receiver.equals(sender)) {
|
if (!data.getReceiver().equals(data.getSender())) {
|
||||||
sendData(sender, receiver, type, payload); //send message to sender if it's a direct message and sender is not receiver.
|
sendData(data.getSender(), data.getReceiver(), data.getType(), data.getText()); //send message to sender if it's a direct message and sender is not receiver.
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.sendData(USER_NONE, userName, getDataTypeError(), "Unknown User: " + receiver);
|
this.sendData(USER_NONE, userName, getDataTypeError(), "Unknown User: " + data.getReceiver());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue