
Homework 7: PennPals
Computing’s core challenge is how not to make a mess of it.
— Edsger W. Dijkstra
- Javadocs for server code
- CIS 120 Java programming style guide
- Frequently Asked Questions
- If you prefer to work locally (instead of in Codio), start from the stub code and the client jar file.
Assignment Overview
In this assignment, you will be using Java to build an internet chat server. In particular, your task will be to design and model the internal state of the server and handle communication with users of the chat system.
HW07 does not have tasks. Instead, it has checkpoints written directly into its description and table of contents (on the left of this page).
- Checkpoint 1: you can complete this checkpoint now.
- Checkpoint 2: you can complete this checkpoint with Mon 11/09 (Lec 27) or Chap 25.
- Checkpoints 3, 4, 5: you can complete these checkpoints with Mon 11/09 (Lec 27) or Chap 25. You will also benefit from the context of Wed 11/11 (Lec 28) or Chap 26.
- Checkpoint 6: you can complete this checkpoint once you’ve learned from Mon 11/16 (Lec 29) or Chap 25.
How to Approach this Project
This project, like the OCaml Paint project, is designed to get you comfortable working with larger software projects with a lot of moving parts.
This project will be significantly easier if you approach it methodically and follow the program design process!
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 specification at once! Programming with documentation can be overwhelming at times, but it is good practice for larger software engineering work. Understand the big picture first; work through the details of implementation at each part by referring back to the instructions. Use the instructions to clarify any confusion or edge cases you encounter.
Pay careful attention to your coding style as you complete this assignment. Things like modularity of design will come naturally if you follow the layout of the tasks listed below and plan ahead. Other stylistic concerns, such as naming conventions and formatting, will require more of an ongoing effort. This assignment has a lot of interrelated components, and you will probably find it rewarding to maintain a fair amount of stylistic discipline when it comes to field and method naming. A poorly named data structure, when it is one of many, can quickly add significant cognitive overhead to tracing through your code, should you need to debug it!
Setting Up
The Codio project should be set up with all the requisite files.
For Eclipse, you’ll need to follow the Eclipse setup instructions for Making a Java Project and import the stub files.
In both cases, make sure to keep the PLAN.txt
file handy - you should use it to write notes about your design ideas as you learn more about the assignment and decide how you’d like to design your program.
Task 1: Understanding the Problem
Client-Server Architecture
A client-server architecture is a pattern for building a distributed system that requires the coordination of a (potentially large) number of different computers. Email and the World Wide Web are examples of such systems; they allow multiple clients, usually the end users of the service, to exchange data with servers. A server is a computer system designed to provide a service to many connected clients at once, often facilitating communication between them.
Clients communicate with the server by means of a protocol, which is a standardized set of instructions for requesting and transmitting information over the internet. For example, you might have heard of HTTP (HyperText Transfer Protocol), which allows browsers to request web pages from a server. Our server and clients will use the PennPals protocol (explained in greater detail below) to communicate with each other.
User IDs and Nicknames
The ServerBackend
class is the base layer of the server, which handles network connections with various clients. When a client connects to the server for the first time, the ServerBackend
generates a unique integer ID to represent the open connection with the client. (There are no guarantees about these integers’ values beyond uniqueness.) Because chatting with other clients based on their ID number is confusing, we will allow clients to have nicknames which are used within the application. Unlike user IDs, which cannot change as long as the client is connected to the server, clients can change their nicknames at any time.
Channels and Owners
The server does not serve as one giant chat room for all connected users. Instead, this protocol is modeled around the idea of channels, or groupings of users on the server. The server has multiple channels, and a client may be in any number of channels (or in none). Any user who opts to join the discussion in a channel will receive all messages and commands that are directed to that channel after they join. At any point a user can also leave a channel, and will then stop receiving messages and commands.
The user that created a channel is designated as that channel’s owner. The owner of a channel may kick other users from the channel. If the channel is invite-only, the owner of the channel must add other users by sending an InviteCommand
. For the sake of simplicity, the owner of a channel cannot be changed; a channel is removed if its owner leaves.
Checkpoint 1
Make sure you understand the definitions of the following terms in the context of this homework assignment: client, server, protocol, channel, nickname, and owner.
Class Diagram
To guide you along this project, we’ve provided a diagram to explain how the parts work together:

PennPals Chat Protocol
The PennPals protocol is a set of commands that can be sent to the server from a client, who is known as the sender of the command. Some commands also include a target user, who is the object of an action that the sender wishes the server to perform.
The table below summarizes each of the client commands:
Command | Effect |
---|---|
NicknameCommand | Changes the sender’s nickname. |
CreateCommand | Creates a new channel with the sender as its owner. |
JoinCommand | Adds the sender to a public channel. |
InviteCommand | Invites the target user to a private channel owned by the sender. |
MessageCommand | Delivers a message to all users in a channel. |
LeaveCommand | Removes the sender from a channel. |
KickCommand | Removes the target user from a private channel. |
How Commands are Represented
Each of the commands of the PennPals protocol above are represented by a class of the same name. These classes all extend a common abstract superclass, called Command. Because Command
is abstract, it cannot be directly instantiated. However, as an abstract class it provides a common interface for all commands and a common implementation for some of the features that all commands should support, such as the name of the sender of the command.
How Commands are Communicated Between the Server and the Clients
The server and client communicate using a text-based protocol, where commands are encoded as strings and transmitted as a stream of characters. When the server receives a command string from a client, it must use the CommandParser
class to convert the string into a corresponding Command
object.
For example, consider the following command string:
:camel MESG java :CIS 120 is the best!
This string is a command issued by the client whose nickname is camel, instructing the server to deliver the message CIS 120 is the best! to every user in the java channel.
For this project, we have provided you with the code necessary to convert Command
objects to and from strings. In particular, each subclass of Command
overrides the toString
method to produce strings (in the client) and the server uses the CommandParser
for the opposite conversion. You don’t need to be concerned with the specifics of this process, but you should take a look at the various toString
implementations because you will likely see commands printed to the console when you run your server.
Subtyping and Dynamic Dispatch
When a server receives a command from a client, it must process that command in some way. Therefore, every concrete subclass of Command
must implement the following abstract method:
public abstract Broadcast updateServerModel(ServerModel model)
Because this method is an abstract method of the Command
class, the server backend can call updateServerModel
with any command and rely on dynamic dispatch to execute the appropriate behavior.
Your job will be to implement the updateServerModel
method for each concrete subclass of Command
, allowing the server to process commands sent by its clients. Before that, however, you will need to think about how the server state should be modeled.
Testing
An important part of any large software project is writing test cases. Tests help you define expected behavior before diving too deep into the implementation. Throughout this homework, make sure to write tests as you go. Writing tests before attempting to implement some functionality will help you get a better understanding of the task and help you verify that the code you’ve written works as expected.
Testing with the GUI client is not sufficient to ensure that your code works as expected. Make sure to write JUnit tests in addition to any manual testing you perform. Use the tests in ServerModelTest.java
as a guide.
Task 2: Designing the ServerModel
The ServerModel is the component responsible for processing each Command
that is generated by the parser, and for keeping track of the server state. Your model will need to keep track of all the users connected to the server (see task 3) and the channels they are in (task 4 and task 5).
Before continuing, read the ServerModel documentation to see what methods the ServerModel
class must provide. The structure of the server’s state should make it convenient to implement those methods!
Selecting the appropriate data structures to model your application state is one of the most important design considerations in software engineering. An improper choice can lead to numerous headaches down the road, and can be difficult to change later on because the model’s implementation relies so heavily on the data structures it uses.
For this reason, we encourage you to take some time to think through the requirements for the server model and plan your choice of collections before beginning your implementation. Grab some coffee, go for a walk, listen to some music—but don’t write any code until you’ve put some thought into your design!
Skim through the remainder of these instructions to get a sense of what is expected from the ServerModel
class. Decide what information the model will be responsible for storing, and how it should be grouped and associated. There isn’t one right answer, but certain implementations are preferable because they limit the complexity associated with manipulating and using the data.
Try to eliminate redundancy and cyclic references in the data you store. Don’t store the same data in two different places, or it might get out of sync!
The next part of the instructions for this task will provide general advice about model design. You should take it into account—a significant portion of your grade for this assignment is based on the clarity and efficiency of the models you design! At the end of this task, we will ask you to implement some model query functions using your design, and answer the questions in the PLAN
file about your design decisions.
Choosing Collections
It’s likely you will need to store groups of objects so you can organize and access them at a later date. Java has a wide range of built-in data structures available in java.util.Collections
, but for simplicity we restrict the ones you may use to those we have discussed at length in this course. In particular, these are:
- TreeSet: Implementation of the
Set
interface using binary search trees, like theBSTSet
you created in OCaml for Homework 3. Elements placed into this collection must implement theComparable
interface (see next section). - TreeMap: Implementation of the
Map
interface using BSTs. Keys must implement theComparable
interface. Similar in structure and efficiency toTreeSet
, but can be used to associate values with keys.-
If you use a TreeMap, you may find the methods .values() and .get() useful.
-
One can iterate over the keys and values in a TreeMap using a for-each loop. The syntax below means: for each
Entry
in aTreeMap
calledt
, retrieve the key and value from that entry, then do something. An Entry holds one key and one value - check the Javadocs for its instance methods.for (Entry e : t.entrySet()) { K key = e.getKey(); V value = e.getValue(); ... }
-
- LinkedList: Implementation of the
List
andDeque
interfaces using a doubly-linked list. This is equivalent to the deques you implemented as part of Homework 4.
You may not use any other types of Collection
to complete this assignment.
Consider the relative merits of each type of data structure and the uses for which they are appropriate. You should use collections whose properties correspond to the properties of the data they will be storing. For example, if your data has a meaningful ordering and permits duplicates, a sorted list might be a good way of representing that order. You should write similar justifications for the data structures you use in your PLAN
.
Make sure the static types you declare for variables and method parameters are interfaces rather than implementations. That is, write List<T>
, Map<K, V>
, etc., rather than LinkedList<T>
or TreeMap<K, V>
. Doing so is good practice as it makes your code easier to maintain and update—if you choose to change the implementing class you can simply replace the constructor call without having to make any other changes.
Creating New Classes
You may find it useful to create new classes to represent certain components of the server state. Classes are a useful organizational tool because they associate state and functionality in small, discrete units. The design principle of separation of concerns is important to keep in mind; the internal details of a particular operation can be encapsulated within a class to hide details other classes don’t need to know about. Feel free to create as many or as few new classes (and/or interfaces) as you deem necessary, and to extend others as you wish. Just make sure to add Javadoc comments in all new files and classes you create to describe what their purpose is!
Here is some useful advice to keep in mind if you end up creating new classes.
-
Designate your class fields as private. Controlling access to the internal fields of your class prevents other classes and users from relying on access to its internal state or breaking invariants. You should use getter and setter methods to query and update the internal state of your class, and ensure that those methods maintain the invariants. This allows you to encapsulate private state and maintain separation between components of your program.
-
Override the
equals
method to define equality for instances of your class. By default, new objects inherit theequals
method fromObject
, which is simple referential equality. If you would like a more meaningful structural comparison, you are responsible for defining it, usually in terms of the data stored within the fields of the class. In addition to theequals
method, it is also good practice to override thehashCode
method to be consistent withequals
. -
Consider having your class implement the
Comparable
interface. TheTreeSet
andTreeMap
data structures rely on their elements having a natural ordering, which can be used to achieve efficient lookup of elements. Although most built-in Java types already implementComparable
, if you would like to store a custom class in one of these data structures, you must implementComparable<T>
for your classT
. Implementing this interface requires you to define the methodcompareTo
according to the specification in the Javadocs for Comparable.
Implementing User Models
Now it’s time to implement some model query functions based on your design. If you have determined you will need to make additional classes, you should have at least a skeletal implementation before continuing. You should add whatever collections you plan on using as private fields in your ServerModel
class, and ensure you are initializing them in your constructor.
You should now implement the getRegisteredUsers
method in the ServerModel
class, using the collections you’ve chosen for storing information about the clients currently connected to the server.
If you’re unfamiliar with a class or method, you should read the Javadocs to get a sense of how it can be used. These HTML pages are generated from the /** ... */
comments before a class or method definition, which are often referred to as Javadoc comments. Here’s a link to the Javadocs for getRegisteredUsers.
You should also implement the getUserId
and getNickname
methods. If you find your logic for any of these methods to be rather convoluted, you should consider whether an alternative design for your ServerModel
state is more appropriate. If you need to change your design, doing so now rather than later will save you many headaches!
Implementing Channel Models
Once you have the ability to model the users connected to the server, you will also need to store the channels on the server, and the users they contain. After adding the necessary collections, you should implement the getChannels
, getUsersInChannel
, and getOwner
methods in ServerModel
. You may need to make some additional modifications to your model at this point—remember to document them in your PLAN
.
Checkpoint 2
At this stage, you should have:
- Added necessary collections to the
ServerModel
class, and initialized them in your constructor. - Implemented user models (
getRegisteredUsers
,getUserId
, andgetNickname
) - Implemented channel models (
getChannels
,getUsersInChannel
, andgetOwner
). - Documented your design choices and justifications in the Task 2 section of your
PLAN.txt
.
Task 3: Connections and Nicknames
The next ServerModel
features you will implement are acknowledging user connections and allowing users to set their own nicknames. We’ll first introduce the (provided) Broadcast
class, which is used by the server to coordinate responses to clients. Then it will be your turn to implement some of those responses.
Generating Broadcast
Objects
While the server receives commands from one client at a time, it is often the case that multiple clients should be informed about the effects of a command. For instance, when a client changes his or her nickname, everybody who can see that client should be informed of the name change, not just the user whose nickname changed.
To facilitate the sending of multiple responses at once, the ServerModel
and ServerBackend
use the Broadcast
class to queue a set of responses to be dispatched to potentially many clients (Note that a response is just a Command
and a user to send that command to.). Given a Broadcast
, the ServerBackend
will take care of sending the appropriate protocol responses to the clients. (Note the separation of concerns here—the ServerModel
does not need to know the intricacies of the protocol, only the public interface for the Broadcast
class).
As you will see in the Javadocs for Broadcast, you cannot create a Broadcast
simply by calling the constructor. Instead, the class provides a set of static factory methods, which can be called to create the appropriate type of Broadcast
. Here are the conditions under which you should use each factory method:
-
Use
Broadcast.connected
when a new client connects. This will be used to inform the client of their initially assigned nickname. -
Use
Broadcast.disconnected
when a client disconnects from the server. This will be used to inform other users in that client’s channels that they have quit the server. Note that because the connection has closed, the original client cannot be sent thisBroadcast
. -
Use
Broadcast.names
when adding a client to a channel. ThisBroadcast
should only be sent as a result of handling aJoinCommand
or anInviteCommand
. It will inform all clients that a new user has been added to the channel in question, and also inform the new user of the names of everybody already in the channel. -
Use
Broadcast.error
when processing a command results in an error. The sender who issued an invalid command (and no other clients) should be informed that their command resulted in an error. There is a specified set of error conditions for each command type, which we’ll introduce as they arise. -
Use
Broadcast.okay
in all other cases where the command is handled successfully. This method can should be used to instruct all clients to perform whatever command was issued by the sender of the command.
Interpreting Broadcast
Test Output
We’ve provided you with several tests that test various components of your chat server. Soon, if not already, you will write your own tests for your server model. If an assert statement compares an expected and actual broadcast, assertEquals(expected, actual)
, and finds that they are not equal, the actual and expected Broadcasts will be printed out as part of the JUnit test output.
Here’s an example of a test output:
broadcast expected:<{User0=[:User1 JOIN java], User1=[:User1 JOIN java, :User1 NAMES java :@User0 User1]}> but was:<{User0=[:User1 JOIN java], User1=[:User1 NAMES java :@User0]}>
.
The test output displays who the Broadcast is sent to and what messages they receive. The assert statement that produced the output shown above expected a Broadcast would be sent to the users with nicknames “User0” and “User1”. The expected Broadcast would tell User0 and User1 different things. User0 would be told that User1 joined the channel “java”: User0=[:User1 JOIN java]
. User1 would be told two things. First, that User1 joined the channel “java”, and second, that the names of the users in the channel “java” are “User0” and “User1”: User1=[:User1 JOIN java, :User1 NAMES java :@User0 User1]
. Note that the NAMES part of the message puts an @ before “User0”. The @ designates User0 as the owner of the channel “java”.
The actual Broadcast was: {User0=[:User1 JOIN java], User1=[:User1 NAMES java :@User0]
. How is this different from what the test expected? In this case, the NAMES part of the Broadcast meant for User1 did not include User1. Now you know that the JoinCommand is producing a Broadcast that doesn’t include User1 in its set of recipients, and you can use that information to start debugging your code!
Handling Client Registration
The registerUser
method will be called by the server backend when a new client connects to the server, passing the ID of the new client as an argument. Because the client has just connected to the server, they have not yet had the chance to set their nickname, and so you should use the provided generateUniqueNickname
method to assign them a default nickname. You should store the association between user ID and nickname in your model’s data structures.
This function will return a Broadcast
object to the server backend. You should use the connected
static method to construct a broadcast containing the client’s initially assigned nickname. Once this is done, your implementation should pass some of the simpler tests in ConnectionNicknamesTest.java
.
The only recipient for this broadcast will be the user who just registered.
Handling Client Disconnection
When a client disconnects from the server, the server backend will call the deregisterUser
method, instructing the client to remove all state associated with the user who has quit (if they later rejoin, they should do so as an entirely new user). There might also be other clients who were in the same channels as this user; they should be notified that the user has quit. (The user who quit cannot be sent any such notification, since they are no longer in contact with the server.)
You will want to construct a broadcast using the disconnected
static method. This method takes the nickname of the user who just quit the server, as well as a set of nicknames of users who were in the same channels as the disconnected user. (This set will be empty for now, since you have not yet implemented channel creation).
If the user that just quit the server was the owner of any channels, those channels should be deleted.
The recipients of this broadcast should be all the users who were in a channel with the disconnecting user.
Handling Nickname Changes
The next step is allowing users to change their nicknames. As will be the case when implementing most commands, you will need to distribute work between the updateServerModel
method of the corresponding Command
and the ServerModel
itself. In this case, you will need to modify the updateServerModel
method of the NicknameCommand
class.
Your updateServerModel
methods should never directly modify the data structures and fields of ServerModel
(this should not even be possible!).
The table below contains a specification of the errors (defined in the ServerResponse enum) that might arise as a result of handling an invalid NicknameCommand
. If such an error occurs, you should immediately return a Broadcast
object created using the error
static method. It is important to detect such errors as early as possible, so that the state of the server is not corrupted as a result of partially performing invalid commands.
Error conditions for
NicknameCommand | |
NAME_ALREADY_IN_USE |
There is already a user with the desired nickname. |
INVALID_NAME |
The desired nickname contains illegal characters. |
You can use the isValidName
method in ServerModel
to validate a nickname.
If the command can be handled successfully by the model, then you should relay the command to any clients in the same channels as the sender by using the okay
static method in Broadcast
, including the user who changed their name. Note that this method takes a Command
as an argument. Since we are creating a Broadcast
as a result of handling the current command, you can pass this
(a reference to the current object instance) as the command.
Checkpoint 3
At this stage, you should have:
- Passed all tests in
ConnectionNicknamesTest.java
- Implemented user registration, deregistration, and nickname changes
- Documented any design changes made during this task in your
PLAN.txt
.
Task 4: Channels and Messages
In this task, you will implement some more commands related to channel creation, entry and exit, and messaging. By the end, you will have a working implementation of most of the server functionality!
Handling Channel Creation
In this step, you will add support for clients creating new channels on the server. As in earlier tasks, you will have to coordinate channel creation between the updateServerModel
method of the CreateCommand
and the ServerModel
itself.
Recall that every channel has a name, an owner, and a set of users who are in the channel. (For now, you can ignore the inviteOnly
parameter; it will be used later.) When handling a CreateCommand
, you should make sure to store this information in your ServerModel
. If the channel was successfully created, you should inform only the channel’s creator using the okay
factory method in Broadcast
. Only the creator of the channel should be in the set of users receiving the response.
If an error creating the channel arises, you should return a Broadcast
containing the appropriate error. Use the same isValidName
method to validate a channel name.
Error conditions for
CreateCommand | |
CHANNEL_ALREADY_EXISTS |
There is already a channel with the desired name. |
INVALID_NAME |
The desired channel name contains illegal characters. |
Adding Users to Channels
If a channel already exists, a client should be able to join it by issuing a JoinCommand
. If no errors arise, then the client should be added to the channel, and the users already present in the channel should be notified that a new user has joined.
How does the new joiner know the names of everyone already in the channel? This is handled by the names
method in Broadcast
, which generates an additional protocol message to the client with the nicknames of the channel’s users. You should consult the Javadocs for names for more information about this method.
In task 5, you will implement invite-only channels, which require users to be invited by the channel’s owner instead of joining freely. This added feature means that the code you write now will need to be extended later on. Writing a clear and well-factored implementation now will make this much easier. For now, you should ignore the JOIN_PRIVATE_CHANNEL
error.
The recipients for this Broadcast should be all the people in the channel that the user just joined.
Error conditions for JoinCommand | |
NO_SUCH_CHANNEL |
There is no channel with the specified name. |
JOIN_PRIVATE_CHANNEL |
The channel is private, and the user has not been invited. |
What if the client tries to join a channel they are already a part of? The resulting model state should be no different than the original, which means we do not need to protect the server state from this sort of error. In cases like this, it is safe to “ignore” the error and process the command as usual.
Sending Messages to Channels
Finally, we are ready to implement the actual messaging part of the server! When a user sends a message to a channel, it should be relayed (via an okay
Broadcast
) to all clients in that channel. You should keep in mind that no part of the server model needs to change for message delivery; you should rely on it only for error condition checking.
Error conditions for MessageCommand | |
NO_SUCH_CHANNEL |
There is no channel with the specified name. |
USER_NOT_IN_CHANNEL |
The user is not in the specified channel. |
Users Leaving Channels
At any time, a user may decide to leave a channel by issuing a LeaveCommand
. This will prevent them from receiving any further messages from that channel. If the command is okay
, all users in the channel should be added to the Broadcast
you create. This includes the user who has left the channel.
Because every channel has exactly one owner (and the owner cannot change), we specify that if the owner of a channel leaves, every user is to be removed from the channel, and the channel itself should be destroyed. You should issue a Broadcast
the same way as before to the same group of recipients, including the owner of the channel; your handling of the command in the ServerModel
is the only place where you will need to account for this case.
Error conditions for LeaveCommand | |
NO_SUCH_CHANNEL |
There is no channel with the specified name. |
USER_NOT_IN_CHANNEL |
The user is not in the specified channel. |
Checkpoint 4
At this stage, you should have:
- Implemented users' ability to create/join/leave channels and send messages
- Passed all tests in
ChannelsMessagesTest.java
. - Tested interactions between multiple chat client instances
Testing your Server Using the Client
Running the Client
At this point, your server supports enough functionality to be used as a real chat server! We’ve provided you with a client application in the file hw07-client.jar
(which is already included in the project files in Codio and can be downloaded separately if you’re using Eclipse).
-
Running the Client in Codio
You can launch the client using the menu options in Codio. Before launching the client, fire up your server from the menu. You should see a small window pop up which indicates the server is running when you open up the viewer through Codio (similar to the previous homework). Now, launch an instance of the client through the menu option. You can create as many instance of the client as you want by clicking on the menu option multiple times. All clients will appear in the same viewer tab. You will need to repeat this process to test your server with multiple instances of the client application.
-
Running the Client in Eclipse
You can launch the client by double-clicking the
hw07-client.jar
file. Before launching the client, fire up your server by running theServerMain
class in Eclipse. You should see a small window pop up which indicates the server is running. Now, launch an instance of the client. You can create as many instance of the client as you want by clicking on the menu option multiple times. You will need to repeat this process to test your server with multiple instances of the client application.
Once the client is running, you will be prompted to enter the IP address of the server. Since the client and the server are running on the same computer (either yours or Codio’s virtual computer), the address you should enter is localhost
. Note that the client will not display all of the channels on the server—to populate the list on the left-hand side, you will either have to create or join a channel.
Advice about Debugging
One caveat: although testing different interactions in the client is a good way to test a range of your server’s behaviors, it is not a replacement for writing JUnit tests. There may be (unintentional) bugs in the client application, and it is not possible to exhaustively test all cases using the client UI. If you encounter a bug while using the client, it is best to translate the sequence of steps you followed into a JUnit test. The TAs are not responsible for helping you debug behavior in the client, unless there is a corresponding JUnit test case.
Task 5: Invite-Only Channels
In this task, you will extend the basic functionality of your chat server with additional features for private, invite-only channels. If your model design in ServerModel
does not yet include information about whether a channel is invite-only, you should add that before moving on.
Once your model is updated to take this information into account, you should go back to your implementation of JoinCommand
and add error detection for the case of JOIN_PRIVATE_CHANNEL
. As you proceed through the remaining steps, if you find duplicated functionality or other suboptimality in your models, you should consider refactoring your implementation.
Inviting Users to Channels
The InviteCommand
is the equivalent of JoinCommand
for invite-only channels. Users are added directly by the channel’s owner. You should use the names
method to inform the joinee of the names of the other users in the channel.
You will notice that there are multiple error messages that might result from an improper command (e.g., inviting a non-existent user to a public channel). In such cases, it is acceptable to return a Broadcast
with any one of the appropriate errors.
Error conditions for InviteCommand | |
NO_SUCH_USER |
There is no target user with the specified name. |
INVITE_TO_PUBLIC_CHANNEL |
The specified channel is public. |
NO_SUCH_CHANNEL |
There is no channel with the specified name. |
USER_NOT_OWNER |
The sender is not the owner of the specified channel. |
Kicking Users from Channels
Our final extension will be supporting the KickCommand
, with which a channel’s owner can remove a user from the channel. This command should be supported for both public and private channels. Like with the LeaveCommand
, if the owner kicks themself out of a channel, you should also remove the channel from your server state entirely.
The recipients for this Broadcast should be all the people in the channel that the user just left, including the kicked user.
Error conditions for KickCommand | |
NO_SUCH_USER |
There is no target user with the specified name. |
USER_NOT_OWNER |
The sender is not the owner of the specified channel |
NO_SUCH_CHANNEL |
There is no channel with the specified name. |
USER_NOT_IN_CHANNEL |
The user is not in the specified channel. |
Checkpoint 5
At this stage, you should have:
- Passed all provided test cases, including those in
InviteOnlyTest.java
- Verified that your server is fully functional - fire up two instances of the client application and spend some time playing around with them!
- Fixed any bugs you found while testing the client application
Task 6: Refactoring
Now that you have implemented all the required features, it is important to take some time to look for any redundant data, convoluted functions, or any other issues that can be refactored into more elegant code. It is very likely that some aspect of your initial design is not completely optimal, so don’t be afraid to adjust some data structures, split functions into multiple helper functions, and so on. Make sure to document any changes you make in your PLAN
file.
Checkpoint 6
You should now have completed any necessary refactoring work. Ensure that your code complies with the CIS 120 Java style guidelines and that it is properly organized and documented.
Broadcast toString
It’s important to understand the output of the toString method of Broadcast - it returns a list of responses. Each response will have a command attached to it in caps. View ClientCommand.java
for a list of the commands. To help understand this, we’ve provided a few examples below.
{User0=[:User0 CONNECT]}
means that User0 connected. The key, “User0”, is the user who sent the broadcast.
{cis120=[:User0 NICK cis120]}
means User0 changed his nickname to CIS120
{User0=[:User0 ERROR 500]}
indicates an error. Specifically, a NAME_ALREADY_IN_USE
error (refer to ServerResponse code 500).
Submission and Grading
Before submitting your assignment, you should take one last look through your PLAN
file to see if there are any questions you haven’t yet answered, or update your answers if necessary.
Submission Instructions
As with previous assignments, you will be uploading hw07-submit.zip
, a compressed archive containing only the following files:
PLAN.txt
src/Command.java
src/ServerModel.java
test/ServerModelTest.java
- Any additional classes you made
If you are using Codio
These files should be organized similar to above (with a src and test directory). The easiest option is to use the Zip menu item in Codio. Do not include any of the other provided files, since doing so may cause your submission to fail to compile.
If you are using Eclipse
Alternative 1 - Zip your files from Eclipse using the instructions below.
Follow these instructions to create and upload hw07-submit.zip
:
- Right click on project, select
Export...
- Expand General, select
Archive File
- Only select the files listed above (click on arrow in window on left and select files in the window on the right)
Browse...
>Save as "hw07-submit"
in Desktop/Documents/Downloads- Select
"Save in zip format"
- Finish
- Go to submission site, find file in
Desktop/Documents/Downloads
and then upload
Alternative 2 - Copy-Paste your code in Codio and zip from there.
You have three free submissions for this assignment. Each additional submission will cost you five points from your final score.
Grading Breakdown
- Automated testing (83%)
- Task 3: Connections and Nicknames (30%)
- Task 4: Channels and Messages (25%)
- Task 5: Invite-Only Channels (20%)
- Model state encapsulation (5%)
- Style (3%)
- Manual grading (17%)
PLAN.txt
(5%)- Server model design (7%)
- Quality of testing (5%)