| 6.170 | Laboratory in Software Engineering
Spring 2000 Problem Set 2: Using Abstract Data Types Due: February 17, 2000 Handout 6 |
Each assignment should be handed in as hardcopy to your TA or to the course secretary (Kincade Dunn, NE43-529) by 4:30 PM on its due date. It is usually most convenient to give the assignment to your TA at the end of class on that day. Additionally, the source and compiled code for each problem set should be made available on Athena so your TA can test your program.
You should put your solution to this problem set in ~/6.170/ps2.
See the general information sheet (handout 1) for more detail
The abstractions you implement will need to be robust against errors. You will be required to develop specifications and implementations that take this into account.
public static String readStringFromFile(String filename) throws BadFilenameException /* effects: returns the contents of the file named * by <filename>, unless: * an I/O error occurs in the process of opening, * reading, or closing the file named by <filename> * => BadFilenameException */
public static void writeStringToFile(String str, String filename) throws BadFilenameException /* effects: creates a new file named by <filename> * whose contents are the contents of <str>, unless: * a file named by <filename> already exists * => BadFilenameException * an I/O error occurs in the process of creating, * writing, or closing the file named by <filename> * => BadFilenameException */
| Note that we do not write "throws NullPointerException" in method signatures, nor do we include NullPointerException in method signatures. In this way, we follow general Java conventions but differ from the convention described in Program Development in Java. For all code you write this term, you should follow the general conventions and not include NullPointerException in signatures or specifications. |
| We suggest you use the FileReader and FileWriter classes in the standard Java package java.io. Some resources you may find useful are: a brief introduction to files and streams in section 2.7 of the class text, Chapter 8 of Beginning Java 2, and a small online tutorial at http://java.sun.com/docs/books/tutorial/essential/io/filestreams.html. |
| x | f(x) |
| a | w |
| b | v |
| c | u |
| d | t |
| e | s |
To help you implement these functions, we have provided the MapCC class. A MapCC is a collection of key-value pairs, where each key and each value is a character. The specification for MapCC is in the appendix. To use MapCC, make sure that your CLASSPATH is set as described in Handout 1.
Encode functions can be represented as strings. The string representation of a function f is a sequence of 4-character units. Each unit contains one character of cleartext (x), a space, one character of ciphertext (f(x)), and a newline ('\n'). In addition, we impose three semantic constraints on string representations of functions:
| It's a little bit tricky to make a file without a trailing newline
('\n'), with the Emacs editor. By default, Emacs ensures that all
files have a trailing newline. Therefore if you write a test input file
to be encrypted, and your encryption map does not have '\n' in the domain,
you may be surprised by the behavior. You can generate files without
a trailing newline in Emacs by adding
|
Here is your task:
Complete the following specifications so that the methods are robust; that is, they should not fail unexpectedly, but should either complete their task or throw an appropriate exception.
public static void makeMap(String mapString, MapCC enc, MapCC dec) throws ??? /* requires: ??? * modifies: enc, dec * effects: fills in the encode and decode maps corresponding * to the encode function represented by <mapString>, unless: * ??? */
public static String crypt(String input, MapCC map) throws ??? /* requires: ??? * effects: encrypts the string <input> by reversing and * translating the characters using the function in <map>. * (Note that this method can be used to encrypt or decrypt * depending on whether <map> is an encode or decode map.) * Returns the result as a new string, unless: * ??? */Be sure to consider unusual cases; this is the challenging part of the problem. For example, what should makeMap do if mapString is not well-formed? What should crypt do if map is not defined on some character of input? You will probably want to create some aptly-named checked exception classes, as in Problem 1. Be sure to document your choices.
class WorkDescriptor {
/* A WorkDescriptor is valid if
* <inFilename> and <outFilename> are non-empty
* at most one of <compress> and <uncompress> is true
* at most one of <encrypt> and <decrypt> is true
* if <encrypt> or <decrypt> is true, <ttFilename> is non-empty
*/
public String ttFilename; // file containing encoding string
public boolean compress;
public boolean uncompress;
public boolean encrypt;
public boolean decrypt;
public String inFilename; // file to be transformed
public String outFilename; // file to contain transformed output
}
public static void doWork(WorkDescriptor wd) throws NotPossibleException /* effects: Carries out the transforms on files as * specified in <wd>, unless: * there is some problem performing the transforms * => NotPossibleException is thrown, and no output file is written */If the WorkDescriptor passed to doWork specifies more than one transform to be carried out at the same time, use the following rule to decide the order in which to apply the transformations: only uncompressed text can be encrypted or decrypted. This means that are several cases to be implemented by doWork depending on exactly which transforms are selected by the WorkDescriptor.
| Do not change the signature or specification of doWork.
Do not throw any other exceptions other than NotPossibleException.
We expect that NotPossibleException is thrown if and only if there
is an error. (Of course you will have to figure out exactly what
"error" means.) Also note that no output file should be written if
NotPossibleException
is
thrown. If you change the interface to doWork, we may not
be able to test and grade your code!
Remember to handle any exceptions that are thrown by the methods you call. For example, if doWork calls readStringFromFile, and readStringFromFile throws BadFilenameException, you will want to catch that and throw NotPossibleException instead. Also remember to close files you've opened. |
The command-line interface prompts the user for commands, the names of the files containing the input and translation table, and the name of the file where the output from the operations on the input should go. Only the first character of each user input to the interface matters. Here is an example of interaction with a sample interface, with user input in bold:
command? [(t)ransform/(q)uit] transform input filename? foo.txt output filename? bar.txt compression? [(c)ompress/(u)ncompress/(n)o] compress encryption? [(e)ncrypt/(d)ecrypt/(n)o] decrypt translation filename? ttable.txt ... done command? [(t)ransform/(q)uit] trans input filename? foo.txt output filename? bar.txt compression? [(c)ompress/(u)ncompress/(n)o] c encryption? [(e)ncrypt/(d)ecrypt/(n)o] no ... done command? [(t)ransform/(q)uit] quit goodbyeNote that if neither encryption nor decryption is requested, there is no prompt for the translation filename.
You will want to use Java's System.in to query the user. The
method System.in.read does not return characters, so it is more
convenient to convert the input stream to a BufferedReader. This
code fragment queries the user twice and displays the results of the queries:
BufferedReader In = new BufferedReader(new InputStreamReader(System.in));
System.out.print("Input1? ");
String input1 = In.readLine();
System.out.print("Input2? ");
String input2 = In.readLine();
System.out.println("Input1 = " + input1);
System.out.println("Input2 = " + input2);
|
The main challenge of this problem is to make the application robust. The application should behave predictably in all cases (bad input from the user, corrupt files, I/O errors, etc.) If an error occurs, the application should display a useful error message. For example, if asked to decompress a file containing "xy44", the application should report that the file format is corrupt, rather than generating an output file containing "xy4444".
We are not requiring you to write or hand in test cases for this problem. However, we strongly recommend that you do test your application carefully because we will be running our own test cases, and your code will be graded on its robustness.
public class PS2 {
public static void main(String[] args)
/* effects: Runs the file transformation application
* with a command-line interface.
*/
}
/* Overview: A MapCC is a set of key-value pairs, where the key
and value are chars. The first entry of each pair is the KEY
and the second entry is the VALUE of the mapping relationship.
Each KEY can occur at most once in the map. MapCCs are mutable.
A typical MapCC is {<k1, v1>,...,<kn, vn>}
such that ki = kj => i = j. */
public MapCC()
// effects: Initializes this to be a new, empty map
public void addPair(char key, char value) throws DuplicateException
/* modifies: this
* effects: Inserts the pair (<key>, <value>) into the table.
* Throws DuplicateException if there is already a
* pair in this with key <key>.
*/
public char lookup(char key) throws NotFoundException
/* effects: Throws NotFoundException if there is no pair in this
* with key <key>, else returns the value associated with <key>.
*/
public void deletePair(char key) throws NotFoundException
/* modifies: this
* effects: removes the pair (<key>, c) from the table.
* Throws NotFoundException if there is no pair
* in this with key <key>.
*/
public int size()
// effects: Returns the number of pairs in the table
public boolean isMember(char k)
// effects: Returns true iff k is a key in this.
Here is an example of using a MapCC:
public MapCC map = new MapCC();
map.addPair('a', 'z');
System.out.println("SIZE: " + map.size()); // PRINTS SIZE: 1
char c = map.lookup('a');
System.out.println("KEY a GIVES VALUE " + c); // PRINTS KEY a GIVES VALUE z
map.deletePair('a');
System.out.println("Size: " + map.size); // PRINTS SIZE: 0