CS 211 Project 3: Cipherous Symmetry
- Due: Friday 3/4/2016 by 11:59 pm
- Approximately 5.7% of total grade
- Submit to Blackboard
CODE DISTRIBUTION
- Project Files: p3pack.zip
- Test files: P3Tests.java
CHANGELOG
- Sun Feb 28 22:09:12 EST 2016
- There will be no honors problem for the project. Use the time to prepare for the first exam.
- Fri Feb 26 16:08:12 EST 2016
- A section has been added discussing strategies for implementing
MorseCipher
methods. Review this section if you are struggling to finish this portion of the project. - Mon Feb 22 08:53:23 EST 2016
- Clarification: if an
Alphabet
is asked toget(i)
wheni
is out of bounds, throw aNotInAlphabetException
with any character as theoffender
. Use the first constructor forNotInAlphabetException
which allows one to specify the exact message that will be displayed. Use any character foroffender
.
Table of Contents
1 Overview
1.1 Encryption Introduction
Encryption plays a central role in the age of computing. Every online purchase, every social media post, every login to a remote server would carry drastically more risk without the ability to obscure the contents of a message from intervening parties and still enable the message recipient to make sense of the message contents.
In this project, you will construct a small class hierarchy involving different kinds of encryption. The hierarchy will illustrate one of the nice features of inheritance: that several child classes can share and use code from a parent class in a uniform way. The fact that all of the classes will descend from a common parent also means they may be used interchangeably: a program wishing to do encryption could select from any of the ciphers provided here and utilize them via a uniform interface.
Several classes are provided for you to use but it will take some work to understand how to use them. Two of the encryption algorithms you will implement, the Caesar Cipher and the Vigenere Cipher, are very easy to understand and will provide an introduction to how inheritance works. The third cipher, MorseCipher, is different but we show how we can still fit it into our structure.
1.2 Class Hierarchy
There are a total of seven classes in this project but there is not a tremendous amount of code to write. Most of your effort will be spent understanding the organization of the eventual solution. The classes fall into the following hierarchy.
Cipher (abstract) | +--MorseCipher | +--SymmetricCipher (abstract) | +--CaesarCipher | +--VigenereCipher Alphabet NotInAlphabetException
1.3 Project Files
The following files are relevant to the project. Some are provided in
the project distribution while others you must create. You should
submit all files listed unless marked as Optional
(in the State
column).
File | State | Notes |
---|---|---|
Alphabet.java | Modify | Provided version contains only some important static fields to avoid mistyping |
AlphabetTests.java | Testing | |
NotInAlphabetException.java | Create | |
NotInAlphabetExceptionTests.java | Testing | |
Cipher.java | Create | Very short abstract class |
SymmetricCipher.java | Create | Descends from Cipher, abstract |
SymmetricCipherTests.java | Testing | |
CaesarCipher.java | Create | Descends from SymmetricCipher, concrete |
CaesarCipherTests.java | Testing | |
VigenereCipher.java | Create | Descends from SymmetricCipher, concrete |
VigenereCipherTests.java | Testing | |
MorseCipher.java | Modify | Descends from Cipher. Provided version contains only some important static fields to avoid mistyping |
MorseCipherTests.java | Testing | |
P3Tests.java | Testing | Run all standard tests for the project |
P3Demo.java | Provided | Demonstrates use of each Cipher |
junit-cs211.jar | Testing | JUnit library for command line testing |
ID.txt | Create | Create in setup to identify yourself |
1.4 A Demonstration
P3Demo.java
is part of the project pack and demonstrates the central
functionality of the Cipher
class hierarchy: descendants of Cipher
can be used interchangeably as they all provide functionality through
their encrypt() / decrypt()
methods. However, each child class
specializes these methods to scramble and unscramble text
differently. The demo also shows that several of the ciphers must be
initialized with an Alphabet
which indicates which characters it can
handle. The default alphabet is used but smaller alphabets can be
constructed.
// Demonstrate the use of interchangeable ciphers. Most of the classes // in the project will need to be complete for this code to work // properly. public class P3Demo { public static void main(String[] args){ Alphabet alphabet = Alphabet.DEFAULT; String encrypted, decrypted; Cipher c; String message = "All your base are belong to us."; c = new CaesarCipher(2,alphabet); encrypted = c.encrypt(message); decrypted = c.decrypt(encrypted); System.out.printf("Cipher: %s\n",c.toString()); System.out.printf("Message: %s\n",message); System.out.printf("Encrypted: %s\n",encrypted); System.out.printf("Decrypted: %s\n",decrypted); System.out.println(); // Cipher: Caesar Cipher (shift=2) // Message: All your base are belong to us. // Encrypted: Cnn2 qwt2dcug2ctg2dgnqpi2vq2wu? // Decrypted: All your base are belong to us. c = new CaesarCipher(17,alphabet); encrypted = c.encrypt(message); decrypted = c.decrypt(encrypted); System.out.printf("Cipher: %s\n",c.toString()); System.out.printf("Message: %s\n",message); System.out.printf("Encrypted: %s\n",encrypted); System.out.printf("Decrypted: %s\n",decrypted); System.out.println(); // Cipher: Caesar Cipher (shift=17) // Message: All your base are belong to us. // Encrypted: R22&%5!8&sr9v&r8v&sv254x&05&!9M // Decrypted: All your base are belong to us. c = new VigenereCipher("Cats",alphabet); encrypted = c.encrypt(message); decrypted = c.decrypt(encrypted); System.out.printf("Cipher: %s\n",c.toString()); System.out.printf("Message: %s\n",message); System.out.printf("Encrypted: %s\n",encrypted); System.out.printf("Decrypted: %s\n",decrypted); System.out.println(); // Cipher: Vigenere Cipher (password='Cats') // Message: All your base are belong to us. // Encrypted: C!|D $<,21(.g](,g])+n$:=2('Dw*o // Decrypted: All your base are belong to us. // Comment out this section if not implementing the MorseCipher message = "ALL YOUR BASE"; c = new MorseCipher(); encrypted = c.encrypt(message); decrypted = c.decrypt(encrypted); System.out.printf("Cipher: %s\n",c.toString()); System.out.printf("Message: %s\n",message); System.out.printf("Encrypted: %s\n",encrypted); System.out.printf("Decrypted: %s\n",decrypted); System.out.println(); // Cipher: Morse Cipher // Message: ALL YOUR BASE // Encrypted: .- .-.. .-.. -.-- --- ..- .-. -... .- ... . // Decrypted: ALL YOUR BASE } }
1.5 Suggestions
This is a larger project than our first two. It may seem a bit daunting at first due to the larger number of classes to create. Here are some suggestions of how to stay sane.
- One Class at a time
- Work on a single class until it is finished. Don't move on until you're confident the class works right. Start by adding all "method stubs" that the compiler complains are missing so that the relevant test file compiles are reports mostly errors. Then begin filling in method definitions checking frequently on your progress by running the tests. Use the test cases to guide your development and make forward progress but remember that test cases don't prove the correctness of your code.
- Read the spec and plan your efforts
- After you gain a basic understanding of the project, do some
planning before diving in. Notice the dependencies of which
classes rely on which other classes. A suggested ordering of
what to tackle is as follows.
NotInAlphabetException
Alphabet
Cipher
SymmetricCipher
CaesarCipher
VigenereCipher
MorseCipher
This ordering is mostly reflected in the order of class descriptions below.
- Understand the tests
- When you fail a tests, inspect the test case code. Trace through your code by hand drawing pictures and checking assumptions. Understanding what the test is looking for and where your code is producing different results is the key to finding logic errors. Add print statements to show you values during computation, to help you trace behavior. If you are really stuck, start exploring the debugger which allows you to stop at arbitrary lines and step forwards line by line and inspect any variable.
2 Class Alphabet
Alphabet
represents the set of characters that will be allowed in
some message. For different messages, we can choose to use different
Alphabet
objects.
The primary function of Alphabets
is to provide translation of
characters from symbols to integers and back via its indexOf(c)
and
get(i)
methods. Several ciphers will require translating a character
like 'C' into a number. The Alphabet
provides such a functionality
via its a.indexOf('C')
method which will produce the integer
associated with C
if the letter is in the alphabet. Similarly,
converting a number like 8
to an equivalent character is done via
a.get(8)
which returns a character from the alphabet.
Adhere to the abstraction barrier set up by Alphabet
and use its
methods to do character/number translations elsewhere in the project.
This is a required part of the manual inspection criteria.
2.1 Fields
private String symbols
public static final Alphabet DEFAULT = ...
. Due to the chance to mistype this, we have provided it in the project pack inAlphabet.java
.
2.2 Methods
public Alphabet(String symbols)
. The constructor initializessymbols
.public int indexOf(char c)
. Returns the index ofchar
parameterc
in stringsymbols
. For example, the index of 'b' in "abc" is1
. ThrowsNotInAlphabetException
ifchar
parameterc
is not insymbols
.public char get(int i)
. Returns thechar
at positioni
ofsymbols
. For example, thechar
at position1
of "abc" is 'b'. ThrowsNotInAlphabetException
ifint
parameteri
is not a position insymbols
. TheNotInAlphabetException
may have any character associated with as theoffender
parameter for its constructor.public int length()
. Returns the length of stringsymbols
.public String getSymbols()
. Returnssymbols
.public String toString()
. String representation. Example: ifsymbols
is "ABC", returns "Alphabet(ABC)"public boolean equals(Object other)
. Returntrue
ifother
is anotherAlphabet
and has the same characters and ordering asthis
alphabet. The below examples below should compile and produce the indicated output.Object a1 = new Alphabet("ABCabc"); Object a2 = new Alphabet("ABCabc"); Object a3 = new Alphabet("123"); Object s1 = new String("123"); Object o1 = new Object(); System.out.println( a1.equals(a2) ); // true System.out.println( a1.equals(a3) ); // false System.out.println( a1.equals(s1) ); // false System.out.println( a1.equals(o1) ); // false
2.3 Demonstration of Alphabet
The following DrJava session demonstrates the basic functionality
of the Alphabet
class. It's primary purpose is to provide
translation of characters from symbols to integers and back via its
indexOf(c)
and get(i)
methods.
Welcome to DrJava. > Alphabet a2e = new Alphabet("abcde"); > a2e.indexOf('c') 2 > a2e.indexOf('a') 0 > a2e.indexOf('z') Not in alphabet: 'z' not found in Alphabet(abcde). at Alphabet.indexOf(Alphabet.java:15) > a2e.get(3) 'd' > a2e.get(0) 'a' > a2e.get(9) Asked to get symbol @9, but length of Alphabet is 5. at Alphabet.get(Alphabet.java:22) > a2e.length() 5 > a2e.toString() "Alphabet(abcde)" > String syms = a2e.getSymbols(); > syms "abcde" > Alphabet def = Alphabet.DEFAULT; > def.length() 93 > def.indexOf('A') 0 > def.indexOf('a') 26 > def.indexOf('0') 62 > def.indexOf('?') 90 > def.get(92) '>' > def.get(32) 'g' > def.getSymbols() "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz 1234567890!@#$%^&*()_+-=[]{}\|;:'",./?<>" > def.toString() "Alphabet(ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz 1234567890!@#$%^&*()_+-=[]{}\|;:'",./?<>)"
3 Class NotInAlphabetException
Class NotInAlphabetException
inherits from class RuntimeException
.
3.1 Fields
public final String msg;
public final char offender;
public final Alphabet a;
3.2 Methods
public NotInAlphabetException(String msg, char offender, Alphabet a)
. The constructor initializes the data members.In the event that a
NotInAlphabetException
is needed for an unknown character, use this constructor with any character can be used for theoffender
parameter. This situation arises during some operations of theAlphabet
class.public NotInAlphabetException(char offender, Alphabet a)
The constructor initializes the data members. The value of the message is created by callingString.format()
to create a string whose format is illustrated by the following example:"Not in alphabet: 'a' not found in ABC."
In the event that a
NotInAlphabetException
is needed for an unknown character, do not use this constructor. Use the other constructorpublic String toString()
. Returnsmsg
.
4 Class Cipher
Class Cipher is an abstract class and all of its methods are abstract. This parent class represents any object that can encrypt and decrypt strings.
4.1 Methods
public abstract String encrypt(String s);
public abstract String decrypt(String s);
5 Class SymmetricCipher
Class SymmetricCipher is an abstract class that inherits from class Cipher. Not all of its methods are abstract.
5.1 Fields
protected Alphabet alphabet
: The alphabet that this cipher works on. Characters that are to be encrypted/decrypted that do not exist in the alphabet will cause problems. In such cases, aNotInAlphabetException
should be raised.
5.2 Methods
public SymmetricCipher(Alphabet alphabet)
. The constructor initializes the data member.public int wrapInt(int i)
. Given an index value that may be outside the range of valid indexes into the alphabet, wrap it back around to a valid index.public int rotate(int index, int shift)
. Given an index into the alphabet, rotate it aroundshift
times to the resulting valid index into alphabet.public Alphabet getAlphabet()
. Returnsalphabet
.public String encrypt(String s)
. Implement this method based upon theencrypt1
definition (below), which encrypts a single character (think of the Caesar Cipher for an understanding). ThrowsNotInAlphabetException
if any character is found that isn't in the alphabet.public String decrypt(String s)
. Implement this method based upon thedecrypt1
definition, which decrypts a single character (think of the Caesar Cipher for an understanding). ThrowsNotInAlphabetException
if any character is found that isn't in the alphabet.protected abstract char encrypt1(char c)
. Child classes must provide an implementation of this; it provides a way to encrypt a single character. Child class implementations will throwNotInAlphabetException
if any character is found that isn't in the alphabet.protected abstract char decrypt1(char c)
. Child classes must provide an implementation of this; it provides a way to decrypt a single character. Child class implementations will throwNotInAlphabetException
if any character is found that isn't in the alphabet.
6 Class CaesarCipher
You'll want to do a bit of background reading on the Caesar Cipher to get a feel for it. Wikipedia is a good place to start.
Harvard's CS50 course also has a decent video covering both the Caesar and Vigenere Cipher (next section).
The central idea is that both communicating parties possess a secret number we will refer to as the shift key. Encrypting an integer is simply a matter of adding the shift key onto the integer and wrapping around if the integer overflows (goes beyond the valid indexes). Decrypting an integer simply subtracts the shift key from the integer and wraps it around if the result is negative.
It should be apparent that the shift key is the important secret that is pertinent to the Caesar Cipher: both the sender and receiver need to know the shift key in order to communicate. Since the same key both encrypts and decrypts messages, the Caesar Cipher is a type of symmetric cipher.
You should not need to implement encrypt()
or decrypt()
as these
methods will be inherited from SymmetricCipher
.
6.1 Example
If we had a tiny alphabet as follows (only A → P), their "shift by three" values are shown below.
original | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
shifted 3 | D | E | F | G | H | I | J | K | L | M | N | O | P | A | B | C |
The key to understanding is to think of the alphabet inscribed on two concentric circles, initially so that the A's match each other, the B's match each other, and so on. We then rotate one wheel by the shift amount, showing us the per-character encryption. To decrypt these messages, you only need to shift in the opposite direction.
Consider the larger alphabet with the following index numbers associated with them.
alphabet | a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z | <space> |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
Suppose a Caesar Cipher chooses the shift key +17. The following demonstrates encryption and decryption using the Caesar Cipher.
ENCRYPTION Alphabet Size: 27 Message: catfood Shift: +17 |----------------+-----+-----+-----+-----+-----+-----+-----| | Message | c | a | t | f | o | o | d | |----------------+-----+-----+-----+-----+-----+-----+-----| | Message Ints | 2 | 0 | 19 | 5 | 14 | 14 | 3 | | Shift | +17 | +17 | +17 | +17 | +17 | +17 | +17 | |----------------+-----+-----+-----+-----+-----+-----+-----| | Intermediate | 19 | 17 | 36 | 22 | 31 | 31 | 20 | |----------------+-----+-----+-----+-----+-----+-----+-----| | Rotate? | no | no | -27 | no | -27 | -27 | no | | Encrypted Ints | 19 | 17 | 9 | 22 | 4 | 4 | 20 | |----------------+-----+-----+-----+-----+-----+-----+-----| | Encrypted | t | r | j | w | e | e | u | |----------------+-----+-----+-----+-----+-----+-----+-----| Encrypted Text: trjweeu DECRYPTION Alphabet Size: 27 Message: trjweeu Shift: +17 |----------------+-----+-----+-----+-----+-----+-----+-----| | Message | t | r | j | w | e | e | u | |----------------+-----+-----+-----+-----+-----+-----+-----| | Message Ints | 19 | 17 | 9 | 22 | 4 | 4 | 20 | | Shift | -17 | -17 | -17 | -17 | -17 | -17 | -17 | |----------------+-----+-----+-----+-----+-----+-----+-----| | Intermediate | 2 | 0 | -8 | 5 | -13 | -13 | 3 | |----------------+-----+-----+-----+-----+-----+-----+-----| | Rotate? | no | no | +27 | no | +27 | +27 | no | | Decrypted Ints | 2 | 0 | 19 | 5 | 14 | 14 | 3 | |----------------+-----+-----+-----+-----+-----+-----+-----| | Decrypted | c | a | t | f | o | o | d | |----------------+-----+-----+-----+-----+-----+-----+-----| Decrypted Text: catfood
Use the Alphabet
class to determine the which number corresponds
to each character in a message during encryption and decryption. This
will be done the most in the implementation of encrypt()
and
decrypt()
for which SymmetricCipher
is primarily responsible.
CaesarCipher
will need to implement encrypt1()/decrypt1()
that
determines the results of shifting single ints.
6.2 Fields
protected int shift
: The shift that this cipher uses to adjust characters during encryption/decryption.
6.3 Methods
public CaesarCipher(int shift, Alphabet alphabet)
. The constructor stores theshift
andalphabet
.- Note: even if
shift
is out of range of the alphabet, store it exactly as given and let other code deal with it.
- Note: even if
public CaesarCipher(int shift)
. This constructor stores theshift
and uses the defaultAlphabet
found inAlphabet.DEFAULT
.- Note: even if
shift
is out of range of the alphabet, store it exactly as given and let other code deal with it.
- Note: even if
public String encrypt(String s)
. Shifts each character by theshift
amount and returns the newly encoded string. Inherited implementation.public String decrypt(String s)
. Shifts each character in the opposite direction by theshift
amount, which will recover the original secret message. Inherited implementation.public char encrypt1(char c)
. Encrypts and returns a single character based on theshift
and thealphabet
in use. Will throwNotInAlphabetException
if any character is found that isn't in the alphabet.public char decrypt1(char c)
. Decrypts and returns a single character based on theshift
and thealphabet
in use. Will throwNotInAlphabetException
if any character is found that isn't in the alphabet.public String toString()
. Return a string representation of the cipher in the following formatCaesar Cipher (shift=2)
where the value associated with shift is taken from the internal field of the class.
7 Class VigenereCipher
As before, a little background reading can go a long way to gaining a basic understanding: Wikipedi's Description of the Vigenere Cipher.
Class VigenereCipher extends SymmetricCipher
. Although we shift
characters in a similar fashion to the Caesar Cipher, it is more
sophisticated: instead of a static shift value that is used for every
single letter, a secret password is chosen (made entirely of symbols
in our alphabet). To determine the amount of shift for each character
of the secret message, the password is concatenated to itself until
it's as long as the secret message, and we compare indexes in the
secret message with the same spot in our concatenated passphrase. If
the password's character is e.g. the 11th symbol in our alphabet, then
that corresponding character in our secret message is shifted by 11
(in the same style as described previously for the CaesarCipher
).
7.1 Example
Suppose we want to encrypt the secret message "furball", and we have
previously established with our co-conspirators that "cat" is the
passphrase. We'll use an Alphabet
with only lowercase letters and a
space.
alphabet | a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z | <space> |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
Per character, we figure out which letter of the password controls the amount of shift, perform the shift, and result in the next character of the output.
Alphabet Size: 27 Message: furball Password: cat |----------------+----+----+-----+----+----+-----+----| | Message | f | u | r | b | a | l | l | | Password | c | a | t | c | a | t | c | |----------------+----+----+-----+----+----+-----+----| | Message Ints | 5 | 20 | 17 | 1 | 0 | 11 | 11 | | Password Ints | +2 | +0 | +19 | +2 | +0 | +19 | +2 | |----------------+----+----+-----+----+----+-----+----| | Intermediate | 7 | 20 | 36 | 3 | 0 | 30 | 13 | |----------------+----+----+-----+----+----+-----+----| | Rotate? | no | no | -27 | no | no | -27 | no | | Encrypted Ints | 7 | 20 | 9 | 3 | 0 | 3 | 13 | |----------------+----+----+-----+----+----+-----+----| | Encrypted | h | u | j | d | a | d | n | |----------------+----+----+-----+----+----+-----+----| Encrypted Text: hujdadn
Note that the two l's of "furball" are shifted by different letters in the password and thus have different encodings.
Decryption reverses the process by subtracting off the indices associated with the password characters.
Alphabet Size: 27 Message: hujdadn Password: cat |----------------+----+----+-----+----+----+-----+----| | Message | h | u | j | d | a | d | n | | Password | c | a | t | c | a | t | c | |----------------+----+----+-----+----+----+-----+----| | Message Ints | 7 | 20 | 9 | 3 | 0 | 3 | 13 | | Password Ints | -2 | -0 | -19 | -2 | -0 | -19 | -2 | |----------------+----+----+-----+----+----+-----+----| | Intermediate | 5 | 20 | -10 | 3 | 0 | -16 | 11 | |----------------+----+----+-----+----+----+-----+----| | Rotate? | no | no | +27 | no | no | +27 | no | | Decrypted Ints | 5 | 20 | 17 | 1 | 0 | 11 | 11 | |----------------+----+----+-----+----+----+-----+----| | Decrypted | f | u | r | b | a | l | l | |----------------+----+----+-----+----+----+-----+----| Decrypted Text: furball
7.2 Encryption/Decryption
This is a bit trickier here, as Vigenere ciphers are stateful during
encryption: they need to track which password integer to use for each
position of the message. It is tempting to override
encrypt()/decrypt()
and write your own loops for this. Resist this
urge and take the following approach:
- Use an internal field associated with each instance of the class to track the position in the password array
- Override
encrypt()
to set this field to0
, then call the parent method's version ofencrypt()
- In
encrypt1()
use the field to determine how much shifting is to be done. Then update the internal password position by incrementing it - Take a similar approach for decryption: override
decrypt()
to set the password position to 0 and use that position duringdecrypt1()
.
Full credit solutions will have very short methods with no loops for
encrypt
and decrypt
.
7.3 Fields
protected String password
The password, used to generate shift values.protected int passwordPos;
Records the current location in a String that is being crypted. This is the 'state' that is used in successiveencrypt1
calls byencrypt
(similarly for decrypting).
7.4 Methods
public VigenereCipher(String password,Alphabet alphabet)
The constructor initializes the data members. This includes the data member in the parent classSymmetricCipher
.public VigenereCipher(String password)
. The constructor initializes the data members. This includes the data member in the parent classSymmetricCipher
, which is initialized to theDEFAULT
Alphabet.public String getPassword()
. Returnspassword
.protected char encrypt1(char c)
. Relies uponpassword
andpasswordPos
to encrypt a singlechar
. Must incrementpasswordPos
. Will throwNotInAlphabetException
if any character is found that isn't in the alphabet.protected char decrypt1(char c)
. Relies uponpassword
andpasswordPos
to decrypt a singlechar
. Must incrementpasswordPos
. Will throwNotInAlphabetException
if any character is found that isn't in the alphabet.public String encrypt(String s)
. We can't wholly use the inherited version, because we need to know at what position in the string we're encrypting. InitializespasswordPos
to0
, and then invokes the parent class's version ofencrypt
(which in turn uses this class'sencrypt1
definition). When characters not in the alphabet are encountered, aNotInAlphabetException
is thrown.public String decrypt(String s)
. We can't wholly use the inherited version, because we need to know at what position in the string we're decrypting. InitializespasswordPos
to0
, and then invokes the parent class's version ofdecrypt
(which in turn uses this class'sdecrypt1
definition). When characters not in the alphabet are encountered, aNotInAlphabetException
is thrown.public String toString()
. Return a string representation of the cipher in the following formatVigenere Cipher (password='Cats')
where the value associated with password is taken from the internal field of the class.
8 Class MorseCipher
Morse code is actually a cipher with encryption and decryption actions. Unusually, the intent is not to hide information, merely to represent it in a form more suited for transit. By representing letters with dashes and dots, it is easy to transmit data through lossy environments such as transmission across wires.
The project pack contains a start for MorseCipher.java
which defines
the alphabet we will use along with the Morse codes associated with
the those letters . You will implement the same encrypt()
and
decrypt()
methods as before. Other than the series of dots and
dashes representing each letter, we see three spaces separating each
letter of a word and seven spaces separating words. There are no
spaces at the end of the message. We must create exactly the correct
amount of spacing in our encrypted messages, and we must correctly
extract the letters and spaces when decrypting.
8.1 Fields
public static final String letters...;
public static final String[] codes...;
See the shared MorseCipher.java
file in p3pack.zip for the entire
representation. It has been shared to avoid copying errors as you
implement your project.
8.2 Methods
public MorseCipher()
. The constructor usesletters
to fill in itsalphabet
field.public String encrypt(String plainText)
. Converts a string of letters, digits, and spaces to the corresponding morse code representation. All lowercase letters are implicitly converted to uppercase during encryption.public String decrypt(String cryptText)
. Converts a string of morse code representation back to a string of uppercase letters, digits, and spaces. Any letters that were originally lowercase will only appear as uppercase.
8.3 Strategies for Implementation of Morse Methods
Unlike the other ciphers, the MorseCipher
does not involve any
secret information. Instead, it is more properly thought of as
encoding/decoding based on lookup tables of character equivalences.
In the provided MorseCipher.java
file, two tables appear
- A
String
calledletters
which gives the valid characters in theMorseCode
. This table is a good candidate for anAlphabet
object to ease looking up characters. - An array of
Strings
calledcodes
which give the series of dashes and dots equivalent to each character in the allowed set of characters.
The central strategy while encrypting is simple: for each character in the message, determine the index of that character and output the equivalent dash-dot string. Each dash-dot is separated by 3 spaces and each word is separated by 7 spaces
Message: "ALL YOUR BASE" A: ".-" " " L: ".-.." " " L: ".-.." " " _: " " Y: "-.--" " " O: "---" " " U: "..-" " " R: ".-." " " _: " " B: "-..." " " A: ".-" " " S: "..." " " E: "." Encrypted: .- .-.. .-.. -.-- --- ..- .-. -... .- ... .
Decrypting is a matter of reading dots/dashes until a space is encountered. The sequence of dashes/dots is looked up in the table of equivalences and the equivalent character is added to the growing message. After this, count how many spaces there are prior to the next dot/dash: 3 spaces means the next character is part of the same word, 7 means the next character starts a new word and a space should be put into the growing decoded message.
Encrypted: .- .-.. .-.. -.-- --- ..- .-. -... .- ... . position: ^ code: "" mesg: "" Encrypted: .- .-.. .-.. -.-- --- ..- .-. -... .- ... . position: ^ code: ".-" -> A mesg: "A" Encrypted: .- .-.. .-.. -.-- --- ..- .-. -... .- ... . position: ^ code: "" -> 3 spaces, same word mesg: "A" Encrypted: .- .-.. .-.. -.-- --- ..- .-. -... .- ... . position: ^ code: ".-.." -> L mesg: "AL" Encrypted: .- .-.. .-.. -.-- --- ..- .-. -... .- ... . position: ^ code: "" -> 3 spaces, same word mesg: "AL" Encrypted: .- .-.. .-.. -.-- --- ..- .-. -... .- ... . position: ^ code: ".-.." -> L mesg: "ALL" Encrypted: .- .-.. .-.. -.-- --- ..- .-. -... .- ... . position: ^ code: "" -> 7 spaces, new word mesg: "ALL " Encrypted: .- .-.. .-.. -.-- --- ..- .-. -... .- ... . position: ^ code: "-.--" -> Y mesg: "ALL Y" ...
The code to accomplish decryption is more intricate than other processes we have previously dealt with. It may be helpful to establish private helper methods to perform tasks such as
- Scan through spaces in a string until the next non-space character and return the number of spaces.
- Scan all non-space characters from a given start position and return a string of those non-space characters.
While not strictly necessary, such little methods can simplify your the logic of your decryption routine to make it "prettier". Make sure to document any helper methods you use.
9 Honors Section Problem: None
There is no honors problem for this assignment.
10 (50%) Automated Tests grading
- We are providing a battery of unit tests in the various files, all
of which may be run via
P3Tests.java
which will be used by graders to evaluate your code. You may want to begin studying how these tests work and experiment with your own. (See the specification for project one for instructions on running JUnit tests.) - Tests may be expanded as the HW deadline approaches.
- It is your responsibility to get and use the freshest set of tests available.
- Tests will be provided in source form so that you will know what tests are doing and where you are failing.
- Code that does not compile and run tests according to the specified command line invocation may lose all automated testing credit. Graders will usually try to fix small compilation errors such as bad directory structures or improper use of packages. Such corrections typically result in a loss of 5-10% credit on automated testing. However, if more than a small amount of error to fix problems seems required, no credit will be given.
11 (50%) Manual Inspection Criteria
The following features will be evaluated during manual inspection of your code and analysis documents. It is worth a total of 50% of the overall grade for this project.
11.1 (5%) Adherence to Alphabet Abstraction Barriers grading
The purpose of defining classes such as Alphabet
is to simplify the
process of translating characters to integers and back again. Methods
like Alphabet.get()
and Alphabet.indexOf()
should be employed to
perform such translations rather than attempting to directly
manipulate the fields of that class. Translation primarily occurs in
encrypt1()/decrypt1()
where the methods of Alphabet
should be
employed.
11.2 (20%) Proper Use of Inheritance for Code Re-Use grading
- SymmetricCipher (5%)
- Implement
encrypt
/decrypt
. Use calls toencrypt1
/decrypt1
which will be defined in child classes. - Caesar Encryption (5%)
- Override
encrypt1()/decrytp1()
and correctly leverage the inherited definition ofencrypt()/decrypt()
. - Vigenere Encryption (5%)
- Override
encrypt1()/decrytp1()
and correctly leverage the inherited definition ofencrypt()/decrypt()
. - Avoid Variable Shadowing (5%)
- If a parent class already has a field, there is no need for child class to have a similar field. Do not duplicate fields.
11.3 (5%) Inheritance Correctness and Safety grading
- Constructors (3%)
- Child classes appropriately use parent class constructors to create objects.
-
@Override
tags everywhere they can be (2%) - Every time we override a method, we should label it with
@Override
so that the compiler can ensure we are actually overriding a method as expected. There is an exact list of places this must occur, and it is up to you to discern where.For a short discussion of the
@Override
tag, have a look at this tutorial site. It is useful as it enables the compiler to flag errors when a method is meant to override a parent but due to typing errors it is not.
11.4 (5%) MorseCipher Elegance
The encrypt()
method of MorseCipher
should be relatively short
(20ish lines) and be fairly easy to follow. Comment it a little if
you see the need to explain your reasoning.
The decrypt()
method is comparatively gnarly. It involves some
significant looping, conditionals, and cleverness. Make your
code as clean as possible and include some comments to describe your
approach in each section. Line by line descriptions are not needed
but comments like "find the character that matches the code", "check
whether code is finished" and "advance past spaces" help a reader to
discern what each portion is doing.
11.5 (10%) Coding Style and Readability grading
There are a few more points for coding style and readability this time. You are creating many more files from scratch this time, and we want to see you creating well organized, readable code. Comment meaningfully and adequately. We won't deduct points for too many comments and will give feedback if comments are too abundant and not meaningful.
- Class Documentation (2%)
- Each class has an initial comment indicating its intended purpose, how it works, and how it relates to or uses other classes in the project.
- Field Documentation (2%)
- Each field of a class is documented to indicate what piece of data is tracked and how it will be used. Both public and private fields are documented.
- Method documentation (3%)
- Each method has a short description indicating its intended purpose and how it gets its job done.
- Code Cleanliness (3%)
- Indent and {bracket} code uniformly throughout the program to improve readability.
11.6 (5%) Correct Project Setup grading
Correctly setting up the directory structure for each project greatly eases the task of grading lots of projects. Graders get cranky when strange directory structures or missing files are around and you do not want cranky graders. The following will be checked for setup on this project.
- The Setup instructions were followed closely
- The Project Directory is named according to the specification
- There is an Identity Text File present with the required information in it
- Code can be compiled and tests run from the command line
12 Project Submission
12.1 Provided Files
Download the zip file p3pack.zip which contains necessary files for this assignment. Unzip this file which will create a folder which you should as you project directory as described below.
12.2 Project Directory
Rename the pXpack
directory (folder) according to the project
directory convention: NetID-LabSecNum-pNum
where
- masonID is your GMU Net ID, the first part of your GMU email address.
- labSecNum is your lab section number, such as
201
,202
,2H1
, etc. - pNum represents the project number, such as
p1
.
Examples
msnyde14-2H1-pX
for Mark Snyder in lab section 2H1ckauffm2-205-pX
for Chris Kauffman in lab section 205
All work you do should be saved in your project directory for eventual zipping and submission.
12.3 Identity Text File
Create a text file in your project directory called ID.txt
which
has identifying information in it. A sample ID.txt
looks like this:
Mark Snyder msnyde14 G0071234 Lecture: 002 Lab: 205
It contains a full name, mason ID, G#, lecture section, and lab section in it.
Failure to submit your work in an appropriately named directory and
with the ID.txt
file in it with correct information will result in
the loss of 5%.
12.4 Submission
Create a zip file of your homework directory and submit it to the course Blackboard page. Do not e-mail the professor or TAs your code.
Create a .zip
file of your directory as you will learn in the first
programming lab.
To submit your zip to Blackboard:
- Click on the Projects Link
- Click on Project 2
- Scroll down to "Attach a File"
- Click "Browse My Computer"
- Select your Zip file
You may submit as many times as you like; only the final valid submission will be graded. If you turn in late work within 48 hours, it will consume late tokens. If you turn in late work beyond 48 hours, that particular submission is invalid and will be ignored.
You should always verify that your submission is successful. Go back
to the assignment and download your last submission; is there actually
a file there? Can you unzip that file, and see .java
files in there
as you'd expect? Can you run those files? It turns out you can do
quite a bit to ensure you've properly submitted your work. Don't get a
zero just because of a failure to actually turn in the right files! It
is your responsibility to correctly turn in your work.