CS 124, Spring 2013
Lab 8: Vigenère Cipher
Once again this week, you will be working with subroutines. In this case, you will work on an implementation of the Vigenère Cipher, the system for encoding and decoding text messages that was discussed and demonstrated in class. This is an exercise in writing subroutines and in using an API that provides some utility functions that can be used in solving the problem.
You should create an Eclipse project named lab8. You will need copies of the files CodeUtil.java and TextIO.java; You should copy them from /classes/cs124 into your project's src folder.
This lab is due at the beginning of Lab 9 (which is two weeks from today because of Spring Break). To turn in the lab, you can copy your project folder into your homework folder in /classes/cs124. (Alternatively, you can just turn in the Java files that are need to run your program; put them in a folder named lab8.)
The Assignment
You should write a program that does encoding and decoding of messages using the Vigenère cipher. For the main() routine in your program, you should use the following code, which you can copy-and-paste into your program:
public static void main(String[] args) { String plainText; // The un-encrypted message. String cipherText; // The encrypted message. String key; // The key for the Vigenere cipher. boolean encodeFlag; // Whether to do encoding or decoding. key = readVignereKey(); // returns a string of letters only! encodeFlag = askUserEncodeOrDecode(); if (encodeFlag) { plainText = readInput(); plainText = CodeUtil.lettersOnly(plainText); cipherText = vigenereEncode(plainText,key); writeOutput(cipherText); } else { cipherText = readInput(); cipherText = CodeUtil.lettersOnly(cipherText); plainText = vigenereDecode(cipherText,key); writeOutput(plainText); } } // end main();
To finish the assignment, you should write all the subroutines that are required by this main() routine. This includes writing a Javadoc comment for each subroutine. Here are the requirements for the subroutines:
- readVignereKey() -- must let the user enter one line of text, to be used as the key in the cipher. The value that the user enters can contain spaces and punctuation, but the string that is returned by the function must contain only letters.
- askUserEncodeOrDecode() -- must ask the user whether to encode or decode the user's input. A return value of true means to encode; a return value of false means to decode.
- readInput() -- must let the user enter multiple lines of text, with a blank line to indicate the end of the input. Input ends when the user presses return without typing anything. All the input lines must be combined into one long string, and that string is the return value of the function. Use a StringBuffer (see below).
- writeOutput(string) -- must write all the characters of the string to standard output (System.out), with a line break after every 60-th character. (In fact, the string will consist entirely of uppercase letters, with no line breaks in the string.)
- vigenereEncode(plainText,key) -- must encode the plaintext using the specified key, and return the resulting ciphertext. You can assume that every character in the plaintext and in the key is a letter. A character, ch, from the plaintext is to be encoded by calling CodeUtil.encode(ch,offset), where the offset is computed from one of the letters in the key. To determine which letter from key to use, you need the position of ch in plaintext. Convert that position into a position in key by taking position%key.length(). Use a StringBuffer to build the ciphertext string (see below).
- vigenereDecode(cipherText,key) -- must decode the ciphertext using the specified key, and return the resulting plaintext. This is almost identical to vigenereEncode(), except that you are decoding instead of encodeing.
You should use functions from CodeUtil.java when appropriate. Read that file to see what functions are available.
Here is an example ciphertext that was encoded using the key carpe diem. This is an encoding of the preamble to the U.S. constitution: "We the people of the United States, in order...". Your program should be able to decode it:
YEKWISMSBNEFUXKMYZKTVSWWIXQUIEDVGMVFQFFGQDUSDGPVGJHKXGPIFCIV BENNIJWNXAXUEEZCWXZIPQMVHXLKXDCNHJMOQXKRRFKMGMJATTYTGRUQAPDV UIQKIBTODDXHBLQIEETVDTAQNFRGIDVHEGCLGIWPINNEJHMQOWAHLZQIUBCF QOLGWHTZQUAESSXZTAUTVGMWGHAQRUPMQIRPGSKPFOQWTVHZHGRVWFKTLIMR VJATTYTYQQXQFSKPXHASRCMVGMFI
The StringBuffer Class
There are several points in the Vigenere program where you will have to create a potentially long string by combining shorter strings or individual characters. This coul be done using the "+" operation to combine the strings, but that can be inefficient if you do it a lot, since a new copy of the entire string is created each time you add something to the string. Java has a standard class named StringBuffer that is much more efficient for building lots of strings. You should use it in your program.
An example of using StringBuffer is in the lettersOnly method in CodeUtil.java. This example shows the typical use of a StringBuffer:
public static String lettersOnly(String str) { StringBuffer buffer = new StringBuffer(); for (int i = 0; i < str.length(); i++) { char ch = str.charAt(i); if ( ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' ) buffer.append(ch); } return buffer.toString(); }
You can compare this to performing the same task using "+":
String str = ""; for (int i = 0; i < str.length(); i++) { char ch = str.charAt(i); if ( ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' ) str = str + ch; } return str;
A StringBuffer builds up a string internally, using an append() method instead of "+". The lettersOnly subroutine creates a StringBuffer named buffer, adds characters to the buffer by calling buffer.append(), and gets the string that was created inside the buffer by calling buffer.toString(). Note that buffer.append() can be used to add strings and other types of data to the buffer, as well as characters.
Style Rules For Subroutines
Here are a few new style rules, relevant to writing subroutines and using member variables. You are expected to follow these rules, starting with Project 8.
- You should always use meaningful names for subroutines.
- The name of a subroutine should begin with a lower case letter.
- A subroutine should always have a single, well-defined task.
- Member variables should be used to save information about "state" that has to be saved between calls to subroutines. They should not be used for simple communication between routines; use parameters and return values for that.
- Every subroutine should have a comment that describes the task performed by the subroutine, the purpose of each of its parameters, and the meaning of the return value, if any. It should also describe any important exceptions that can be thrown by the subroutine. For non-private subroutines, at least, the comment should be in the form of a JavaDoc comment. That is, it should come just before the definition of the subroutine, and it should start with /** and end with */. (It is recommended, but not required, that you use the @param, @return, and @throws tags in the comment. These tags must always come at the end of the comment, after the description of the purpose of the subroutine.) Note that if you type /** on the line before a subroutine and press return, Eclipse will put in an outline of a JavaDoc comment for you.
- Public member variables should be rare. But if you define one, you should provide a JavaDoc-style comment just before the declaration of the variable.
- The main comment on a class should also be in JavaDoc format.
Note that Javadoc comments are covered in the textbook in Section 4.5.4 Be sure to read that section and make sure that you understand how to write Javadoc and what it is for.