FileDocCategorySizeDatePackage
Rot13Charset.javaAPI DocExample8264Mon May 20 00:24:30 BST 2002com.ronsoft.books.nio.charset

Rot13Charset.java

package com.ronsoft.books.nio.charset;

import java.nio.CharBuffer;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.util.Map;
import java.util.Iterator;
import java.io.Writer;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.OutputStreamWriter;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.FileReader;

/**
 * A Charset implementation which performs Rot13 encoding.  Rot-13 encoding
 * is a simple text obfuscation algorithm which shifts alphabetical characters
 * by 13 so that 'a' becomes 'n', 'o' becomes 'b', etc.  This algorithm
 * was popularized by the Usenet discussion forums many years ago to mask
 * naughty words, hide answers to questions, and so on.  The Rot13 algorithm
 * is symmetrical, applying it to text that has been scrambled by Rot13 will
 * give you the original unscrambled text.
 *
 * Applying this Charset encoding to an output stream will cause everything
 * you write to that stream to be Rot13 scrambled as it's written out.  And
 * appying it to an input stream causes data read to be Rot13 descrambled
 * as it's read.
 *
 * @author Ron Hitchens (ron@ronsoft.com)
 * @version $Id: Rot13Charset.java,v 1.9 2002/05/20 07:24:31 ron Exp $
 * Created: January, 2002
 */
public class Rot13Charset extends Charset
{
	// The name of the base charset encoding we delegate to.
	private static final String BASE_CHARSET_NAME = "UTF-8";

	// Handle to the real charset we'll use for transcoding between
	// characters and bytes.  Doing this allows applying the Rot13
	// algorithm to multi-byte charset encodings.  But only the
	// ASCII alpha chars will be rotated, no matter the base encoding.
	Charset baseCharset;

	/**
	 * Constructor for the Rot13 charset.  Call the superclass
	 * constructor to pass along the name(s) we'll be known by.
	 * Then save a reference to the delegate Charset.
	 */
	protected Rot13Charset (String canonical, String [] aliases)
	{
		super (canonical, aliases);

		// Save the base charset we're delegating to.
		baseCharset = Charset.forName (BASE_CHARSET_NAME);
	}

	// ----------------------------------------------------------

	/**
	 * Called by users of this Charset to obtain an encoder.
	 * This implementation instantiates an instance of a private class
	 * (defined below) and passes it an encoder from the base Charset.
	 */
	public CharsetEncoder newEncoder()
	{
		return new Rot13Encoder (this, baseCharset.newEncoder());
	}

	/**
	 * Called by users of this Charset to obtain a decoder.
	 * This implementation instantiates an instance of a private class
	 * (defined below) and passes it a decoder from the base Charset.
	 */
	public CharsetDecoder newDecoder()
	{
		return new Rot13Decoder (this, baseCharset.newDecoder());
	}

	/**
	 * This method must be implemented by concrete Charsets.  We always
	 * say no, which is safe.
	 */
	public boolean contains (Charset cs)
	{
		return (false);
	}

	/**
	 * Common routine to rotate all the ASCII alpha chars in the given
	 * CharBuffer by 13.  Note that this code explicitly compares for
	 * upper and lower case ASCII chars rather than using the methods
	 * Character.isLowerCase and Character.isUpperCase.  This is because
	 * the rotate-by-13 scheme only works properly for the alphabetic
	 * characters of the ASCII charset and those methods can return
	 * true for non-ASCII Unicode chars.
	 */
	private void rot13 (CharBuffer cb)
	{
		for (int pos = cb.position(); pos < cb.limit(); pos++) {
			char c = cb.get (pos);
			char a = '\u0000';

			// Is it lower case alpha?
			if ((c >= 'a') && (c <= 'z')) {
				a = 'a';
			}

			// Is it upper case alpha?
			if ((c >= 'A') && (c <= 'Z')) {
				a = 'A';
			}

			// If either, roll it by 13
			if (a != '\u0000') {
				c = (char)((((c - a) + 13) % 26) + a);
				cb.put (pos, c);
			}
		}
	}

	// --------------------------------------------------------

	/**
	 * The encoder implementation for the Rot13 Charset.
	 * This class, and the matching decoder class below, should also
	 * override the "impl" methods, such as implOnMalformedInput() and
	 * make passthrough calls to the baseEncoder object.  That is left
	 * as an exercise for the hacker.
	 */
	private class Rot13Encoder extends CharsetEncoder
	{
		private CharsetEncoder baseEncoder;

		/**
		 * Constructor, call the superclass constructor with the
		 * Charset object and the encodings sizes from the
		 * delegate encoder.
		 */
		Rot13Encoder (Charset cs, CharsetEncoder baseEncoder)
		{
			super (cs, baseEncoder.averageBytesPerChar(),
				baseEncoder.maxBytesPerChar());

			this.baseEncoder = baseEncoder;
		}

		/**
		 * Implementation of the encoding loop.  First, we apply
		 * the Rot13 scrambling algorithm to the CharBuffer, then
		 * reset the encoder for the base Charset and call it's 
		 * encode() method to do the actual encoding.  This may not
		 * work properly for non-Latin charsets.  The CharBuffer
		 * passed in may be read-only or re-used by the caller for
		 * other purposes so we duplicate it and apply the Rot13
		 * encoding to the copy.  We DO want to advance the position
		 * of the input buffer to reflect the chars consumed.
		 */
		protected CoderResult encodeLoop (CharBuffer cb, ByteBuffer bb)
		{
			CharBuffer tmpcb = CharBuffer.allocate (cb.remaining());

			while (cb.hasRemaining()) {
				tmpcb.put (cb.get());
			}

			tmpcb.rewind();

			rot13 (tmpcb);

			baseEncoder.reset();

			CoderResult cr = baseEncoder.encode (tmpcb, bb, true);

			// If error or output overflow, we need to adjust
			// the position of the input buffer to match what
			// was really consumed from the temp buffer.  If
			// underflow (all input consumed) this is a no-op.
			cb.position (cb.position() - tmpcb.remaining());

			return (cr);
		}
	}

	// --------------------------------------------------------
	
	/**
	 * The decoder implementation for the Rot13 Charset.
	 */
	private class Rot13Decoder extends CharsetDecoder
	{
		private CharsetDecoder baseDecoder;

		/**
		 * Constructor, call the superclass constructor with the
		 * Charset object and pass alon the chars/byte values
		 * from the delegate decoder.
		 */
		Rot13Decoder (Charset cs, CharsetDecoder baseDecoder)
		{
			super (cs, baseDecoder.averageCharsPerByte(),
				baseDecoder.maxCharsPerByte());

			this.baseDecoder = baseDecoder;
		}

		/**
		 * Implementation of the decoding loop.  First, we reset
		 * the decoder for the base charset, then call it to decode
		 * the bytes into characters, saving the result code.  The
		 * CharBuffer is then de-scrambled with the Rot13 algorithm
		 * and the result code is returned.  This may not
		 * work properly for non-Latin charsets.
		 */
		protected CoderResult decodeLoop (ByteBuffer bb, CharBuffer cb)
		{
			baseDecoder.reset();

			CoderResult result = baseDecoder.decode (bb, cb, true);

			rot13 (cb);

			return (result);
		}
	}

	// --------------------------------------------------------

	/**
	 * Unit test for the Rot13 Charset.  This main() will open and read 
	 * an input file if named on the command line, or stdin if no args
	 * are provided, and write the contents to stdout via the X-ROT13
	 * charset encoding.
	 * The "encryption" implemented by the Rot13 algorithm is symmetrical.
	 * Feeding in a plain-text file, such as Java source code for example,
	 * will output a scrambled version.  Feeding the scrambled version
	 * back in will yield the original plain-text document.
	 */
	public static void main (String [] argv)
		throws Exception
	{
		BufferedReader in;

		if (argv.length > 0) {
			// open the named file
			in = new BufferedReader (new FileReader (argv [0]));
		} else {
			// wrap a BufferedReader around stdin
			in = new BufferedReader (new InputStreamReader (System.in));
		}

		// Create a PrintStream which uses the Rot13 encoding
		PrintStream out = new PrintStream (System.out, false, "X-ROT13");

		String s = null;

		// Read all input and write it to the output
		// As the data passes through the PrintStream
		// it will be Rot13 encoded.
		while ((s = in.readLine()) != null) {
			out.println (s);
		}

		out.flush();
	}
}