PS-6
Starter code: editor.zip
Introduction
Goal: to build a collaborative graphical editor — akin to Google Docs' ability to have multiple simultaneous editors of the same document.
In both cases, multiple clients connect to a server, and whatever editing any of them does, the others see. So I can add an ellipse, a friend on another computer can recolor and move it, etc. This code handles multiple objects at a time, and can draw rectangles, line segments, and "freehand" shapes in addition to ellipses.
The basic client/server set-up is much like that of the chat server. Each client editor has a thread for talking to the sketch server, along with a main thread for user interaction (previously, getting console input; now, handling the drawing). The server has a main thread to get the incoming requests to join the shared sketch, along with separate threads for communicating with the clients. The client tells the server about its user's drawing actions. The server then tells all the clients about all the drawing actions of each of them.
There is one twist, to make this work nicely. A client doesn't just do the drawing actions on its own. Instead, it requests the server for permission to do an action, and the server then tells all the clients (including the requester) to do it. So rather than actually recoloring a shape, the client tells the server "I'd like to color this shape that color", and the server then tells all the clients to do just that. That way, the server has the one unified, consistent global view of the sketch, and keeps all the clients informed.
Given that the architecture is much the same as with the chat server (and other client/server set-ups), the bulk of the assignment comes down to establishing the message passing protocols between client/server and ensuring that they all maintain and modify a consistent shared view of a common sketch.
Getting started
The starter code provides a number of files including:
Editor: client, handling GUI-based drawing interactionEditorOne: a sample program to get you started on the EditorEditorCommunicator: for messages to/from the serverSketchServer: central keeper of the master sketch; synchronizing the various EditorsSketchServerCommunicator: for messages to/from a single editor (one for each such client)EchoServer: an alternative server useful for development / debuggingShape: interface for a graphical shape, with implementations Ellipse, Polyline, Rectangle, and Segment
Please take the time to familiarize yourself with these files, and read over the whole assignment carefully regarding how the pieces fit together, before even thinking about coding. Begin by looking at EditorOne. This code is an implementation of a simpler version of the Editor you'll write as part of this assignment. Feel free to modify the existing variables and methods if you wish; this is just a blueprint for you to start with.
Task 1: Shapes & Sketches (20 pts)
Task 1.1: Lines & rectangles (10 pts)
Fill in the Rectangle and Polyline classes so that they implement the Shape interface. Use the completed Ellipse and Segment classes for inspiration.
A Polyline is like a multi-joint Segment: (x1,y1) to (x2,y2) to (x3,y3), etc. It is a geometric representation of a freehand drawing. To test containment of a mouse press in a polyline, you can leverage Segment.pointToSegmentDistance, since each pair of points along a polyline is basically a segment. Note that this method is static, i.e. you don't have to create a Segment instance to call it. Note also that for Polyline, the contains method should test whether a pixel is within the line stroke, not the fill area within it.
Task 1.2: Creating a Sketch (10 pts)
You will need to create a new Sketch class in order to represent the current state of the sketch. It is up to you how to implement it.
The server will maintain a "master" version of the sketch, and should broadcast messages to the clients so that their "local" versions of the sketch are all equivalent. To add a new shape (once finished), an editor client sends a request to the server to add it, giving the info needed for a new instance. The server will then determine a unique ID for that shape, and broadcast to everyone an indication to add the shape (again, with all the info needed for the constructor) with the given ID. Thus all editors have the same ID for the same shape.
Your Sketch class should therefore have some data structure that holds a collection of shapes and their associated IDs. It should then be able to add, delete, recolor, or move a shape given its ID. Because multiple threads may be accessing a sketch (the different server communicators for the server's master sketch), the methods that access its shape list should be synchronized.
You will also likely want a method that takes a mouse press point and determines which shape it is in, being careful to pick the topmost shape (the most recently drawn) if it is contained in multiple shapes. If you use a TreeMap for the ID-to-shape mapping, with an increasing ID number for newer shapes, then a traversal of the tree will yield them in order of newness. Hint: TreeMap's descendingKeySet() and navigableKeySet() methods allow you to iterate through the Set of keys in high-to-low or low-to-high order, respectively.
Task 2: The Editor (20 pts)
The structure of the Editor looks just like EditorOne, but now needs to handle a sketch of multiple shapes instead of a single shape, and make and handle requests via the EditorCommunicator.
Task 2.1: Drawing the sketch (10 pts)
Fill in drawSketch. Each shape knows how to draw itself, so this is a matter of having the sketch loop over its shapes and call each of their draw methods. Again, be careful to draw them from bottom to top (oldest to newest). Also draw the shape that is currently being drawn, curr, if there is one.
Task 2.2: Handling mouse events (10 pts)
Fill in handlePress, handleDrag, and handleRelease.
If in drawing mode, create or modify the current shape (not yet in the sketch) similar to EditorOne. Creating a new Rectangle or Segment works much like Ellipse: click one corner and drag to the other. Polyline keeps track of all the points over the course of dragging, allowing an arbitrary curve to be sketched. You may need to write an extra method for Polyline to support this behavior.
For handleRelease, have the EditorCommunicator send a request to the server to add the current shape to the sketch. Similarly, for all non-drawing modes (delete, recolor, move), figure out which shape should be modified or deleted and have the EditorCommunicator send a request to the server to make the change. You will write the corresponding methods in EditorCommunicator in the next task.
Don't forget to call repaint() as necessary.
Task 3: Client-server communication (30 pts)
The communicators work much like in the chat server — sending and receiving messages on behalf of the client and server, updating the sketch, and, on the GUI side, having the editor update the display. Communication is always via Strings, using PrintWriter.println on one side to communicate with BufferedReader.readLine on the other side.
To help in development, we have provided an EchoServer which you can run in place of a SketchServer, testing your Editor and EditorCommunicator before worrying about the server side of things and how to go about handling multiple editors. When the EchoServer is running, it will read in input that your EditorCommunicator sends and "echo" it back so that your EditorCommunicator can read it in. This should help you while developing/testing your message protocol for updating a sketch, and that you are actually updating your local sketch correctly according to received messages.
Task 3.1: Sending messages (10 pts)
In order to handle a message, it might be useful to write a class that takes a String-based command (sent via writer to reader) and parses it to determine what action to perform and update the sketch accordingly. I recommend just using a simple format for the messages (i.e., no JSON or XML!) with the various pieces separated by spaces or commas. Then, for example, you can unpack the pieces into an array by String.split, and access the pieces by indexing into the array. For example:
String[] tokens = String.split("a b 1 2");
tokens is ["a","b","1","2"]
tokens[0] returns "a", tokens[2] returns "1", etc.
Integer.parseInt(tokens[2]) returns 1
When comparing Strings (e.g., to decide which action to perform for a message), be sure that you test equality with equals, not with ==.
Hint: for recoloring, Color.toString is nice for reading but not for parsing. On the other hand, its RGB value (flashback to PS-1!) is just an int.
Task 3.2: Client-side communication (10 pts)
Create appropriate method(s) in EditorCommunicator to send messages from the Editor to the SketchServerCommunicator. These should be the requests to modify the sketch.
Also fill in the run method to receive messages from the SketchServerCommunicator and act appropriately — these messages tell the Editor how to update their sketch (don't forget to call the Editor's repaint to display the changes).
Task 3.3: Server-side communication (10 pts)
Fill in the run method in SketchServerCommunicator to receive messages from the EditorCommunicator and act appropriately. When a new client joins, they need to be filled in on the state of the world (what shapes are already there). Then it should receive the client's messages, update the server's "master" sketch, and broadcast the change to all clients.
Testing
Start up a server instance first. Then start an editor. Test on your own machine (set serverIP to "localhost"), and with a friend (give them your IP address or vice versa). Note: to run multiple Editors in IntelliJ you must click Run -> Edit configurations, then select Editor in the left pane and click "Modify options" dropdown in the right window and check "Allow multiple instances".
Consider possible multi-client issues. What unexpected/undesired behaviors are possible? Can you actually make any of them happen? Where and why do you use synchronized methods; will they handle everything?
Take a screenshot of a collaboratively edited sketch. If you're working independently, you can just have two Editors running on your own machine. Also write a short response to the above questions on possible multi-client issues.
Extra credit
- [10 pts] Support other common graphical editor commands, e.g., bring to front and send to back (note that you'll need to keep a separate list of the drawing order), group and ungroup, undo and redo, etc.
- [10 pts] Allow a client to "lock down" a shape, so that nobody else can modify it until the lock is released. (Draw such shapes differently to distinguish them.)
- [10 pts] Set up an access control, so that a client can specify with what other clients they are willing to share a sketch.
Submission Instructions
Turn in the following in a single zip file:
- your completed versions of all the classes, thoroughly documented.
- a snapshot (in png format) of a collaborative client-server drawing session.
- and a document with your brief dicussion of synchronization issues.
Grading Rubric
Total of 100 points
Correctness (70 points)
| 5 | Rectangle |
|---|---|
| 5 | Polyline |
| 10 | Event handlers |
| 10 | Sketch: maintaining shape list, identifying selected shape |
| 10 | Drawing shapes and partial shapes |
| 5 | Sending strings between clients and server, using EditorCommunicator and ServerCommunicator |
| 10 | Composing messages, and decomposing and acting on them |
| 10 | Run loops in ServerCommunicator and EditorCommunicator (5 each) |
| 5 | Consistency of sketches across clients and server, including new clients |
Structure (10 points)
| 4 | Good decomposition of and within methods |
|---|---|
| 3 | Proper used of instance and local variables |
| 3 | Proper use of parameters |
Style (10 points)
| 3 | Comments included within methods and for new methods |
|---|---|
| 4 | Consistent and informative names for variables, methods, parameters |
| 3 | Clean and consistent layout (blank lines, indentation, no line wraps, etc.) |
Testing (10 points)
| 7 | Snapshot of a collaborative drawing |
|---|---|
| 3 | Discussion of multi-client |