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(); } }