COMP 310
Spring 2018

HW08:  RMI Chat Program

Home  Info  Canvas   Java Resources  Eclipse Resources  Piazza

The goal of this assignment is to design and implement a flexible, extensible chat program using RMI.

 Look in Piazza for the latest notes and decisions concerning the application and its API's!   Each Design Group will have their own private discussion group in Piazza.

Note that the common package will not be completed or available until we work out the API's in class and lab first, so be sure to attend both!   In the mean time, connect the common package to your Design Group's proposed API respository and test your API by writing your ChatApp to use it!

Design Teams:  Please follow the General Design Guidelines

Chat Program Requirements:

The chat program must be able to:

  1. Connect and communicate with any other student's implementation (including the staff's).
  2. Support two-way text chatting with
    1. Multiple, simultaneous single individuals in a chat room
    2. Multiple, simulataneous independent chat rooms.
  3. Support an "auto-connect-back" feature wherein an accepted request to chat is automatically connected without the user having to know the requestor's address.
  4. Support the ability to handle new kinds of data packets at run-time, e.g. images, files, etc. without initially having the explicit ability to handle that kind of data packet.
  5. To join the group chat only requires a single manual connection to a single existing participant.
  6. The ChatApp system should use a peer-to-peer architecture.    (In can be assumed that all participants are on the same network, i.e. all participants can communicate with directly any other participant, if desired.)
  7. The ChatApp system should be based on a message-passing architecture, not a command-passing architecture.
  8. Assume that the "discovery server" used to get the IP address for an initial connection only returns an IP address without any other information (reality: "name: IP" written on a whiteboard).
  9. Any given chat application should be minimally reliant on global knowledge about the rest of the connected chat applications.
  10. Restrictions:

Get the provided library packages (when available):   

This includes:

Get the ChatApp API common interfaces (API) packages

The class will be designing the major interfaces needed to communicate with another ChatApp implementation.   Class attendence is mandatory!   The current version of those interfaces will be available through a second svn:externals link.   Each student is responsible for knowing the results of all class and lab design discussions.

This includes the latest version of the common interfaces and additional documentation developed by the class.   ALL students' code MUST adhere to the API defined by the common package!   The code in this package will change often, so be sure to perform an "Update to Head" frequently!

In the common package is also  additional published documentation that gives additional API information.   The information in the common API documentation is part of your required specifications!

Make sure your fully qualified class names are unique

In order to ensure that there are no accidental class name clashes between teams, place all of your own implementation code, but NOT the svn:externals code above, under a package labeled with your team's NetID's, e.g. "src/netid1_netid2/model/...", "src/netid1_netid2/view/...", "src/netid1_netid2/controller/..."    etc.

If you need to modify your code to comply with this naming convention, simply use the "refactor" feature in Eclipse to move your code to a new package:

 

This project requires more code writing than any previous assignment, so START EARLY!!!    There are also numerous implementation pitfalls due to RMI and due to possibly poor design by the class.   Find these early and submit bring them up for discussion right away so that they can be addressed and fixed in a reasonable time.   Since interoperability is a requirement for this assignment, no one is coding in a vacuum, don't keep things to yourself!   

The code snippets shown here are for ILLUSTRATIVE PURPOSES ONLY.   Your code will probably NOT look exactly the same as your interfaces will be different.   Do NOT attempt to rote copy any code below!

Resources:

Tips and Traps

Don't Remove A User While Traversing the List of Users

If you send everyone, including yourself, an IRemoveUser data packet, you will remove a user (yourself) from the list of users, as the chat room is traversing the that same list as it sends the data packet to every user.   But removing an element from a collection as you are traversing it is asking for trouble because you will make the loop count erroneous, possibly causing elements of the collection (i.e. users) to be skipped.  

Solution:   Remove yourself from the chat room before telling the rest of the chat room to remove you.

Don't Serialize an Anonymous Inner Class

Unless, of course, you want Java to attempt to suck your entire ChatApp down a network hole.   'Nuff said.

Don't Use Static Classes and/or Fields

The hazy semantics of the inter-object relationships and closure issues that can arise can cause wildly unpredictable results.   Static anything in a network system is just plain bad news.   Stay away...far, far away.

Sometimes You Don't Want To Dispatch To a New Thread

Assuming that the call to IChatRoom.sendMsg() (or whatever it is method to send a message is called) is a blocking call, it is not always desirable to make that call using another thread, as one would do to free the GUI thread from being blocked.   If you want to make sure that the message is sent before doing something else, call the sendMsg() method directly and let your current thread block on the call.   That will guarantee the next thing your thread does is after that call fully completes.

Note:  Depending on exactly what you are trying to accomplish, completely blocking the current thread may not be entirely desirable either, so you may wish to entertain other options as well, e.g. dispatching to a custom thread rather than the standard one that is used to send messages (see, for instance, below).   The key is to focus on the order of execution that you want for the tasks at hand.

Insuring that Tasks Are Executed After Sending A Message While on a Separate Thread

Sometime you want to do something definitively after sending a message to the chat room.   But the usual construct to dispatch the sending of a message to another thread to free the GUI thread doesn't allow other tasks to be performed in a manner that is definitively after the sending of the message.   If that's the sort of thing you need, try this modification:

Somewhere, define the following interface, which is simply a work-around the fact that Java will put a warning about generic parameters in arrays when we use a vararg parameter with a generically defined type:

/**
 * Convenience interface to get around type-erasure generated warnings about generic types in varargs.
 */
interface IVoidLambdaDP extends IVoidLambda<ADataPacket> {
}

Modify the standard "sendData" thread-dispatching method to take a vararg parameter of the above type:

	/**
	 * Send a data packet and process the returned data asynchronously on a new
	 * thread.  On that same thread, after the message is sent and the returned status
	 * objects processed, a vararg array of commands is run.  
	 * The data is the input parameter to the command's apply
	 * method which has a void return.  The vararg allows no commands as a 
	 * viable sendData call.
	 * 
	 * @param data
	 * @param cmds Vararg of commands to run after the message is sent and return status's processed.
	 */
	public void sendData(final ADataPacket data, final IVoidLambdaDP... cmds) {

		// spawn a new thread to handle the actual transmission, thus freeing
		// this thread to return.
		(new Thread() {
			public void run() {
				// Send the data packet to the chat room
				Iterable<ADataPacket> returnStatus = room.relayMsg(data, userStub);
				// process the returned value using a utility method
				processStatusCollection(returnStatus, data);
				// Run the commands, if any, handing them the data.
				for (IVoidLambdaDP cmd : cmds) {
					cmd.apply(data);
				}
			}
		}).start(); // start the new thread
	}

A typical call would look like:

		sendData(data, new IVoidLambdaDP() {

			@Override
			public void apply(ADataPacket... params) {
				// do some stuff here.  Will be done after the data is sent.
			}
		});

Any number of IVoidLambdaDP's can be specified, including none and they will all get run.

 

The Return Value of myDataPacketAlgo.getCmd(id) Needs to be Downcast.

Because getCmd is a method inherited from AExtVisitorAlgo, the return type of getCmd is typed to be the superclass of ADataPacketAlgoCmd, namely IExtVisitorAlgoCmd. But an ADataPacketAlgo only allows ADataPacketAlgoCmds to be installed, so the return value can be safely downcast.  Your code should look something like this (here, IUser is the Remote message receiver):

ADataPacketAlgoCmd<ADataPacket, ?, IUser> cmd = (ADataPacketAlgoCmd<ADataPacket, ?, IUser>) myDataPacketAlgo.getCmd(id);

This will generate an "unsafe cast" warning from the compiler, but just take the quick fix to add an "@SuppressWarnings("unchecked")" attribute to the enclosing method.

 

Passed Parameters and Fields Do NOT Behave the Same!

A common mistake is to pass a reference to the chat room to the connection object's constructor.   Later, the model's field that references the current chatroom is changed to be that of a new chat room.  But the connection object is unchanged and still internally refers to the old chat room because it does not reference the chatroom field of the model.   Using an anonymous inner class implementation of the connetion object neatly save the day here.  Why is it safe to use anonymous inner classes for the connection and message receiver RMI server objects?     

 

 

Find more help at the Java How-To's web page.

See the web page on How to Make Mini-MVC's, which are needed in order to support multiple chatrooms.


Grading Criteria

 

 

 

 


© 2018 by Stephen Wong