Rot13 (Java)

From LiteratePrograms
Jump to: navigation, search
Other implementations: Haskell | Java | Python | Scheme | Sed

This program is under development.
Please help to debug it. When debugging
is complete, remove the {{develop}} tag.


Here, we present a Java program that performs Rot13 encoding and/or decoding for text passed on the command line or via a filter.

The heart of this cypher is a simple character substitution that moves the letters greater than or equal to 'n' 13 places towards the front of the alphabet and letters less than or equal to 'm' 13 places towards the back. This is done separately for the lower case and upper case letters. Punctuation and non-English characters are left unchanged.

For example, if the plain text was:

Now is the Winter of our Discontent,
Made glorious Summer by this Son of Yorke:

The encoded string would be:

Abj vf gur Jvagre bs bhe Qvfpbagrag,
Znqr tybevbhf Fhzzre ol guvf Fba bs Lbexr:

If the Rot13 algorithm is reapplied to the encoded string, the original plain text is returned.

We also take the opportunity to introduce the reader to the JUnit test framework and the Apache Ant build tool, both of which are a standard part of the professional Java development toolbox.

Contents

[edit] The Algorithm

In the following, c is the character currently being encoded.

<<encode c>>=
if      (c >= 'a' && c <= 'm') c += 13;
else if (c >= 'n' && c <= 'z') c -= 13;
else if (c >= 'A' && c <= 'M') c += 13;
else if (c >= 'N' && c <= 'Z') c -= 13;

A function to use this method would simply take a string, extract the characters, encode them using the character encoding scheme, and rebuild a string from the encoded text, as follows:

<<encoder>>=
/**
 * Encode plain text using the Rot13 algorithm.
 * @param plainText the plain text message.
 * @returns the plain text message encoded using the Rot13 algorithm.
 */
public static String encode(String plainText) {
  // deal with the case that method is called with null argument
  String encodedMessage = "";
  if (plainText == null) return plainText;
  for (int i = 0; i < plainText.length(); i++) {
    char c = plainText.charAt(i);
    encode c
    encodedMessage += c;
  }
  return encodedMessage;
}

[edit] Testing the Algorithm

For the test we shall use part of the opening soliloquy quote from Act I Scene I of William Shakespeare's Richard III.

<<declare test strings>>=
private String plainText;
<<define test strings>>=
plainText = "Now is the Winter of our Discontent,\n" +
            "Made glorious Summer by this Son of Yorke:\n" +
            "And all the clouds that lowr'd vpon our house\n" +
            "In the deepe bosome of the Ocean buried.\n" +
            "Now are our browes bound with Victorious Wreathes,\n" +
            "Our bruised armes hung vp for Monuments;\n" +
            "Our sterne Alarums chang'd to merry Meetings;\n" +
            "Our dreadfull Marches, to delightfull Measures.\n" +
            "Grim-visag'd Warre, hath smooth'd his wrinkled Front:\n" +
            "And now, in stead of mounting Barbed Steeds,\n" +
            "To fright the Soules of fearfull Aduersaries,\n" +
            "He capers nimbly in a Ladies Chamber,\n" +
            "To the lasciuious pleasing of a Lute.";

To test the algorithm, it is sufficient to verify that the cypher text produced by Rot13.encode is an Rot13 translation of the plain text. This was done by feeding the plain text into an online Rot13 translator (http://www.rot13.com/index.php was used) and assigning the result to the string cypherText.

<<declare test strings>>=
private String cypherText;
<<define test strings>>=
cypherText = "Abj vf gur Jvagre bs bhe Qvfpbagrag,\n" + 
             "Znqr tybevbhf Fhzzre ol guvf Fba bs Lbexr:\n" + 
             "Naq nyy gur pybhqf gung ybje'q icba bhe ubhfr\n" + 
             "Va gur qrrcr obfbzr bs gur Bprna ohevrq.\n" + 
             "Abj ner bhe oebjrf obhaq jvgu Ivpgbevbhf Jerngurf,\n" + 
             "Bhe oehvfrq nezrf uhat ic sbe Zbahzragf;\n" + 
             "Bhe fgrear Nynehzf punat'q gb zreel Zrrgvatf;\n" + 
             "Bhe qernqshyy Znepurf, gb qryvtugshyy Zrnfherf.\n" + 
             "Tevz-ivfnt'q Jneer, ungu fzbbgu'q uvf jevaxyrq Sebag:\n" + 
             "Naq abj, va fgrnq bs zbhagvat Oneorq Fgrrqf,\n" + 
             "Gb sevtug gur Fbhyrf bs srneshyy Nqhrefnevrf,\n" + 
             "Ur pncref avzoyl va n Ynqvrf Punzore,\n" + 
             "Gb gur ynfpvhvbhf cyrnfvat bs n Yhgr.";

For the actual test, we shall use the JUnit test framework, the de facto standard for automated unit testing in Java. For the first test, we verify that the cypher text produced by Rot13.encode() matches that which we produced independently:

<<test encoder>>=
public void testEncode() {
  assertEquals(cypherText, Rot13.encode(plainText));
}

The test is self explanatory. When run, the JUnit framework will automatically execute the testEncode method (and all other methods starting with the word test). The test assertion assertEquals fails by throwing an exception if cypherText does not match the string produced by the method under test Rot13.encode(plainText). Such failures are marked as test failures by the JUnit framework. If the test is successful, JUnit silently moves on to the next test. Thus in JUnit testing silence is golden!

The Rot13 algorithm is symmetric, thus if we encode the encoded text, we retrieve the original message. Our second test will verify that this happens.

<<test decoder>>=
public void testDecode() {
    assertEquals(plainText, Rot13.encode(cypherText));
}

To ensure that the test framework is properly set up, we put the code to define the test strings into JUnit's setUp method. This method will be called before each test is executed and ensures that the test data is consistent.

<<set up test framework>>=
public void setUp() {
    define test strings
}

The test program itself is simply a standard Java class which imports the JUnit framework. The test method inherits all of its test behaviour from JUnit's TestCase class.

<<Rot13Test.java>>=
package org.literateprograms.cyphers;

import junit.framework.TestCase;

public class Rot13Test extends TestCase {

    declare test strings
	
    set up test framework

    test encoder

    test decoder

}

To compile this, you will need to include the JUnit framework in your classpath. The easiest way to do this is to put a copy of junit.jar (which comes with the JUnit distribution) into your source directory. Now create a directory for the generated class files:

 $ mkdir classes

and compile the Rot13 and Rot13 class files:

 $ javac -cp '.;classes;junit.jar' -d classes Rot13.java Rot13Test.java

Finally run the tests using the JUnit test runner (command for Junit 3.8.1 described):

 $ java -cp '.;classes;junit.jar' junit.textui.TestRunner org.literateprograms.cyphers.Rot13Test
 ..
 Time: 0.016
 
 OK (2 tests)

As you can see, JUnit runs the tests and if it reports a simple OK message, all is well.

[edit] User's guide to the program

We would like the program to behave like a standard filter program (albeit with Java conventions). Assuming that the class file for Rot13 is packaged up in a JAR file called cyphers.jar, a standard way to use the program would be:

 java -cp cyphers.jar uk.literateprograms.cyphers.Rot13 < plaintext.txt

In which the text contained in the file plaintext.txt would be passed to the program through standard input, translated using the Rot13 encoding and the result would appear on the console (standard output). This form of program can also act as a filter as in:

 cat plaintext.txt | java -cp cyphers.jar uk.literateprograms.cyphers.Rot13

and can make use of file redirection as in:

 java -cp cyphers.jar uk.literateprograms.cyphers.Rot13 < plaintext.txt > cypher.txt

As an extra feature, we would like to use a command line argument to encode a string provided at the command line. For this we would like the program to give:

 $ java -cp cyphers.jar uk.literateprograms.cyphers.Rot13 -e "Now is the Winter of our Discontent"
 Abj vf gur Jvagre bs bhe Qvfpbagrag

If we call the program with unexpected arguments, or using the -h argument, we'd like it to produce a simple usage message:

 $ java -cp cyphers.jar uk.literateprograms.cyphers.Rot13 -h 
 Usage: java uk.literateprograms.cyphers.Rot13 [-h] [-e "text to be encoded"] [< plaintext] [> cyphertext]
 where arguments are:
 -e encode text on the command line
 -h print this message


[edit] The Command-Line Interface (CLI) Program

This type of program we wish to produce is typical of the programs that are featured in classic command line interfaces such as those provided in Unix and MS-DOS. The development of a Java version is a useful introduction to simple I/O and the arguments array in the Java main method. All of the action is contained in the main method:

<<main method>>=
public static void main(String [] args)
throws IOException {

   String input = ""; // data to be encoded

   check and process arguments

   open input stream if required

   encode data
 
   exit

}

Note we declare that main throws IOException because we do not want to deal with I/O errors.

[edit] Processing the Command Line Arguments

To set up the program for action, we need to interpret and process the command-line arguments. These will be passed as Strings (one string per word) in the String array args. We expect either one, two or three arguments. Anything else is an error and we print an error message, the usage message and exit the program. By convention, the error and usage message are printed on standard error (Java System.err) and the program exit is actioned by calling System.exit(1), which by convention this means abnormal termination.

<<check and process arguments>>=
if (args.length <= 3) {
  process arguments
}
else {
  System.err.println("Error: Too many arguments.\n" + usage());
  System.exit(1);
}

The usage message is simply the message described in the user guide returned from usage() as a string:

<<define usage method>>=
private static String usage() {
  return "Usage: java -cp cyphers.jar uk.literateprograms.cyphers.Rot13 [-h] [-e \"text to be encoded\"] [< plaintext] [> cyphertext]\n" +
  "where arguments are:\n" +
  "-e encode text on the command line\n" +
  "-h print this message";
} 


The arguments are processed as follows:

  1. If the argument is -e the following argument is taken to be the text to be encoded or decoded. Because the arguments are passed as one word per argument element, a long text will have to be passed to the program in quotes as in java -cp cyphers.jar org.literateprograms.cyphers.Rot13 -e "A Very Short Message".
  2. If the argument is -h the usage message is printed and the program exits by executing System.exit(0), which by convention means OK.
  3. Any other arguments are rejected and the program exits with abnormal exit status.

Here is the implementation of this algorithm:

<<process arguments>>=
int argNumber = 0;
while (argNumber < args.length) {
  if ("-e".equals(args[argNumber])) {
    argNumber ++;
    input = args[argNumber];
    argNumber ++;
  }
  else if ("-h".equals(args[argNumber])) {
     System.err.println(usage());
     System.exit(0);
  }
  else {
     System.err.println("Error: unexpected argument " + args[argNumber]);
     System.err.println(usage());
     System.exit(1);
  }
}

Please note that if the -e argument has been accepted, the text to be encoded will be in the String input. If not, this string will be the empty string.

[edit] Read Data from the Standard Input Stream

If the string to be encoded was not passed to the program using the -e argument, the String input will still be empty. In this case, e assume that we wnat to take the plain text from standard input. The test for this is:

<<open input stream if required>>=
if (input.length() == 0) {
  open standard input and extract data into input string
}

Setting up a data stream that can textual read from standard input is rather more complex in Java than in most other comparable languages. The complexity is accounted for by the supposition that the data input could come from a range of sources, including files, URLs, various compressed archives, JAR files and encrypted data channels, etc. Still, it remains something of a mystery why the most common case, read from a keyboard (a channel known as standard input), could not have been made as simple as the case of writing to the console (standard output). The latter is achieved by a simple call to System.out.println(String). The former requires rather more set up: we have to wrap the DataStream System.in in an InputStreamReader and then wrap the result in a BufferedReader.

<<open standard input and extract data into input string>>=
BufferedReader in = new BufferedReader(
   new InputStreamReader(System.in));

Once we have created the buffered reader, we can read the input using the readLine. This will return null when the end-of-file (EOF) is reached, thus we can read the lines in the body of a while statement, and concatenate each line with the input string.

<<open standard input and extract data into input string>>=
String s;
while ((s = in.readLine()) != null) {
  input += s + "\n";
}

Note that the realLine() does not return the end-of-line character, so we add it back when we construct the string.

[edit] Encode the Data and Write the Results to the Standard Output Stream

<<encode data>>=
System.out.println(encode(input));

[edit] Exit the Program

To exit the program, we simply call System.exit(0) which signals success.

<<exit>>=
System.exit(0);


[edit] The Final Program

Here is the final program.

<<Rot13.java>>=
package org.literateprograms.cyphers;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;

public class Rot13 {

   encoder

   main method

   define usage method
}

We compile the program in this way:

$ javac -cp '.;classes' -d classes Rot13.java

For convenience in later deployment of the program, we package the compiled classes into up into a JAR file. On Unix-like operating systems we do this like this:

$ cd classes
$ jar -cf ../cyphers.jar * 
$ cd ..

We can now run the program like this:

$ java -cp cyphers.jar org.literateprograms.cyphers.Rot13 -h
Usage: java -cp cyphers.jar uk.literateprograms.cyphers.Rot13 [-h] [-e "text to be encoded"] [< plaintext] [> cyphertext]
where arguments are:
-e encode text on the command line
-h print this message
$ chrisjobling$ java -cp cyphers.jar org.literateprograms.cyphers.Rot13 -e "The general is going to advance. Send reinforcements"
Gur trareny vf tbvat gb nqinapr. Fraq ervasbeprzragf

For convenience in testing here is our test data as a text file:

<<plaintext.txt>>=
Now is the Winter of our Discontent,
Made glorious Summer by this Son of Yorke:
And all the clouds that lowr'd vpon our house
In the deepe bosome of the Ocean buried.
Now are our browes bound with Victorious Wreathes,
Our bruised armes hung vp for Monuments;
Our sterne Alarums chang'd to merry Meetings;
Our dreadfull Marches, to delightfull Measures.
Grim-visag'd Warre, hath smooth'd his wrinkled Front:
And now, in stead of mounting Barbed Steeds,
To fright the Soules of fearfull Aduersaries,
He capers nimbly in a Ladies Chamber,
To the lasciuious pleasing of a Lute.

Using the program in its filter form produces the output that we expect:

 $ java -cp cyphers.jar org.literateprograms.cyphers.Rot13 < plaintext.txt
 Abj vf gur Jvagre bs bhe Qvfpbagrag,
 Znqr tybevbhf Fhzzre ol guvf Fba bs Lbexr:
 Naq nyy gur pybhqf gung ybje'q icba bhe ubhfr
 Va gur qrrcr obfbzr bs gur Bprna ohevrq.
 Abj ner bhe oebjrf obhaq jvgu Ivpgbevbhf Jerngurf,
 Bhe oehvfrq nezrf uhat ic sbe Zbahzragf;
 Bhe fgrear Nynehzf punat'q gb zreel Zrrgvatf;
 Bhe qernqshyy Znepurf, gb qryvtugshyy Zrnfherf.
 Tevz-ivfnt'q Jneer, ungu fzbbgu'q uvf jevaxyrq Sebag:
 Naq abj, va fgrnq bs zbhagvat Oneorq Fgrrqf,
 Gb sevtug gur Fbhyrf bs srneshyy Nqhrefnevrf,
 Ur pncref avzoyl va n Ynqvrf Punzore,
 Gb gur ynfpvhvbhf cyrnfvat bs n Yhgr.

And the final proof is:

 $ java -cp cyphers.jar org.literateprograms.cyphers.Rot13 < plaintext.txt | java -cp cyphers.jar org.literateprograms.cyphers.Rot13
 Now is the Winter of our Discontent,
 Made glorious Summer by this Son of Yorke:
 And all the clouds that lowr'd vpon our house
 In the deepe bosome of the Ocean buried.
 Now are our browes bound with Victorious Wreathes,
 Our bruised armes hung vp for Monuments;
 Our sterne Alarums chang'd to merry Meetings;
 Our dreadfull Marches, to delightfull Measures.
 Grim-visag'd Warre, hath smooth'd his wrinkled Front:
 And now, in stead of mounting Barbed Steeds,
 To fright the Soules of fearfull Aduersaries,
 He capers nimbly in a Ladies Chamber,
 To the lasciuious pleasing of a Lute.

If we want to use the program in production, we simply need to add cyphers.jar to the Java classpath.

[edit] Building and Testing the Program with Ant

Along with the JUnit framework, used and illustrated in this article for unit testing, another standard tool in the professional Java developer's toolbox is the Apache Ant build tool. To conclude this article, we shall demonstrate how Ant can be used to control the whole build process from compiling the Java source and test code, to running the JUnit tests all the way to packaging the program up as a jar file for deployment. We shall create and document a typical build file for this simple Java project which can also serve as a template for further projects. We shall conclude the article by demonstrating its use in building and running the Rot13 program. We will not document the installation of Ant nor the set up required to incorporate JUnit into an Ant build system. We assume that the interested reader will consult the documentation for details of how to do this.

Here is the Ant build file:

<<build.xml>>=

[edit] References

  • Apache Ant Build Tool.
  • JUnit Unit Testing Framework for Java.
  • William Shakespeare, Richard III, Gutenberg Text Version.
  • Rot13, Wikipedia Article.</codeblock>
Download code
hijacker
hijacker
hijacker
hijacker