CIS 1200

Homework: PennPals

Computing’s core challenge is how not to make a mess of it.      — Edsger W. Dijkstra

Helpful Links

Overview

This assignment involves building an internet chat server (like Slack) in Java. Your code will model the internal state of the server and handle communication with chat clients.

The key content for this assignment is described in Chapters 24, 25, and 26 of the lecture notes – we encourage you to study these before getting to work. If you want to start on the project early, you will need to read the lecture notes ahead of the posted schedule. (This will also have the benefit that you will be prepared to ask questions during the lectures on these topics. :-)

Here are some checkpoints for this assignment (but as always, starting earlier is better!):

  • Checkpoint 1 (Tuesday): Complete Task 1.
  • Checkpoint 2 (Wednesday): Complete Task 2.
  • Checkpoint 3 (Friday): Complete Task 3.
  • Checkpoint 4 (Monday): Complete Task 4.
  • Checkpoint 5 (Tuesday): Complete Task 5 (optional) and Task 6 and hand it in!

Make sure you run the tests for each task (and that they pass!) before moving on to the next section.

You will be changing just a few files…

  • src/main/java/org/cis1200/ServerModel.java
  • src/test/java/org/cis1200/ServerModelTest.java … but you will need to read and understand some of the others.

Task 1: Understanding the Problem

Start by familiarizing yourself with the following concepts and design principles for this assignment:

Server

A computer system designed to provide a service to many connected clients at once.

Client (User)

An end user of a service like our chat server.

  • In this assignment, a client is the program run by a user who has connected to the server to chat with other users.
  • Each client is assigned a unique userId when they connect to the server. This integer cannot be changed, and it persists as long as the client is connected. If a client later reconnects, they will get a new userId.
  • Each client also has a nickname - a string that other users can see. A user can change their own nickname at any time.

We use “client” and “user” interchangeably in these instructions.

Channel

A group of users (clients) connected to the server. Every channel has an owner – the user who created the channel.

  • A user in a channel receives all messages sent to that channel after they join.
  • A user in a channel can leave the channel at any time. After they leave, they will no longer get messages from that channel.
  • At any given time, a user connected to the server can be in any number of channels (including none).
  • Think about what Java construct you can use to group together all of the data related to a channel!

Owner

The owner of a channel can remove anyone from that channel (including themselves). If the owner leaves for any reason, the entire channel is removed.

Channel Privacy

A channel is either public or private. If it is public, any user who knows the name of the channel can join. If it is private, new users are only added if the owner invites them, in which case they automatically join.

Protocol

A standardized set of instructions for communicating over the Internet by requesting and transmitting data.

In this assignment, clients communicate with the server by sending commands that instruct the server to create new channels, send messages on channels, etc. We represent this communication protocol in our code base using subclasses of an abstract class Command:

  • NicknameCommand changes the sender’s nickname.
  • CreateCommand creates a new channel with the sender as its owner.
  • JoinCommand lets the sender join a public channel.
  • InviteCommand adds a target user to a private channel owned by the sender.
  • MessageCommand sends a message to all users in a specific channel.
  • LeaveCommand lets the sender leave a channel they are in.
  • KickCommand removes a target user from a public or private channel owned by the sender.

Before moving on, familiarize yourself with the definitions of the Command class and all of its subclasses in the provided file Command.java. Pay particular attention to the private instance variables of each class and the “getter” methods that provide access to this information.

On receiving a command, the server is responsible for updating its internal state and producing a ResponseSet: a set of responses sent to any clients that should be informed about the command.

Task 2: The internal state of the ServerModel

In this task, you need to:

  • Add necessary instance variables to ServerModel (javadocs) and initialize them in the constructor.
  • Implement user query methods: existingUserId, getUserId, existingNickname, getNickname, and getRegisteredUsers.
  • Implement channel query methods: getChannels, getUserNicknamesInChannel, and getOwner methods.
  • Pass all tests in Task2Test.java.

If you’re unsure what a method does or what its arguments mean, check the online Javadocs or the Javadoc comments above the method definition (it’s the same information, since the online Javadocs are generated from the comments).

ServerModel Overview

The ServerModel is responsible for processing all incoming Commands and keeping track of the server state. You will need to add data structures to track:

  • All users connected to the server (their userIds and nicknames)
  • The channels that users are in
  • Information on each channel, like its owner, members, etc.

This is just the information that has to be stored in ServerModel - the way in which that information is stored is entirely up to you.

(The word Model in ServerModel means that this class just manages the core state and associated behaviors of the server. Isolating the core functionality in this way makes it easier to design and test.)

Data Structure Choices

You have a choice of three data structures to use for this assignment: TreeSet, TreeMap, and LinkedList. Think about which data structure(s) you’d like to use. Consider a data structure whose capabilities fit the nature of the information being stored. Some questions you can ask yourself to pick a data structure include:

  • Are duplicate entries allowed?
  • Is ordering important?
  • What kind of relationship is being stored?
  • Which data structures could avoid unnecessary looping?
  • Which structures allow direct access to the data you need?

You may use only these data structures from java.util. In particular, even if you are already familiar with classes such as HashSet, HashMap, or ArrayList, you may not use them for this assignment. One of the goals of this homework is for you to practice working with these specific data structures.

Do your best to avoid redundancy. Having the same information in multiple places will make your life much more difficult.

You can change your mind later. You need to make a choice now to get started with this point of the assignment, but as you learn more about what you need to do, you might find that you want to change things up. It is normal to need to do this. At each step of this project, we will ask you to document your choices so that you can see how they evolve as you work through the material and add new features.

Creating New Classes

You might find it helpful to represent parts of the server state using new classes.

You are not required to do so for this assignment, but if you do choose to create a new class, you should consider the following guidelines:

Define the class in a new file and in the proper package

If you want to define your custom class called NewClass, you should create a file named NewClass.java. (Note that your file name should correspond to your class name!) Each new class needs to have package org.cis1200; at the top of the file.

Override equals

This is useful for testing, but also required if you use the class as elements of a LinkedList or TreeSet, or as keys of a TreeMap. Checking if an equivalent instance of your class is already in a Collection requires overriding equals, since this defines the conditions under which two instances are judged to be the same.

If you don’t override equals, Java will use == by default, so two instances with identical values in their fields may still be marked as “not equal”.

Consider implementing the Comparable interface (javadocs)

This is required if you plan to use objects of the class as elements of a TreeSet or as keys of a TreeMap, for reasons that are similar to the reasons to override equals. Because TreeSet and TreeMap are based on insertion into a BST, they need to be able to compare elements to each other and determine positioning.

Add Javadoc comments

All methods in the new class should have Javadoc comments to describe what they do.

Task 3: Connections and Setting Nicknames

This is the first task where you need to start implementing methods that correspond to commands from the chat protocol. Each of these methods is defined in the ServerModel class and called by a corresponding updateServerModel method of a subclass of Command.

For this task, you need to:

  • Implement registerUser, deregisterUser, and changeNickname
  • Pass all tests in Task3Test.java
  • Add your own unit tests for these methods in ServerModelTest. Think about situations that were not covered by Task3Test.

Hints: the isValidName helper method has been provided for you. You may add additional helper methods to the ServerModel class, as long as they are marked private.

What is a Response?

A Response object represents a message that the server should send to one or more clients after it finishes processing a command.

For example, when a client changes its nickname, every other client who can see that client should be notified of the change so that its client-side applications can display the new nickname.

The Response class constructor is marked private, so the only way to construct a Response is via the following static methods:

  • Response.connected is used when a new client connects.
  • Response.disconnected is used when a client disconnects from the server.
  • Response.names is used when a client joins or is invited to a channel.
  • Response.error is used when it is not possible to complete a command for some reason
  • Response.okay is used when a CreateCommand, MessageCommand, LeaveCommand, or KickCommand executes successfully. More on that later.

Please read the instructions for the next few tasks carefully - as well as the Javadocs for Response and ResponseSet - to ensure that you pass in the correct parameters when constructing responses.

What is a ResponseSet?

In class you’ve learned about Sets, which are collections of objects without duplicates. We can have Sets of Integers, Sets of Strings, etc. In the same way, you should just think of a ResponseSet as a Set of Responses! If you take a look at that class, you will see that its only instance field is a Set, to which Response instances are added to through a helper method.

Task 4: Channels and Messages

In this task, you should:

  • Implement all parts of createChannel and joinChannel that don’t involve channel privacy
  • Implement sendMessage and leaveChannel
  • Check that you pass all tests in Task4Test.java
  • Add your own unit tests for these methods in ServerModelTest. Think about situations that were not covered by Task4Test.
  • Run the client application to try out your code for channel creation, joining channels, messaging, and leaving channels.

Testing with the Client Application

The chat server is now functional enough to try channel creation, joining channels, messaging, and leaving channels, by running it together with one or more instances of hw07-client.jar (the client application).

Running the Client in Codio

  1. Start the server with the menu.
  2. Launch a client instance with the menu. If you want multiple clients at once, click on the menu option once for each client.
  3. Click the “PennPals” option to open up a window to view the client.

Running the Client in IntelliJ

  1. Start the server by running ServerMain.java.
  1. Launch a client instance by right-clicking on hw07-client.jar and clicking “Run”.

  2. When prompted to enter the server’s host name, enter “localhost” to connect the client to the server running on your computer.

If you need multiple instances of the client, you may need to open a command line (PowerShell for Windows, Terminal for macOS) and run java -jar <path-to-client>, where <path-to-client> is replaced by the location of hw07-client.jar. If you’re not sure how to type the path manually, just drag the .jar from File Explorer/Finder to the command line window after typing java -jar

Note: Using the client application is not a substitute for writing JUnit tests!

Task 5 (Kudos): Channel Privacy

Note: this task is optional, and not required to receive full credit on the homework assignment. However, completing this task will give you additional practice with Java collections and will produce a more fully featured chat implementation.

In this task, you should:

  • Add channel privacy to your server state
  • Modify createChannel to deal with creation of private channels
  • Modify joinChannel to handle users trying to join private channels
  • Implement inviteUser and kickUser: if a owner kicks themselves, the channel should be destroyed too
  • Check that your code passes all provided test cases in Task5Test.java
  • Add your own unit tests to ServerModelTest for these methods. Think about situations that were not covered by Task5Test.
  • Try out these features using multiple instances of the client application

Task 6: Refactoring

You’ve completed all of the required features! Hooray! Now look for any redundant data, duplicate code, convoluted functions, or other issues that make your code less readable. Don’t be afraid to adjust some data structures, add helpers, and so on.

If you are worried about breaking your code, make a zipfile for your current version, before making any modifications. However, this is the point where your unit tests will help: if you do introduce a bug while refactoring, a good test suite should immediately detect and help you isolate it.

Make to check that your code complies with our style guidelines.

Submission Instructions

You will be uploading only the following files:

  • src/main/java/org/cis1200/ServerModel.java
  • src/test/java/org/cis1200/ServerModelTest.java
  • Any additional classes you made

If you are using Codio

The easiest option is to use the Zip Project menu item in Codio. Do not include any of the other provided files, since doing so may cause your submission to fail to compile. You can directly upload the zip produced by Codio into Gradescope.

If you are using IntelliJ

Gradescope allows you to easily drag-and-drop files into it, or you can click “Browse” in Gradescope to open up a file browser on your computer to select files. Upload only the files listed above.

Grading Breakdown

  • Automated testing (90%)
    • Task 2: Server State Access (5%)
    • Task 3: Connections and Nicknames (40%)
    • Task 4: Channels and Messages (35%)
    • Task 5: Invite-Only Channels (0%, Kudos only)
    • Model state encapsulation (5%)
    • Style (5%)
  • Manual grading (10%)
    • Server model design (5%)
    • Quality of your testing (5%)

You have three free submissions for this assignment. Each additional submission will cost you five points from your final score.