This lab focuses on debugging and testing as well as their role in incremental development. You will spend much of your time in this lab exploring programs that do not always work, predicting where they will fail, and learning to design fixes and tests for these fixes. This lab also reinforces the idea of coding to an interface and extends the set of basic java constructs that you are using to include exceptions.
Before you start this lab, you should read the book chapter on exceptions (chapter 10). You will probably also want to review the parts of chapter 2 that deal with incremental development, debugging and testing. (Begin with the section on "Building the Program") You should of course also be familiar with the material that we have covered in preceding labs (chapters 1-8), especially with the sections on reading and implementing interfaces.
Although you will not be writing a great deal of code in this lab, you will be writing and testing the same code over and over. You will need to come to lab with a good development plan, including the various stages of development that you will build, the behavior that you expect to see at each stage of the plan, the tests that will verify that behavior, and the tests that you would expect that code to fail. This staged development and testing plan is a required part of your lab check-in.
For this assignment, you may discuss the code for the project in as much detail as you like with your classmates, but you should do the testing of networks in the prelab as well as the writeup on your own. You may also get comments from other students on all portions of your writeup before turning it in, if you wish. Please include the names of anyone with whom you collaborate, in any way, on this assignment, and indicate the nature of the collaboration. If you do not collaborate at all, please indicate this as well.
You will be turning in the fully documented final code that you write for this lab. This code must be your own. We do encourage you, however, to ask your classmates for help debugging during lab. Helping someone else to debug their code is a great way to improve your own programming skills.
This assignment emphasizes the following topics
You should read through this entire assignment and complete the Lab preparation section before you come to lab.
This assignment is due at midnight on Thursday, September 29, 2005. You should send electronic versions to sd-psets@lists.olin.edu and paper versions to Katie.
You should read through this entire assignment before you begin.
To use the code, download the nodenet.jar file (available Friday; old version here). You can run the program from a command prompt in the directory to which you downloaded nodenet.jar by typing
java -jar nodenet.jar
To set up a NetBeans project, open NetBeans, select New Project from the File menu, click "Java Application" from the list, and hit the Next button. Name your project "nodenet" or something equally clever, and hit Finish.
Now right-click "Libraries", choose "Add JAR/Folder", and find nodenet.jar. Finally, you need to tell NetBeans the name of the main class, so right-click the project name, select "Properties" and "Run", and set the name of the main class to "nodenet.Main" (without the quotes).
Go ahead and run the code now.
The idea of the simulation is to experiment with various network configurations and empirically determine their behaviors. In other words, play with the application and see what happens. When the GUI pops up, you will see two panels, side by side. The panel on the right, which is currently empty, is where all of the simulations will take place. The panel on the left contains the available GUI elements -- all nodes, in this case. Above both panels are three menus: File, Simulation, and Selection. To use the simulator in the most basic way, you first construct a network and then set it in motion. The necessary steps are described below.
One of the main things you need in a simulator are nodes. In order to add a node to your simulator, select one of the nodes from the panel on the left, then click on the drawing panel to place it somewhere. There are three types of nodes which appear when you first run the program:
Sources and sinks display the number of packets they have produced or consumed, respectively.
The other crucial element in a simulator is channels. Channels run from one node to another, and are represented by a black line with an arrow, connecting the two nodes. In order to add a channel to your simulator, double click on the first node, then click on the second node. Channels have directions: they go from the node you start with to the node you end with. It is possible to have more than one channel between two nodes (e.g., one in each direction), but this isn't drawn particularly well. To remove a channel, outline the channel's arrow by clicking on it, then select Remove from the Selection menu.
To start objects moving through the demo, select Start from the Simulation menu. You can pause the simulation using Pause. You can dynamically add elements while the simulation is running. You can also temporarily disable a node or channel by selecting it and then choosing the Disable command from the Selection menu. To re-enable an element, use Enable. Using the Remove option to physically remove an element from the network while the simulation is running is discouraged; deletion is provided only for creating a new network, not for dynamically changing network behavior.
Experiment with the network for a few minutes. Don't get too caught up yet... Can you get the channels to fill up? Can you create any other interesting behaviors? Remember that you can disable channels using the Selection menu while the simulation is running. You may want to keep a record of your experiments to compare your later results with. Your record should include both network topology (how things are connected) and dynamic behavior (what you did and what it did). Beware: although you can theoretically save any particularly interesting network configurations using the Save As option under the File menu, this feature doesn't always work when your code is recompiled.
The DefaultConnectingNode is a pretty well behaved intermediate node type. We have also provided two less functional types of connecting nodes: TypeAConnectingNode and TypeCConnectingNode. You can load these into the simulation by restarting it with an argument from the command line:
java -jar nodenet.jar nodenet.TypeAConnectingNodeYou can also right-click on the project in NetBeans, choose "Properties" and "Run", then fill in the Arguments with nodenet.TypeAConnectingNode.
Try this now. You should see the new node type in the left-hand panel. Although DefaultConnectingNode is still there, you should probably ignore it for the remainder of your experimentation.
At this point, your job is to characterize TypeAConnectingNode, and once you are satisfied with your characterization, do the same for TypeCConnectingNode. First, you should see how the node type works under simple conditions. Use your notes to record how each node type behaves. Try different network conditions. Remember that you can add, disable, and remove Nodes and Channels (though you should only do so while the simulation is stopped). NB: You should be able to reset a particular configuration by saving it and then reloading it, but we do not promise that this feature will work under all conditions.
Your immediate goal is to find at least one configuration under which TypeAConnectingNode does something that seems inherently unreasonable to you. You should also find at least one such configuration for TypeCConnectingNode. Ideally, though, you should go beyond just finding a single configuration that "breaks" the node type. You should spend some time characterizing (and recording) how the node type responds to a variety of conditions to see whether you can figure out what types of mistaken assumptions or other errors it contains. Also try to predict how the node type will behave under certain conditions, then experiment to see whether you are right. Again, record these predictions as well as their outcomes. Do not spend a long time over the characterization task (total).
Your NodeBehavior class will not implement Runnable or Animate; instead it will be called by a Node, which has its own Thread and will call the NodeBehavior's method. The heart of Node's run method looks something like this:
InputChannelVector inputChannels; OutputChannelVector outputChannels;
public void run( ) { while( true ) { this.nodeBehavior.transmitPacket( this.inputChannels, this.outputChannels ); } }Since transmitPacket( ) will be called from inside a while( true ) loop, you only have to handle one object at a time: You should read at most one object from one channel and write it out.
The transmitPacket method takes two arguments. The first argument is an InputChannelVector, and the second argument is an OutputChannelVector. Both InputChannelVector and OutputChannelVector are essentially Lists (in the java.util sense), but with some typing changes that make them nicer to work with in this problem set. That is, the two ChannelVectors are collections of Channels. For example, the particular InputChannelVector that gets handed to a particular node's transmitPacket method is the list containing all of the InputChannels that lead to that node. Note that you cannot get a packet out of an InputChannelVector; you can only get an InputChannel out of an InputChannelVector (and a packet from that!) You should look at the interface documentation (linked above) to see what features these Vectors offer.
Inside your transmitPacket method, then, you'll need to select a particular InputChannel from the InputChannelVector. Each InputChannel in the InputChannelVector has the following method:
public Object readObject() throws ChannelEmptyException, ChannelDisabledException;
You only need to read one packet from one input channel on each time around the interactive control loop. (The packets are of type java.lang.Object.) Be careful, though: a given input channel may be empty or disabled, in which case it will throw an appropriate exception.
The second argument to transmitPacket( ) is an OutputChannelVector. An active non-full output channel would be a good place to put any packet you may have picked up. As a corollary to InputChannel, OutputChannel has
public void writeObject( Object ) throws ChannelFullException, ChannelDisabledException;But be careful: when an output channel is full, it will throw an exception if you try to write to it. Eventually, you'll have to make sure that you don't drop any packets if this happens, or write packets that don't exist.
The main piece of your coding will be to implement the transmitPacket( )method of NodeBehavior. This method should get an object from a single InputChannel from inputChannels (using readObject()) and feed it to an OutputChannel in outputChannels (using writeObject()). Remember that you may have multiple InputChannels and OutputChannels, and that some of them may be disabled..
To begin with, you should write a piece of code that compiles. This will involve writing a class that implements the NodeBehavior interface and starting up your application with that class loaded. It is perfectly fine if you cannot move any packets on that initial load; compiling and running the application is always a good place to start.
You should develop a plan for how you will slowly build up good functionality. For each step, you should indicate what code you will write, what tests you will use to determine whether your code works, and at least one test on which you expect your code to fail. Then go on to the next step; what will you build next, how will you know whether it works, and what will it not be able to do? This sequence of construction and testing is your development plan. Your development plan should have at least four steps with associated tests (and may have many more) before you come to lab.
Ultimately, you will need to think about strategies for how you want to read packets from channels. There are obviously several ways that you can choose the input and output channels from their respective Vectors. Think of at least two, and record these strategies. The more creative you can be, the more interesting your lab will be. You should design code for at least one of these strategies, and ideally for more than one. (It's OK, and even sometimes preferred, that you include at least one very simple strategy.)
One of the main points of this lab is to observe the effects of different strategies on the behavior of your network as a whole. Is your strategy fair? Does it drop packets? Create them? Do packets cluster in certain parts of your network? Can you come up with a strategy that behaves differently?
You should be aware that both InputChannelVector and OutputChannelVector contain a method, size(), which returns the size of the Vector; additionally, both contain a method, isEmpty, which returns true if the Vector is empty.
Additionally, InputChannelVector contains three more methods:
OutputChannelVector contains the same three methods listed above, except that each method returns an OutputChannel instead of an InputChannel.
Channels occasionally throw exceptions. You need to be concerned about three of these:
Some utilities that you might want to use:
public static int cs101.util.MoreMath.randomInt( int range );returns an int between 0 and range, inclusive. You can refer to it as MoreMath.randomInt if you include the line import cs101.util.*; at the top of your file.
public static void Thread.sleep( int millis ) throws InterruptedException;causes the current Thread to sleep for millis milliseconds. This allows you to put delays in your nodes. Warning: You will have to catch the exception, though you don't necessarily have to do anything with it.
If you have other ideas, design them and then ask us about them.
You will need a record of your experimentation with TypeAConnectingNode and TypeCConnectingNode, a development plan containing at least four steps with testing strategies, and two different channel selection strategies before you come to lab. You should also be able to sit down and write the simplest piece of code that you will test (i.e., what you need to get it to compile) when you first come to lab. This means that you will need to be able to answer the question marked with a Q, below.
Begin the lab by implementing a node behavior. You will need to define a new class, say MyClass. What should you call the file it's defined in? What does it need to extend or implement? What methods does it have? (Remember that the interactive control loop is already defined for you, but it expects to be able to call your transmitPacket() method with two lists.
The first lines of your file, before your class definition, will need to say:
package nodenet;Also, you should be sure that your class is public.
Once your code compiles, you should try running it. To run your code, you will need to supply the name of your node behavior class to nodenet. You can do this by supplying the fully-qualified name of your class just as you did with nodenet.TypeAConnectingNode, etc. Remember that the fully-qualified class name includes the package the class is in: nodenet.MyClass. (assuming that MyClass is the name of your class, and also that you've already compiled your class.)
You can now test your code using a the network(s) that you have designed for this purpose. Does it work as you expected? Does it break as you expected? Record your observations. When you have verified that your network works as you expected (or not, and you have understood why not), go on to the next step in your development plan.
Don't forget to tell the course staff about your networks, so that we can use them, too :)
Writing a class that compiles, runs, and attempts to forward packets is the target exercise for this lab. It need not forward packets perfectly, just make some progress on sane networks.