University of Virginia, Department of Computer Science
CS201J: Engineering Software, Fall 2002

Problem Set 3: Implementing Data Abstractions Out: 10 September 2002
Due: 19 September 2002, before class

Collaboration Policy (same as PS2)

For this problem set, you may either work alone and turn in a problem set with just your name on it, or work with one other student in the class of your choice. If you work with a partner, you and your partner should turn in one assignment with both of your names on it.

Regardless of whether you work alone or with a partner, you are encouraged to discuss this assignment with other students in the class and ask and provide help in useful ways. You may consult any outside resources you wish including books, papers, web sites and people. If you use resources other than the class materials, indicate what you used along with your answer.

Reading: Before beginning this assignment, you should read Chapter 5.3-5.10.

Purpose

In Problem Set 2, you used the StringTable data abstraction that provided a mapping between Strings and float values. For this problem set, you will implement the StringTable data abstraction.

We believe the examples in this problem set will give you enough information about ESC/Java to do what you need for this assignment. A manual for ESC/Java is available (http://gatekeeper.dec.com/pub/DEC/SRC/technical-notes/SRC-2000-002.html) (but we don't think you'll need it for this problem set).

Reasoning About Data Abstractions

First, we will consider a implementing the Poly abstraction from Chapter 5 (specified in Figure 5.4). Suppose we implement Poly using a java.util.Vector. The Vector type allows us to represent an ordered collection of Objects. The objects we will store in our vector are records of <term, coefficient>. Here is the representation:
import java.util.Vector;

class TermRecord {
   // OVERVIEW: Record type 
   int power;
   int coeff;
   TermRecord (int p_coeff, int p_power);
}

public class Poly {
   private Vector terms; // A Vector of TermRecord objects.

   ...
}
Suppose this is the implementation of degree:
   public int degree () {
      // EFFECTS: Returns the degree of this, i.e., the largest exponent
      //    with a non-zero coefficient.  Returns 0 if this is the zero Poly.
      return terms.lastElement ().power;
    }
Question 1: (8) What rep invariant would make the implementation of degree above satisfy its specification?

In an alternate implementation with the same rep, suppose this is the implementation of coeff:

   public int coeff (int d) {
      // EFFECTS: Returns the coefficient of the term of this whose
      //     exponent is d.  

      int res = 0;
      Enumeration els = terms.elements ();

      while (els.hasMoreElements ()) {
	 TermRecord r = (TermRecord) els.nextElement ();
	 if (r.power == d) { res += r.coeff; }
      }

      return res;
   }
Question 2: (7) What rep invariant would make the implementation of coeff above satisfy its specification? Explain how a stronger rep invariant would make it possible to implement coeff more efficiently.

Annotating Rep Invariants

The StringSet data abstraction is similar to IntSet from Figure 5.6. We represent a set of strings using a Vector. Its abstraction function is similar to that for IntSet (p. 101):
   AF(c) = { c.els[i] | 0 <= i <= c.els.size }
And its rep invariant (p. 102) is:
   c.els != null && 
   for all integers i . c.els[i] is a String &&
   there are no duplicates in c.els
If we run ESC/Java on StringSet.java without adding any annotations, eight warnings are produced. Note: when you run escjava in the DOS shell, you can use | more to prevent the messages from scrolling up the screen: escjava StringSet.java | more

The first warning message is:

StringSet: insert(java.lang.String) ...
------------------------------------------------------------------------
StringSet.java:25: Warning: Possible null dereference (Null)
        if (getIndex (s) < 0) els.add (s); 
                                 ^
In Java, a variable declared with an object type can either hold a value of the declared type, or the special value null (which is treated as a value of any object type). So, els is either a Vector or null. But, if els is null, it would be an error to invoke a method on it, hence ESC/Java produces a warning for the method call els.add. How does the programmer know this call is safe?

The answer is the rep invariant — it contains the term c.els != null, so we know els is not null for the this object at the beginning of insert. Since nothing in insert assigns to els, we know it is still not null when we call els.add.

To prevent the ESC/Java warning, we need to document the rep invariant using a formal annotation. Formal annotations are Java comments that are ignored by the Java compiler, but interpreted by ESC/Java. They are denoted by a @ character after the comment open. We express it as: //@invariant els != null.

After we have added the annotation (inv1/StringSet.java), running ESC/Java produces four warnings. The first message warns that the precondition for add may not be satisfied:

StringSet: insert(java.lang.String) ...
------------------------------------------------------------------------
StringSet.java:27: Warning: Precondition possibly not established (Pre)
        if (getIndex (s) < 0) els.add (s); 
                                      ^
Associated declaration is "/net/af10/evans/escjava/escjava/lib/specs/java/util/Collection.spec", line 217, col 8:
    //@ requires !containsNull ==> o!=null
        ^
A Vector may contain either all non-null elements or some possibly null elements. If the Vector containins only non-null elements, the precondition for add requires that the parameter is non-null. Note that our informal rep invariant did not preclude having null in the Vector.

To make ESC/Java happy, we must explicity state whether or not the Vector can contain null. We choose to allow null in the Vector by adding //@invariant els.containsNull == true to indicate that the els Vector may contain null.

The second message is similar to the first one, except it is warning about the type of the Vector elements, not whether or not they are null:

StringSet.java:27: Warning: Precondition possibly not established (Pre)
        if (getIndex (s) < 0) els.add (s); 
                                      ^
Associated declaration is "/net/af10/evans/escjava/escjava/lib/specs/java/util/Collection.spec", line 218, col 8:
    //@ requires \typeof(o) <: elementType || o==null
        ^
Since the elements of a Vector may be any object type, we need to document the actual type of the Vector elements. In our informal rep invariant, we expressed this as for all integers i . c.els[i] is a String. We can express this with a formal annotation: //@invariant els.elementType == \type(String).

So, now our invariant annotations are:

    //@invariant els != null
    //@invariant els.containsNull == true
    //@invariant els.elementType == \type(String)
Running ESC/Java produces four warnings. The first two reveal limitations of ESC/Java:
StringSet: StringSet() ...
------------------------------------------------------------------------
StringSet.java:24: Warning: Possible violation of object invariant (Invariant)
    }
    ^
Associated declaration is "StringSet.java", line 18, col 7:
    //@invariant els.containsNull == true
       ^
Possibly relevant items from the counterexample context:  
  (vAllocTime(brokenObj<4>) < after@21.24-21.24)
  ... (18 lines cut showing counterexample)
  brokenObj<4> != null
(brokenObj* refers to the object for which the invariant is broken.)

------------------------------------------------------------------------
StringSet.java:24: Warning: Possible violation of object invariant (Invariant)
    }
    ^
Associated declaration is "StringSet.java", line 19, col 7:
    //@invariant els.elementType == \type(String)
       ^
Possibly relevant items from the counterexample context:  
  objectToBeConstructed == brokenObj<5>
  RES-21.24:21.24 == brokenObj<5>
(brokenObj* refers to the object for which the invariant is broken.)
ESC/Java is not able to prove the invariant is true for the constructor, but by inspecting the code we can convince ourselves that it is. The Vector constructor returns a vector with no elements, so it does not contain null, and every element it contains (that is, none) is of type String. We can add set annotations to convince ESC/Java the invariant is true:
    public StringSet () {
	// EFFECTS: Initializes this to be empty: { }
	els = new Vector ();
	//@set els.elementType = \type(String)
	//@set els.containsNull = true
    }  // ESC/Java is unable to prove the invariant for the empty constructor without the set's.

The next warning is:

StringSet: remove(java.lang.String) ...
------------------------------------------------------------------------
StringSet.java:38: Warning: Precondition possibly not established (Pre)
        els.removeElementAt (i);
                            ^
Associated declaration is "/net/af10/evans/escjava/escjava/lib/specs/java/util/Vector.spec", line 569, col 8:
    //@ requires index < elementCount ;
        ^
The precondition for Vector.removeElementAt requires that the value of the parameter is a valid index of an element in the vector: requires index < elementCount. The elementCount is a specification variable that indicates the number of elements in the vector.

We know this is safe because getIndex always returns a value less than elementCount. To enable ESC/Java to use this, we need to document it as a postcontition of getIndex. This is done by adding an ensures clause (which has the same meaning as an informal EFFECTS clause):

    private int getIndex (String s) 
       //@ensures \result < els.elementCount
The final warning is:
StringSet: getIndex(java.lang.String) ...
------------------------------------------------------------------------
StringSet.java:50: Warning: Possible null dereference (Null)
            if (s.equals (els.elementAt (i))) {
                 ^
The method call dereferences s, the parameter to getIndex. We could either add a precondition that s is not null, or fix the code of getIndex to handle the case where s is null. Since we decided to allow null in the StringSet, we take the second approach and change the implementation of getIndex to work when s is null:
    private int getIndex (String s) 
        //@ensures \result < els.elementCount
    {
	// EFFECTS: If x is in this returns index where x appears, else returns -1.
	for (int i = 0; i < els.size (); i++) {
	    if (s == null) {
		if (els.elementAt (i) == null) {
		    return i;
		}
	    } else {
		if (s.equals (els.elementAt (i))) {
		    return i;
		}
	    }
	}

	return -1;
    }
Now, running ESC/Java on StringSet.java produces no warnings.

Note that we did not include the there are no duplicates in c.els part of our informal invariant in our formal annotations. Some terms in invariants are too complex to describe and check with ESC/Java. To check this at runtime, we should define and use a repOk method.

Question 3: (10) In the example above, we decided to allow null in the els vector. A reasonable alternative design decision would be to not allow null in the els vector. Modify StringSet.java to not allow null in the string set.

Start with inv1/StringSet.java, the version of StringSet after adding the first invariant clause. Instead of adding //@invariant els.containsNull == true you will add //@invariant els.containsNull == false.

Check your implmentation with ESC/Java, and add annotations or fix the code so running ESC/Java on your program produces no warnings. (Recall that you can require a parameter to be non-null using //@requires s != null.)

Implementing StringTable

For the remaining questions, you will implement the StringTable data abstraction you used in Problem Set 2. Your implementation should satisfy this specification for StringTable. (Note: it is slightly different from the PS2 specification. We have removed the exception from addName and changed its effects clause accordingly).
public class StringTable 
{
    // overview: StringTable is a set of <String, double> entries,
    //    where the String values are unique keys.  A typical StringTable
    //    is {<s0: d0>, <s1: d1>, ... }.
    //

    //@ghost public int numEntries ; // The number of entries in the table
    
    public StringTable () 
      // effects: Initializes this as an empty table: { }.
      //@ensures numEntries == 0;
      { }

    /* 
    ** This method was used in PS2, but you do not need to implement it for PS3.

    public StringTable (java.io.InputStream instream) 
       // requires: The stream instream is a names file containing lines of the form
       //                   <name>: <rate>
       //           where the name is a string of non-space characters and the rate is
       //           a floating point number.
       // modifies: instream
       // effects:  Initializes this as a names table using the data from instream.
       { }
    */

    public void addName (/*@non_null@*/ String key, double value) 
       // requires: The parameter name is not null.  (This is what the
       //    ESC/Java /*@non_null@*/ annotation means.)
       // modifies: this   
       // effects:  If key matches the value of String in this, replaces the value associated
       //    with that key with value.  Otherwise, inserts <key, value> into this.
       //           e.g., if this_pre = {<s0, d0>, <s1, d1>, <s2, d2>}
       //                     and s0, s1 and s2 are all different from key
       //                 then this_post = {<s0, d0>, <s1, d1>, <s2, d2>, <key: double>}.
       //                 if this_pre = {<s0, d0>, <s1, d1>, <s2, d2>}
       //                     and s1 is the same string as key
       //                 then this_post = {<s0, d0>, <s1, value>, <s2, d2>}
       //                 
       //@modifies numEntries
       //@ensures  numEntries >= \old(numEntries);
       { }  
    
    public double getValue (String key)
       // EFFECTS: Returns the value associated with key in this.  If there is no entry matching
       //      key, returns 0.
       // Note: it would be better to throw an exception (but we haven't covered that yet).
       { }
    
    public /*@non_null@*/ String getNthLowest (int index)
       // requires: The parameter index is non-negative and less than
       //    the the number of entries in this.
       //@requires index >= 0;   
       //@requires index < numEntries;
       // EFFECTS: Returns the key such that there are exactly index entries in the table for
       //    with the value of the entry is lower than the value of the returned key.  If two
       //    keys have the same value, they will be ordered in an arbitrary way such that
       //    getNthLowest (n) returns the first key and getNthLowest (n + 1) returns the second key.
       //
       //    e.g., getNthLowest (0) returns the key associated with the lowest value in the table.
       //          getNthLowest (size () - 1)  returns the key associated with the highest value in the table.
       { }
   
    public int size ()
       // EFFECTS: Returns the number of entries in this.
       //@ensures \result == numEntries;
       { }
    
    public String toString ()
       // EFFECTS: Returns a string representation of this.
       { }

    public /*@non_null@*/ StringIterator keys () 
       // EFFECTS: Returns a StringIterator that will iterate through all the keys in this in 
       //    order from lowest to highest.
       { }
}


Question 4: (10) Describe at least two possible representation choices for StringTable. Discuss the advantages and disadvantages of each representation, and explain why you selected the representation you did. Your choice of representation will have a big impact on how difficult it is to implement the StringTable data type, so think carefully about how you will implement all the methods of StringTable in choosing your representation.

Question 5: (10) What is your representation invariant?




Question 6: (10) What is your abstraction function? Your abstraction function should map your concrete representation to the abstract string table.

The StringIterator datatype returned by the keys method is implemented by StringIterator.java. Its constructor expects an java.util.Enumeration object, which is what java.util.Vector.elements () returns.

Question 7: (25) Implement the StringTable datatype. Check your implementation with ESC/Java. Add annotations to document your invariant and preconditions and postconditions of methods as necessary to remove warnings.

Question 8: (10) Devise a testing strategy for your implementation. You should add a main method to your StringTable class that runs the test cases. Describe your testing strategy in general, and any problems in your implementation that were discovered in testing.

Question 9: (10) Submit your code using the Submission Form. Your program will be tested on public test cases, as well as secret test cases (which will not be revealed until after the assignment is due). You receive 10 points if your code passes all the test cases.

Turn-in Checklist: On Thursday, 19 September, bring to class a stapled turn in containing your written answers to all questions and all the code you wrote. Your final code should also be submitted using the Submission Form.

Credits: This problem set was developed for UVA CS 2001J Fall 2002 by David Evans and tested by Serge Egelman.


Changes and Clarifications:


CS201J University of Virginia
Department of Computer Science
CS 201J: Engineering Software
Sponsored by the
National Science Foundation
cs201j-staff@cs.virginia.edu