In this assignment, you will be using Java to build an internet chat server!
Like with the OCaml Paint project, we want to give you practice designing and reasoning about code at a larger scale than one method or function at a time. Rather than simply leading you through the assignment function by function, we will be providing you with a lot of background information and documentation about the server components you will be writing and how they interface with our given code. Your job will be to implement your server so that it has the features we specify, with the internal design of the server being almost entirely up to you!
This project will be significantly easier if you are patient and follow the program design process! Start each task by reading the instructions for that part, referencing portions of code and Javadocs as necessary. To help you out, we've listed some questions you should be able to answer at the end of each task before moving on. Once you are confident in your understanding of a particular task, you should write test cases that translate this understanding into code. Only after that should you begin programming your implementation.
When you encounter errors or confusion, you may feel the need to refer back to the instructions and specifications. This is perfectly normal—we don't expect you to memorize every tiny detail of the server spec at once! Programming with documentation can be overwhelming at times, but it is good practice for larger software engineering work. Don't aim to understand absolutely everything at first, just figure out what you need to know to get started, and then use the instructions to clarify any confusion or edge cases you encounter.
Because we have provided a lot of documentation, if you have conceptual questions that could be answered by reading it more carefully, the TAs will probably just direct you to the relevant sections of the assignment. You can save yourself some time by using the resources available to you—especially ctrl/command-F and the tables of contents.
Good luck!
For this assignment, you will be writing two components of an internet chat server: a parser that processes commands from the server's clients, and server model logic that allows the server to store and update its current state.
The diagram below is a high-level overview of all the parts of this application. You will need to edit the classes in green.
First off, you can download the assignment files here. The client application that connects to the server can be downloaded here. (See Task 5 below for instructions on how to run the client.)
You are given a lot of files in this homework assignment. However, the only given files and methods that you need to edit are:
You are free to add your own helper methods and classes to your project. You should NOT, however, change the method header for any of the methods listed above!
Remember that programming style comprises a portion of your grade for this assignment. You should review the CIS 120 Java programming style guidelines to familiarize yourself with the conventions for this course.
The first step you should take when designing any program is to make sure you understand the problem. In this case, that means making sure you understand the high-level concepts that are discussed throughout the documentation for this assignment. Read through the Server Concepts section of the spec. You should be able to answer (at a high level) the following questions:
The first task is to convert the command strings that the clients and servers communicate with into a data structure that is easier to use.
Your job will be to implement the parse method in CommandParser.java. This method should parse a command string (such as "MESG java :CIS 120 is cool!") to create a Command object. The Command object returned by parse will be used later on by your server model.
You should start this task by reading over the command parsing section of the project specification. This spec describes how each command must be formatted when it is sent from the client to the server. You will also want to look at Command.java and note how a Command object is constructed.
We give you an incomplete set of unit tests in the file CommandParserTest.java. Before you implement your CommandParser, you should add more test cases to this file based on the specification.
After you have created your test cases, you should then implement the parse method. As you do so, consider the following tips:
There are some features of the Java standard library that you may find useful for this task. Consider taking a look at Java's documentation for the built-in String class, especially the indexOf and substring methods. These methods may help you in parsing the strings your program receives.
The Command class contains a definition of an enum called Type used to represent the various types of commands that can be sent between a client and server. The Java tutorial includes a section on enums that can help you understand this code.
You may find valueOf method of the Command.Type enum useful. This method allows you to get the enum value from a string representing its name.
While what it means for a string to represent a valid Command is clearly specified, you should keep in mind that there are still edge cases which can occur within valid strings. For instance, you will want to consider the fact that the payload can contain any character.
Now that you have implemented the parser, you will write the model for your server. The ServerModel is the component responsible for processing each Command that is generated by your parser, and for keeping track of the server state. The first features you will implement for the server model are acknowledging user connections and allowing users to set their own nicknames.
Before continuing, you should now familiarize yourself with how the server model (in ServerModel.java) interacts with the code that we provide. Before continuing, read through the specification sections on the server model and its handle method. Move forward after you can confidently answer the following questions...
By the end of Task 2 and Task 3, your server should have implementations for the following methods:
By the end of Task 3, the only command that the handle method must process is the NICK command, handling both valid and invalid uses. For the time being, you do not have to worry about sending the QUIT response in deregisterUser, since there aren't channels for the deregistered user to be in yet! You may just return an empty Broadcast for now.
Luckily for you, we have already provided you all of the tests that will be used to grade Task 3 in ClientConnectionTest.java. Your main job here is to read through them and make sure you understand how they work! There are comments in the file that explain what is going on in each of test.
Now that you understand the problem and the provided test cases, you should start working out how you will actually accomplish this task. The key challenges you will face are how to store the registered users on the server, and how to keep track of which user IDs are associated with which users.
We provide a few questions to help guide your design process in the README.txt you downloaded with the assignment files. You should answer the questions there prior to proceeding. Be thorough in your answers; we would like to see your thought process as you work on this part of the assignment!
You may have noticed references to the ServerUtil class in the code you reviewed as part of Task 2a. This class contains several utility methods to help you during this assignment, including the isValidNickname method for verifying that a nickname satisfies the specification requirements. You can find the full listing of methods in this class in the Javadocs for this class.
You will want to utilize the Java Collections library, which provides various classes and interfaces suitable for modeling the internal states found in the chat server. You are allowed to use the following collections for this assignment (and no others):
One of the trickier components of this task is keeping track of which nickname is associated with which user ID. This is because the server identifies connections to users by their unique ID, which must be the same across nickname changes.
For example, if someone has ID 42 and nickname "OCamlRox120", any messages intended for "OCamlRox120" should be indicated as being sent to user ID 42 in any Broadcast that has to relay a message to "OCamlRox120". If "OCamlRox120" changes his or her nickname to "JavaRox121", any messages intended for "JavaRox121" should still be sent to user ID 42.
Now it is time to implement your design from Task 2! You should start by getting registerUser, deregisterUser, and getRegisteredUsers to work, and then move on to adding the handle behavior (error and otherwise) for NICK. Once you are done, your code should pass all of the provided tests in ClientConnectionTest.java.
After completing this task, you should go back and complete the section for Task 3 in your README.txt, documenting any changes to your design that you may have needed to make while finishing this part of the ServerModel.
Now that your server allows users to connect and disconnect, you will extend your implementation in Task 5 to allow them to join channels and message one another. After Task 4 and Task 5, your server will support the following commands:
With these changes in functionality, your server will need to generate the following responses:
You will also change your functionality from Task 3 in the following ways...
For this task, you should start by looking at the specification and making sure you understand what the server model should do when each command is received. Make sure that you can answer the following questions about each of the commands listed above:
While you were given the tests for Task 3, you will need to write your own test cases for Task 5. We provide you with some basic tests for each command in ServerModel.java; you will need to identify and write tests for any cases not covered already. You should study the protocol and the tests you were given for Task 3. You may also find it helpful to read over the Javadocs for TestUtil and ServerUtil to see the full range of testing functions you can use. When writing test cases, make sure to test the more complex cases. For example, when a user is kicekd from one channel, it should not affect the other channels of which they are a member. Remember also that an owner leaving a channel should correctly update the server state.
Once your have written your tests, you should sketch out a plan for implementing these changes to your ServerModel. The key challenge you'll need to tackle here is organizing the users into channels so it is easy to relay messages properly. As before, we have provided some prompts in your README.txt that you should answer prior to continuing.
In this part of the assignment, you will have to think about relaying commands to clients other than the original sender. You should think carefully about how to organize everything so it is simple to relay a command to the correct clients. Make sure to go back and review the information about relaying commands in the specification.
Once you have your tests and design nailed down, implement channels. We suggest that you try to implement the behaviors for the commands one at a time, bearing in mind that testing some commands requires other commands to work correctly (e.g. CREATE has to work before you can test JOIN).
Once you are finished with this portion of the assignment, you should be able to fire up the provided chat client JAR (see below for instructions) and connect to your server! Try booting up two instances of the client and sending messages between them!
Remember that you should go back to the Task 5 section of your README.txt and log any changes to your design that you made while working on this task.
Since this task requires you to edit your (hopefully functioning) code from Task 3, you should take the time to save a copy of your work. We cannot give you credit for Task 3 if you break it while working on this task!
Once you finish Task 5, you can try running your server and testing it with the provided client (available here).
To start your server, you will need to run ServerMain.java in Eclipse. Your server will launch and wait for clients to connect to it. While your server is running, a small window will appear to indicate that your server is running. When you wish to close your server, you can either close the GUI window that popped up when you launched the server or hit the red square at the top-right of the console in Eclipse.
To launch an instance of the client, double-click on hw08-client.jar. If you are on OS X and this doesn't work, try right-clicking and selecting "Open" to open the client. You will be prompted to either input an IP address or connect to a server being hosted on your own computer. Your server must be running before you connect the client.
You can run multiple clients on your computer, switching between the windows to simulate multiple users connecting to your server. To run multiple clients, you may have to create multiple copies of the client jar and run each one separately.
While you are running the chat server from Eclipse, it will output all commands sent by and received by the server to the Eclipse console. Commands received by the server are represented in the string format from the protocol spec. ServerResponses share a similar format, but have the sender of the message prepended before the command type.
One minor annoyance: the client only displays a list of the channels on the server it has already joined. You will have to know the names of any channels you wish to join ahead of time.
We provide you the client application because it is fun (your code will actually work as a chat server!), but you should keep in mind that you should still write an exhaustive suite of unit tests for your server. Manual testing is time-consuming, often inexhaustive, and prone to human error.
For this task, you need to add support for the INVITE command in your ServerModel. In addition, you will need to change the behavior of JOIN to enforce the invite-only restriction on channels (if you have not already). Make sure to INVITE works correctly with the other commands. For example, if a user is invited to an invite-only channel and changes their nickname, they should still be able to join that channel.
At this point, you know the drill: write out test cases for this functionality, think through your design, and then go forward with implementation. As before, we have given you a few prompts in your README.txt that you should fill out before starting. Once you finish with your implementation, fill out the README portion labeled "After You Finish", then kick back and relax: you're done!
At this point, you may start finding the design you established in Task 2 difficult to work with. Do not be afraid to refactor your existing code before starting on Task 6 if you think it would make implementing Task 6 easier. An exhaustive test suite for your earlier work will help ensure you don't accidentally break your implementation as you do this! You should document any changes you plan to make to your original design in your README.txt.
For this assignment, you must submit a zip directory named files.zip containing the following files:
These files should be in the root of the zip file (there should be no extra folders or anything inside the zip that contains your files). PLEASE CHECK THAT YOUR UNZIPPED ARCHIVE DOES NOT CONTAIN ANY SUBDIRECTORIES BEFORE SUBMITTING. This is generally the cause of most submission errors. Do not put any of the other provided files in the zip file.
This is the only second time we are using this assignment, and it has been significantly revised since last semester. We would really appreciate any comments you may have about anything in the assignment in your submission feedback!