import java.io.*; import java.util.Vector; import java.util.Enumeration; class TableEntry { /*@non_null@*/ public String name; public double value; public TableEntry (/*@non_null@*/ String n, double r) { name = n; value = r; } public String toString () { return "<" + name + ": " + value + ">"; } } public class StringTable { // overview: StringTable is a set of entries, // where the String values are unique keys. A typical StringTable // is {, , ... }. // //@ghost public int numEntries ; // The number of entries in the table // Rep: private Vector entries ; // Rep Invariant: //@invariant entries.elementType == \type(TableEntry) //@invariant entries.containsNull == false //@invariant entries.elementCount == numEntries //@invariant entries != null; // invariant the entries are sorted in order by entries[i].value // for all 0 <= i < j < entries.size, entries[i].value <= entries[j].value // invariant the entries do not contain duplicate keys // for all 0 <= i < entries.size, 0 <= j < entries.size, // entries[i].key.equals (entries[j].key) ==> i = j // Abstraction Function: // // AF(c) = { | 0 <= i < entries.size } public StringTable () //@ensures numEntries == 0; { entries = new Vector (); //@set entries.elementType = \type(TableEntry) //@set entries.containsNull = false } /* ESC/Java cannot establish invariant for empty Vector */ /* ** This method was used in PS2, but you do not need to implement it for PS3. */ // Read a StringTable structure from a file formated as the output of toString public StringTable (InputStream instream) // requires: The stream instream is a names file containing lines of the form // : // 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. { this (); try { StructuredReader reader = new StructuredReader (instream); while (true) { try { String name = reader.readThroughAny (":"); reader.skipWhitespace (); double value = reader.readDouble (); reader.skipWhitespace (); if (lookupEntry (name) != null) { System.err.println ("Names file contains duplicate entry: " + name); } else { addName (name, value); } } catch (EOFException e) { break; } } reader.close (); // It would be better to propagate these exceptions to the caller, // but for PS2, we avoid the use of exceptions. } catch (IOException e) { System.err.println ("Error reading names file: " + e); } catch (NoNumberException e) { System.err.println ("Error reading number in names file: " + e); } catch (NumberFormatException e) { System.err.println ("Number format error reading names file: " + e); } } // To avoid duplicate code, we implement a helper method for looking // up an entry in the table. Note that it is declared as "private" // so is not visible to callers. Because it is private, we can // return a mutable component of the rep here. If callers were // allowed to call lookupEntry, that would expose the rep. private TableEntry lookupEntry (String name) // effects: If there is an entry in the table where entry.name.equals (name), // returns that entry. Otherwise, returns null. { for (Enumeration e = entries.elements (); e.hasMoreElements (); ) { TableEntry entry = (TableEntry) e.nextElement (); if (entry.name.equals (name)) { return entry; } } return null; } public void addName (/*@non_null@*/ String name, 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 into this. // e.g., if this_pre = {, , } // and s0, s1 and s2 are all different from key // then this_post = {, , , }. // if this_pre = {, , } // and s1 is the same string as key // then this_post = {, , } // //@modifies numEntries //@ensures numEntries >= \old(numEntries); { TableEntry oldentry = lookupEntry (name); if (oldentry != null) { // We can't just replace the value - that would break the rep invariant. // Instead, we remove it and add the entry again. entries.removeElement (oldentry); } // Linear search to find the location to insert (before the first // element whose value is greater than this one) int index; for (index = 0; index < entries.size (); index++) { TableEntry entry = (TableEntry) entries.elementAt (index); if (entry.value > value) { break; } } entries.insertElementAt (new TableEntry (name, value), index); } public double getValue (String name) { TableEntry entry = lookupEntry (name); if (entry != null) { return entry.value; } else { return 0; } } public /*@non_null@*/ String getNthLowest (int index) //@requires index >= 0; //@requires index < numEntries; { TableEntry entry = (TableEntry) entries.elementAt (index); return entry.name; } public int size () // EFFECTS: Returns the number of entries in this. //@ensures \result == numEntries; { return entries.size (); } public /*@non_null@*/ StringIterator keys () // EFFECTS: Returns a StringIterator that will iterate through all the keys in this in // order from lowest to highest. { Vector allNames = new java.util.Vector (); //@set allNames.elementType = \type(String) ; //@set allNames.containsNull = false ; for (Enumeration e = entries.elements (); e.hasMoreElements (); ) { allNames.addElement (((TableEntry) (e.nextElement ())).name); } return new StringIterator (allNames.elements ()); } //@nowarn Invariant /* ESC/Java has a bug in checking the invariant here, but we know it is preserved since the code doesn't modify the representation at all. The problem is ESC/Java cannot distinguish between a Vector that is used to represent a StringTable, and the local Vector variable that happens to have the same type. */ public String toString () { // Using the mutable StringBuffer type instead of the immutable String // type will save the trouble of making lots of new String objects. StringBuffer res = new StringBuffer ("{ "); boolean firstone = true; for (Enumeration e = entries.elements (); e.hasMoreElements (); firstone = false) { TableEntry entry = (TableEntry) e.nextElement (); if (!firstone) { res.append (", "); } res.append (entry.toString ()); } res.append (" }"); return new String (res); } }