COSC 440

Spring 2016

Lab 2

Complete L&L Chat phase 2: L&L E48, E49, E50, E51, E52

SimpleChat1 code


Test cases phase 1

Test cases phase 2

We will modify our approach to the implementation of features given in exercises E50 and E51.

E50 - Adding Client commands and Server console and commands

Exercise 50 asks you to add several Client-side commands, where the user enters a command by starting a string with # and the name of the command, such as #quit, #logoff, etc. The procedural solution to this is create a long if ... else if ... else if ... statement with an if clause for each command (and one that distinguishes whether it is a command or a string to be sent). This solution is problematic. It is hard to maintain as we add additional functionality by adding more client commands. The existing code in the Client class that handles strings input from the user interface must be changed. One of the principles of O-O programming is to avoid changing code when making modification if possible (except for refactoring, which is changing the whole plan). This kind of change is error-prone and may break code that already exists.

We can fix this problem by creating an abstract ClientCommand class in the client package that specifies an abstract method doCommand. Then for each possible command, we define a class that extends ClientCommand and has as its class name the command. (We will break the usual rule that a class name begins with a capital to facilitate this.) I have done two of these classes to demonstrate, login and quit.

The trick is to use a technique in java called reflection. This allows us to convert a String with the name of a class to an instantiation of the class. In order for this to work, we need to know the parameters to the constructor for the class. Since some of our commands will need an addition String, we define a String instance variable set by the constructor for ClientCommand and each other class has a constructor defined that takes a String and calls super.

The handleMessageFromClientUI method gets the String from the UI. Before we create the command, we need to recognize that it is a command. We check the first character of the String sent from the UI to distinguish between a String to be sent and a command. This is the one if-statement that is needed. The the code to construct the command class is given in a private method:

Class[] param = {String.class};
ClientCommand cmd = (ClientCommand)Class.forName(commandStr).getConstructor(param).newInstance(message);

Class.forName creates a class, that then creates a constructor class with getconstructor that then creates an instance of the class (as an Object) with newInstance. The array of String gives the parameters (in this case a single one of type String) needed for the constructor. The resulting object, cmd, is downcast to be type ClientCommand (which all of the command classes subclass.). The call cmd.doCommand then executes the command as specified in the particular class. Note that the String for the command has to include the package path, so before we create the command class we append "client." to the string making, for example "client.quit" or "client.login".

Since several commands including login require that the client not be connected to the server already, we generalize this aspect in the class NotConnectedClient which extends ClientCommand. This class provides the check for connection and acts appropriately, calling the abstract method doCmd when not connected. Any command that only applies when not connected should be defined by a class that extends this class rather than ClientCommand, as the login class does.

You can complete this exercise by defining a new class for each new user command. Note that my version of the login command does the version described in exercise 51 using another technique -- see below. The code for sending a String for the server to display also uses the technique described below.

Server Console

You can add a server console by copying the approach for creating the ClientConsole1. You will use reflection in the same way to handle the commands from the server console to the server.

In doing this, you should create a new server package and make the new version of EchoServer1 part of that package, with the console in the default package along with the client console. In order to correctly handle the requirement that the setport command can only apply if the server is closed, you will need to add a boolean to indicate this and set/unset it in the appropriate callback methods.

E51 - Sending commands to the server

The server is intended to respond to certain commands that are also designated by using the "#command" format to distinguish them from strings to be echoed. However, this would create a need for a similar reflection technique in the server to handle these without creating a huge if...else if...else if... statement again. Since we are sending objects as our messages -- the book code only sends String objects -- we can do something else. We can define ServerMessageHandler as an abstract class with an abstract method handleMessage. This class also has concrete methods for accessing references to the server itself and to the client connection. Since this class must be seen both by the server and the client, we define it in the common package, along with the subclasses that extend it.

We have done this in the starter code for a version of EchoServer1 that has not been adjusted to work with a server console (E50, part b).

Each command that the client wants to send to the server, including just displaying a string, is written as a subclass of ServerMessageHandler. When the client communicates with the server, it always sends an instance of such a subclass and the server simply supplies the references to itself and the connection, then executes the handleMessage method. We have created classes for two commands, ServerStringMessageHandler to simply display the string and ServerLoginHandler to handle a login in request. The first is called in the ChatClient1 sendMessageToServer method for a non-command String and the second is used in the ChatClient1 constructor.

One of the specifications given in E51 is that a login command to the server be the first command after a connection is made. Consequently, we again consolidate the check for a non-login command in the class ServerNonLoginHandler which does the check and sends an error message to the client if a login has not yet been performed. Otherwise, it calls the abstract method handleMess that needs to defined by subclasses. The class ServerStringMessageHandler is a subclass of this class rather than ServerMessageHandler.

The ServerLoginHandler is not complete, however. You will still need to modify it so that a login is handled on the server side as described in E51.

You will need to make a few changes to adapt this approach to your version of EchoServer1 that communicates with a server console. First, since EchoServer1 has methods to access its console and through it the console's display method, your subclasses of ServerMessageHandler should use these methods and the console's display to display any message to the server, replacing many calls to System.out.println(). In order for this to work, the server must be recognized as an EchoServer1, since the AbstractServer has no connection to a console. Consequently, the type of the server in ServerMessageHandler must be EchoServer1 instead of AbstractServer.

Design diagrams

The following rtf files contain UML class diagrams for the above design for commands.

ClientCommandDiagram   ServerCommandDiagram   ServerMessageHandler