| 
cs205: engineering software? | 
(none) 05 April 2010  | 
1. (7.1 / 10) Which design is better? State clearly the reasons the design you choose is better than the other design.
In Design A, all the datatypes for representing things on the map are subtypes of MapLocation. In Design B, the datatypes that represent things on the map depend on MapLocation, but are not subtypes of it. Which makes more sense? This depends on understanding if the relationship between the other types and MapLocation is a is-a relationship (subtyping) or a has-a relationship (dependency). From the description of map location ("the MapLocation datatype represents a location on the map (for example, using its longitude and latitude)"), it is clear that the has-a relationship is more appropriate. For example, a Road is not a single location on a map; it is a connection between two or more locations. (The following questions make this even more clear).
Very few people answered this correctly. This was not meant to be a trick question, but most answers favored Design A for its apparent simplicity. If you think more carefully about the subtyping relationships (it doesn't make sense for Road to be a subtype of MapLocation), and extensibility (no easy way to add new kinds of places), there is no good argument favoring design A.
   Vector<MapLocation> waypoints;
   Vector<Intersection> intersections;
    
   class Intersection {
      MapLocation loc;
      Road road;
   }
The abstraction function is straightforward:
  AF (rep) = < waypoints, intersections >
    where waypoints = < rep.waypoints.getAt (0), ...,
                           rep.waypoints.elementAt ([rep.waypoints.size() - 1) >
          intersections = < < rep.intersections.getAt(0).loc,
                              rep.intersections.getAt(0).road >, ...
                             < rep.intersections.getAt(rep.intersections.size() - 1).loc,
                               rep.intersections.getAt(rep.intersections.size() - 1).road > >
The rep invariant needs to ensure that the result of applying the
abstraction function to any concrete value that satisfies the rep
invariant satisfies the properties stated in the datatype overview.  
For the abstraction function to work, we need all objects involved to be
non-null:
    RI(c) = c.waypoints != null
        and c.waypoints[i] != null for all 0 <= i < c.waypoints.size()
        and c.intersections != null
        and c.intersections[i] != null 
              and c.intersections[i].loc != null
              and c.intersections[i].road != null
            for all 0 <= i < c.intersections.size ()
We also need to know there are at least 2 waypoints (otherwise the road
has no length).  This requirement was stated explicitly in the overview
specification:
       and c.waypoints.size() >= 2
The tougher constraint is that the intersections must be on the road.
This is somewhat clear from the overview specification, and more clear
from the addIntersection method sepcification.  Expressing it
formally is difficult, but we can express it clearly informally:
       and for all 0 <= i < c.intersections.size ():
          c.intersections[i].loc is on the line between 
             c.waypoints[j] and c.waypoints[j+1] for some j
          and on the line between
             c.intersections[i].road.waypoints[k] 
                 and c.intersections[i].road.waypoints[k+1]
             for some k
          and there are no pair of intersections at the same location 
              c.intersections[i].loc = c.intersections[j].loc ==> i == j
Some rep invariants also included a constratint that there must be at
least one intersection, although there is nothing in the specification
that seems to require this.
public Exit findNearestExit (MapLocation loc)
      throws NoExitException
   MODIFIES: nothing
   EFFECTS: If this contains no exits, throws NoExitException.
      Otherwise, returns an Exit on this such that there is no
      other exit on this that is geographically closer to loc.
Note that this specification makes it clear what "closest" means
(geographically closest without regard to the highway route).  A more
useful function might define "closest" as distance travelled along the
highway, and require the location to be on the road.
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).Suggest a way to fix this problem. A good answer will show clearly how you would change the code. Be careful to make sure that your fix does not allow any new deadlock opportunities.
public class Philosopher {
   private Philosopher colleague;
   private String name;
   private String quote;
   private Object colleagueLock;
   public Philosopher(String name, String quote) {
      this.name = name;
      this.quote = quote;
      this.colleagueLock = new Object();
   }
   public synchronized void setColleague(Philosopher p) {
      synchronized (colleagueLock) {
         colleague = p;
      }
   }
   public synchronized void argue() { ... } // elided
   public void philosophize () {
      Object lock1, lock2;
      if (colleague != null) { // Need a colleague to start an
         // argument.
         // Always grab the lock for whichever name is alphabetically
         // first
         synchronized (coleagueLock) {
            if (name.compareTo (colleague.name) < 0) {
               lock1 = this;
               lock2 = colleague;
            } else {
               lock1 = colleague;
               lock2 = this;
            }
	     
            synchronized (lock1) {
               synchronized (lock2) {
                   System.err.println (name + "[Thread " 
                     + Thread.currentThread().getName () + "] says " + quote);
                   colleague.argue ();
	       } 
            }
         } 
      }
   }
}
6. (6.7 / 10) According to the PS5 comments (question5), "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." Explain in detail a scenario where such a deadlock could occur, or explain why no scenario involving three philosophers with non-reflexive colleagues could produce a deadlock.
In fact, the PS5 comments are wrong — non-reflexive colleagues cannot be the cause of a deadlock. With three philosophers we have two situations to consider:
    iload_1
    iadd
(See Class
30 for information on the instructions.)
Data Abstraction. Java provides good support for data abstraction since we can use the class mechanism to package methods and state, and use private visibility modifier to hide the data representation from clients.
Iteration Abstraction. Java (since version 1.5) provides support for iteration abstraction using the Iterator interface. This allows datatypes to provide methods that enable clients to define a loop that iterates over a data collection without revealing the interals of the collection implementation.
Subtype Abstraction. Java supports subtype abstraction with the extends and implements mechanisms (both of which define subtyping relationships), and through the dynamic dispatching of methods. This allows objects of many types that are subtypes of a given type to be used in the same way.
Concurrency Abstraction. Java supports concurrency abstraction by providing threads and synchronization. These mechanisms hide some of the details of event ordering from a programmer, but don't hide the problems of preventing race conditions and deadlocks from the programmer.