Compare commits

..

No commits in common. "main" and "useDataObjectToSend" have entirely different histories.

8 changed files with 13 additions and 95 deletions

Binary file not shown.

4
Klassendiagramm.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 133 KiB

View File

@ -1,79 +1,2 @@
# Ü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.
# Uebung-hk1-Schrom01-Fassband-Brandleo
Übung Multichat

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 135 KiB

View File

@ -0,0 +1 @@
<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>

View File

@ -48,6 +48,7 @@ public class ChatWindowController {
@FXML
private Button sendButton;
/**
* Takes a message object and stores it used as Model and also starts message Listener via messageListener method.
*
@ -68,7 +69,6 @@ public class ChatWindowController {
startConnectionHandlerListener();
serverAddressField.setText(connectionHandler.getServerAddressProperty().get());
serverPortField.setText(String.valueOf(connectionHandler.getServerPortProperty().get()));
refreshConnectionState(connectionHandler.getStateProperty().get());
}
/**
@ -172,20 +172,12 @@ public class ChatWindowController {
*
* @param newState is the state that it should be set to.
*/
public void refreshConnectionState(State newState) {
public void stateChanged(State newState) {
// update UI (need to be run in UI thread: see Platform.runLater())
Platform.runLater(new Runnable() {
@Override
public void run() {
if(newState == CONNECTED || newState == CONFIRM_DISCONNECT){
connectButton.setText("Disconnect");
messageField.setDisable(false);
sendButton.setDisable(false);
} else {
connectButton.setText("Connect");
messageField.setDisable(true);
sendButton.setDisable(true);
}
connectButton.setText((newState == CONNECTED || newState == CONFIRM_DISCONNECT) ? "Disconnect" : "Connect");
}
});
if (newState == DISCONNECTED) {
@ -264,7 +256,7 @@ public class ChatWindowController {
connectionHandler.getStateProperty().addListener(new ChangeListener<State>() {
@Override
public void changed(ObservableValue<? extends State> observable, State oldValue, State newValue) {
refreshConnectionState(newValue);
stateChanged(newValue);
}
});

View File

@ -0,0 +1 @@
<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>

File diff suppressed because one or more lines are too long