Following are possible class definitions for CompressInputStream and
CompressOutputStream, along with an interface CompressConstants that
they both implement to share some constant data.

These classes implement an extremely simple compression algorithm
designed to compress text consisting mostly of ASCII letters, spaces,
and punctuation marks.  The compressed stream is grouped into 32-bit
words, each containing five 6-bit codes (with 2 unused bits).  62 of
the 64 possible 6-bit values map to the set of letters, spaces, and
punctuation marks that are expected to be used in the uncompressed
stream.  Another 6-bit value is used to indicate that a 8-bit value
not otherwise represented in the 6-bit code is in the stream, and the
next two codes contain the high- and low-order 4 bits of that byte,
respectively.  The last 6-bit value represents no byte in the stream
at all; it is used to pad the rest of a word if an explicit flush() is
requested on a CompressOutputStream.

Note that this extremely primitive compression algorithm is merely a
demonstration; even with the most ideal source data, it does not
produce very dramatic results.  If you have Sun's JDK 1.1, you can
find some real world compression/decompression stream implementations
in the sources for the java.util.zip package.



interface CompressConstants {

    // constants for 6-bit code values
    static final int NOP  = 0;	// no operation: used to pad words on flush()
    static final int RAW  = 1;	// introduces raw byte format
    static final int BASE = 2;	// base for codes found in lookup table
    static final String codeTable =
	"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ,.!?\"'()";
}



import java.io.*;

class CompressOutputStream extends FilterOutputStream
    implements CompressConstants
{

    public CompressOutputStream(OutputStream out) {
	super(out);
    }

    // buffer of 6-bit codes to pack into next 32-bit word
    int buf[] = new int[5];

    // number of valid codes pending in buffer
    int bufPos = 0;

    public void write(int b) throws IOException {
	b &= 0xFF;			// force argument to a byte

	int pos = codeTable.indexOf((char)b);
	if (pos != -1)
	    writeCode(BASE + pos);
	else {
	    writeCode(RAW);
	    writeCode(b >> 4);
	    writeCode(b & 0xF);
	}
    }

    public void write(byte b[], int off, int len) throws IOException {
	/*
	 * This is quite an inefficient implementation, because it has to
	 * call the other write method for every byte in the array.  It
         * could be optimized for performance by doing all the processing
	 * in this method.
	 */
	for (int i = 0; i < len; i++)
	    write(b[off + i]);
    }

    public void flush() throws IOException {
	while (bufPos > 0)
	    writeCode(NOP);
    }

    private void writeCode(int c) throws IOException {
	buf[bufPos++] = c;
	if (bufPos == 5) {	// write next word when we have 5 codes
	    int pack = (buf[0] << 24) | (buf[1] << 18) | (buf[2] << 12) |
	               (buf[3] << 6) | buf[4];
	    out.write((pack >>> 24) & 0xFF);
	    out.write((pack >>> 16) & 0xFF);
	    out.write((pack >>> 8)  & 0xFF);
	    out.write((pack >>> 0)  & 0xFF);
	    bufPos = 0;
	}
    }
}



import java.io.*;

class CompressInputStream extends FilterInputStream
    implements CompressConstants
{

    public CompressInputStream(InputStream in) {
	super(in);
    }

    // buffer of unpacked 6-bit codes from last 32-word read
    int buf[] = new int[5];

    // position of next code to read in buffer (5 == end of buffer)
    int bufPos = 5;

    public int read() throws IOException {
	try {
	    int code;
	    do {
		code = readCode();
	    } while (code == NOP);	// ignore NOP codes

	    if (code >= BASE)
		return codeTable.charAt(code - BASE);
	    else if (code == RAW) {
		int high = readCode();
		int low = readCode();
		return (high << 4) | low;
	    } else
		throw new IOException("unknown compression code: " + code);
	} catch (EOFException e) {
	    return -1;
	}
    }

    public int read(byte b[], int off, int len) throws IOException {
	if (len <= 0) {
	    return 0;
	}

	int c = read();
	if (c == -1) {
	    return -1;
	}
	b[off] = (byte)c;

	int i = 1;
	try {
	    for (; i < len ; i++) {
		c = read();
		if (c == -1) {
		    break;
		}
		if (b != null) {
		    b[off + i] = (byte)c;
		}
	    }
	} catch (IOException ee) {
	}
	return i;
    }

    private int readCode() throws IOException {
	if (bufPos == 5) {
	    int b1 = in.read();
	    int b2 = in.read();
	    int b3 = in.read();
	    int b4 = in.read();
	    if ((b1 | b2 | b3 | b4) < 0)
		throw new EOFException();
	    int pack = (b1 << 24) | (b2 << 16) | (b3 << 8) | b4;
	    buf[0] = (pack >>> 24) & 0x3F;
	    buf[1] = (pack >>> 18) & 0x3F;
	    buf[2] = (pack >>> 12) & 0x3F;
	    buf[3] = (pack >>>  6) & 0x3F;
	    buf[4] = (pack >>>  0) & 0x3F;
	    bufPos = 0;
	}
	return buf[bufPos++];
    }
}
