[an error occurred while processing this directive]

PS5 Comments

1. Explain how the specification of our SimObject datatype violates behavioral subtyping principles. Is there any way to specify SimObject that doesn't violate the substitution principle?
The run method in SimObject has a stronger precondition than the run method in Runnable. The supertype method specification has no precondition, but the subtype precondition is REQUIRES: this is initialized.

The run method in Runnable was specified with EFFECTS: true to allow subtypes to do anything in the run method, but because it has no precondition, subtypes cannot add preconditions. To deal with this problem in SimObject we would need to remove the precondition from run, and modify the implementation so the constructor does the initialization. This would make it harder to use the SimObject type, however.

2. Draw a module dependency diagram showing the design for the walker simulation. It should include all the important classes listed above, and show the subtyping ("is-a") and dependency ("has-a" or "uses-a") relationships between the classes. Identify any circular dependencies in the design and explain why they are necessary (or how they could be avoided).
Here are the replationships:
ClassIs-A (direct supertypes)Has-aUses-a
Gridjava.lang.ObjectSimObjectCoordinate
GridDisplayjava.lang.ObjectGrid, SimObjectChooser 
Coordinatejava.lang.Object  
Directionjava.lang.Object GenRandom
MobileSimObjectSimObject Grid
RandomWalkerMobileSimObject Grid,Direction
SimObjectjava.lang.Object, RunnableGrid
Simulatorjavax.swing.JPanel, java.awt.event.ActionListenerGridDisplay(Grid)
WalkerSimulatorjava.lang.Object Grid, Simulator

Note that the dependency of Simulator on Grid is a weakly uses dependency — Simulator does not invoke any Grid methods, but relies on the existence of a Grid type (but not its specification).

Here is the Modular Dependency Diagram:

One question the design should raise is why isn't WalkerSimulator a subtype of Simulator? At least from the name, and the intended use, it would have made more sense for WalkerSimulator to be a subtype of Simulator.

There is a circular dependency between Grid and SimObject. The Grid implementation depends on SimObject since it uses an array of SimObjects to store the grid objects and calls SimObject methods (pauseObject, resumeObject). The SimObject depends on the Grid since it has an instance variable that stores the Grid this object is on and uses this in the getNeighbors method. A cleaner design would avoid this circular dependency, but doing this is tricky. It is very useful in the SimObject implementation to have access to the containing Grid. One way to avoid this would be to have a single Grid object and make its methods static methods. This would work for the WalkerSimulator since there is only one grid, but it would limit other uses of the grid. The getNeighbors method probably belongs in the Grid class instead of the SimObject. If we moved it there, it would take the location as a parameter, and return an enumeration of the SimObjects neighboring that location.

3. Fix the code for RandomWalker.executeTurn so that two MobileSimObjects will never go into the same square. Your fix should not need to modify any code outside the RandomWalker.executeTurn method. After your fix, you should be able to run the simulation for as long as you want without ever getting an exception for two objects entering the same square.
We need to make sure the square we want to walk into does not become occupied between the time we test it is empty, and when we move into it. We can do this by locking the grid by putting a synchronized (getGrid) around the test and call to setLocation:
public void executeTurn() throws RuntimeException {
   inTurn = true;
   Direction dir = Direction.randomDirection ();
   int newrow = getRow () + dir.northerlyDirection ();
   int newcol = getColumn () + dir.easterlyDirection ();

   if (getGrid ().validLocation (newrow, newcol)) {
      synchronized (getGrid ()) {
         if (getGrid().isSquareEmpty (newrow, newcol)) {
            delay (500);
            setLocation (newrow, newcol);	
          }
      }
   ...
4. Explain why the execution would get stuck if the threads executed in the order described.
The execution gets stuck because the descartes object is locked by the descartes thread, and at waiting to enter the colleage.argue method for its colleague object, plato. The plato object is locked by the plato thread, and waiting to enter the colleague.argue method for its colleague object, descarte. Neither thread can proceeed, since argue is synchronized (it requires the calling thread to hold the lock on the this object).
5. (Tricky, extra credit if you can answer this) Our new Philosopher class now has a race condition that could lead to a deadlock or run-time exception (but it would never be apparent from our current main class). Explain what the race condition is, and construct code that reveals it. Feel free to insert sleep pauses as necessary to make it easier to reveal.
The race condition is that the colleague of the this object could change between the name.compareTo call in the this object's associated thread and the name.compareTo call in the colleague object thread. Since philsophize is not synchronized, the (synchronized) setColleague method could execute in another thread while philosophize is executing. Then, the wrong lock would be grabbed! Instead of locking our current colleague, we would grab the lock for our previous colleague and then call colleague.argue (which is synchronized, and will wait for the lock on our current colleague).

Even without the race condition, there are ways in which our solution could still deadlock: we could have three philosophers who have non-reflexive colleagues. Perhaps we should have a rep invariant that requires colleague relationships be reflexive, and modify the setColleague method to set the colleague's colleague to this.

It could also deadlock if two philosopher's have the same name. Then compareTo returns 0 in both cases.

6. Fix the DrunkPhilosopher class to avoid the deadlock. (Your fix should not involve removing or changing the delay calls to make the deadlock less likely.

We need to remove the synchronized modifier on the philosophize and executeTurn methods, and add code to grab the locks is a set order as with the Philosopher class.
7-9. Here are the applets: