COMP 310
|
HW08: RMI Chat Program |
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
The chat program must be able to:
println
's allowed!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.)ChatApp
system should be based on a message-passing
architecture, not a command-passing architecture.XX
is the year -- Spring semester classes should use
provided_SXX
)This includes:
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.
Add the following line to your svn:externals
property: "common
https://svn.rice.edu/r/comp310/turnin/FXX/ChatApp/Final/common"
(where XX
is the year -- Spring semester classes should use
SXX
)
Before the API is finalized,
test your own design group's
development code by connecting the common
package to
your group's repository by setting the svn:externals
instead to "common
https://svn.rice.edu/r/comp310/turnin/FXX/ChatApp/GroupY/common
"
(where XX
is the year and GroupY
is your
design group name. -- Spring semester classes should use SXX
)
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!
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:
netid1_netid2
"
(substituting the team members' NetIDs).
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!
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.
Unless, of course, you want Java to attempt to suck your entire ChatApp down a network hole. 'Nuff said.
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.
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.
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.
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.
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?
© 2017 by Stephen Wong