//============================================================================== // DeflaterInputStream.java //============================================================================== package tribble.io; // System imports import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FilterInputStream; import java.io.InputStream; import java.io.IOException; import java.io.OutputStream; import java.lang.IllegalArgumentException; import java.lang.NullPointerException; import java.lang.String; import java.lang.System; import java.lang.Throwable; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; // Local imports // (None) /******************************************************************************* * Implements an input stream filter for compressing data in the "deflate" * compression format. * *

* This class serves as a complement to the standard * java.util.zip.InflaterInputStream and * java.util.zip.DeflaterOutputStream classes. * It compresses ("deflates") data like the latter class, but reads the * compressed data from an input stream like the former class. * * * @version $Revision: 1.6 $ $Date: 2006/02/13 23:58:17 $ * @since 2005-05-29 * @author David R. Tribble * (david@tribble.com). * *

* Copyright ©2005 by David R. Tribble, all rights reserved. *
* Permission is granted to freely use and distribute this source * code provided that the original copyright and authorship notices * remain intact. * * @see InflaterOutputStream * @see java.util.zip.DeflaterOutputStream * @see java.util.zip.InflaterInputStream * @see java.util.zip.DeflaterInputStream */ public class DeflaterInputStream extends java.io.FilterInputStream { // Identification /** Revision information. */ static final String REV = "@(#)tribble/io/DeflaterInputStream.java $Revision: 1.6 $ $Date: 2006/02/13 23:58:17 $\n"; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Constants /** Default buffer size (512 bytes). */ private static final int DFL_BUFLEN = 512; /** I/O buffer size (in bytes) (see {@link #main main()}). */ private static final int IO_BUFLEN = 4*1024; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Static methods /*************************************************************************** * Uncompress a file containing data stored in the "deflate" compression * format. * *

* The file to be uncompressed should contain compressed data stored in the * "deflate" compression format, such as the output from the * {@link InflaterOutputStream} class. * * *

* Usage *

* * java tribble.io.DeflaterInputStream [-option...] file * * *

* Options *

*

*
-j *
Use the java.util.zip.DeflaterOutputStream class * instead of this class. * *
-o file *
Output filename. * By default, the output is written to standard output. * *
file *
Input filename. * If this is "-", input is read from standard input. *
* * * @param args * Command line arguments. * * @see InflaterOutputStream#main InflaterOutputStream.main() * * @since 1.1, 2005-06-29 */ public static void main(final String[] args) throws Exception { String inFname = "-"; String outFname = "-"; boolean useThis = true; InputStream in; OutputStream out; byte[] buf; int i; // Get command line options for (i = 0; i < args.length && args[i].charAt(0) == '-'; i++) { if (args[i].equals("-")) break; else if (args[i].equals("-j")) useThis = false; else if (args[i].equals("-o")) outFname = args[++i]; else { System.err.println("Bad option: '" + args[i] + "'"); System.exit(255); } } // Check command line args if (i >= args.length) { // Display a usage message System.out.println("Decompress a file."); System.out.println(); System.out.println("usage: " + DeflaterInputStream.class.getName() + " [-option...] file"); System.out.println(); System.out.println("Options:"); System.out.println(" -j " + "Use the standard Java class instead of this one."); System.out.println(" -o file " + "Output file."); // Punt System.exit(255); } // Open the input file inFname = args[i++]; if (inFname.equals("-")) { // Read from standard input in = System.in; } else { File inFile; // Read from a named file inFile = new File(inFname); if (!inFile.exists() || !inFile.canRead()) throw new IOException("Can't read: " + inFname); in = new FileInputStream(inFname); } // Open the output file if (outFname.equals("-")) { // Write to standard output out = System.out; } else { // Write to a named file out = new FileOutputStream(outFname); } // Deflate the input file buf = new byte[IO_BUFLEN]; if (useThis) { DeflaterInputStream defl; // Initialize the file deflater defl = new DeflaterInputStream(in); // Deflate the contents of the input file for (;;) { int len; // Read and compress some data from the input stream len = defl.read(buf, 0, buf.length); if (len < 0) break; // Write the compressed data to the output stream out.write(buf, 0, len); out.flush(); } // Finish up if (in != System.in) defl.close(); } else { DeflaterOutputStream defl; // Initialize the file deflater defl = new DeflaterOutputStream(out); // Deflate the contents of the input file for (;;) { int len; // Read some data from the input stream len = in.read(buf, 0, buf.length); if (len < 0) break; // Compress and write the data to the output stream defl.write(buf, 0, len); out.flush(); } // Finish up defl.finish(); if (out != System.out) defl.close(); } // Clean up if (in != System.in) in.close(); out.flush(); if (out != System.out) out.close(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Variables /** Compressor for this stream. */ protected Deflater m_defl; /** Input buffer for reading compressed data. */ protected byte[] m_buf; /** Total bytes read from the underlying input stream. */ protected long m_inBytes; /** Total bytes returned from this input stream. */ protected long m_outBytes; /** Temporary read buffer. */ private byte[] m_rbuf = new byte[1]; /** Default compressor is used. */ private boolean m_isDefault; /** End of the input stream has been reached. */ private boolean m_eof; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Constructors /*************************************************************************** * Construct a new input stream with a default compressor and buffer size. * * @param in * Input stream to read the uncompressed data to. * * @since 1.1, 2005-06-29 */ public DeflaterInputStream(InputStream in) { // Initialize this(in, new Deflater(), DFL_BUFLEN); m_isDefault = true; } /*************************************************************************** * Construct a new input stream with the specified compressor and a * default buffer size. * * @param in * Input stream to read the uncompressed data to. * * @param defl * Compressor ("deflater") for this stream. * * @since 1.1, 2005-06-29 */ public DeflaterInputStream(InputStream in, Deflater defl) { // Initialize this(in, defl, DFL_BUFLEN); } /*************************************************************************** * Construct a new input stream with the specified compressor and buffer * size. * * @param in * Input stream to read the uncompressed data to. * * @param defl * Compressor ("deflater") for this stream. * * @param bufLen * Compression buffer size. * * @throws NullPointerException (unchecked) * Thrown if in or defl is null. * * @throws IllegalArgumentException (unchecked) * Thrown if bufLen is less than 1. * * @since 1.1, 2005-06-29 */ public DeflaterInputStream(InputStream in, Deflater defl, int bufLen) { // Initialize super(in); // Sanity checks if (in == null) throw new NullPointerException("Null input"); if (defl == null) throw new NullPointerException("Null deflater"); if (bufLen < 1) throw new IllegalArgumentException("Buffer size < 1"); // Initialize m_defl = defl; m_buf = new byte[bufLen]; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Methods /*************************************************************************** * Close this input stream and its underlying input stream, discarding any * pending uncompressed data. * *

* Note that this method can be called multiple times with no ill effects. * * @throws IOException * Thrown if an I/O error occurs. * * @since 1.1, 2005-06-29 */ public void close() throws IOException //overrides java.io.FilterInputStream { InputStream inp; Deflater defl; // Sanity check if (super.in == null) return; // Clean up inp = super.in; super.in = null; defl = m_defl; m_defl = null; m_buf = null; if (m_isDefault) defl.end(); inp.close(); } /*************************************************************************** * Read compressed data from the input stream. * This will block until some input can be read and compressed. * * @return * A single byte of compressed data (in the range * [0x00,0xFF]), or -1 if the end of the uncompressed input * stream is reached. * * @throws IOException * Thrown if an I/O (read) error occurs. * * @throws NullPointerException (unchecked) * Thrown if the underlying input stream is closed. * * @since 1.1, 2005-06-29 */ public int read() throws IOException //overrides java.io.FilterInputStream { int len; // Read a single byte of compressed data len = read(m_rbuf, 0, 1); if (len <= 0) return (-1); return (m_rbuf[0] & 0xFF); } /*************************************************************************** * Read compressed data from the input stream. * This will block until some input can be read and compressed. * * @param buf * Buffer into which the data is read. * An attempt is made to fill the entire array. * * @return * The actual number of bytes read, or -1 if the end of the uncompressed * input stream is reached. * * @throws IOException * Thrown if an I/O (read) error occurs. * * @throws NullPointerException (unchecked) * Thrown if the underlying input stream is closed. * * @since 1.1, 2005-06-29 */ public int read(byte[] buf) throws IOException //overrides java.io.FilterInputStream { // Read a block of compressed data return (read(buf, 0, buf.length)); } /*************************************************************************** * Read compressed data from the input stream. * This will block until some input can be read and compressed. * * @param buf * Buffer into which the data is read. * * @param off * Starting offset of the data within buf. * * @param len * Maximum number of compressed bytes to read into buf. * * @return * The actual number of bytes read, or -1 if the end of the uncompressed * input stream is reached. * * @throws IOException * Thrown if an I/O (read) error occurs. * * @throws NullPointerException (unchecked) * Thrown if the underlying input stream is closed. * * @since 1.1, 2005-06-29 */ public int read(byte[] buf, int off, int len) throws IOException //overrides java.io.FilterInputStream { int cnt; // Sanity checks if (super.in == null) throw new NullPointerException(); if ((off | len | (off + len)) < 0) throw new IndexOutOfBoundsException(); if ((buf.length - (off + len)) < 0) throw new IndexOutOfBoundsException(); // Read and compress (deflate) input data bytes cnt = 0; while (len > 0 && !m_defl.finished()) { int n; // Read data from the input stream if (m_defl.needsInput() && !m_eof) { n = super.in.read(m_buf, 0, m_buf.length); if (n < 0) { // End of the input stream reached m_defl.finish(); m_eof = true; } else if (n > 0) { m_inBytes += n; m_defl.setInput(m_buf, 0, n); } } // Compress the input data, filling the read buffer n = m_defl.deflate(buf, off, len); cnt += n; off += n; len -= n; m_outBytes += n; } // Done if (cnt == 0 && m_eof) return (-1); return (cnt); } /*************************************************************************** * Skip over and discard data from the input stream. * This method may block until the specified number of bytes are read and * skipped. * * @param n * Number of bytes to be skipped. * * @return * The actual number of bytes skipped. * * @throws IOException * Thrown if an I/O (read) error occurs. * * @throws NullPointerException (unchecked) * Thrown if the underlying input stream is closed. * * @since 1.1, 2005-06-29 */ public long skip(long n) throws IOException //overrides java.io.FilterInputStream { long cnt; // Sanity checks if (super.in == null) throw new NullPointerException(); // Set up if (m_rbuf.length < 128) m_rbuf = new byte[128]; // Skip bytes by repeatedly decompressing small blocks cnt = 0; while (n > 0) { int len; // Read a small block of uncompressed bytes len = m_rbuf.length; len = read(m_rbuf, 0, (n <= len ? (int) n : len)); // Check for end of input reached if (len < 0) break; // Update counters cnt += len; n -= len; } return (cnt); } /*************************************************************************** * Determine the number of bytes that can be read from this input stream * without blocking. * *

* Programs should not count on this method to return the actual number * of bytes that could be read without blocking. * * @return * Zero after the end of the underlying input stream has been reached, * otherwise always returns 1. * * @throws IOException * Thrown if an I/O (read) error occurs. * * @since 1.1, 2005-06-29 */ public int available() throws IOException //overrides java.io.FilterInputStream { if (m_eof || super.in == null) return (0); return (1); } /*************************************************************************** * Test if this input stream supports the {@link #mark mark()} and * {@link #reset reset()} methods. * These operations are not supported. * * @return * False, always. * * @since 1.1, 2005-06-29 */ public boolean markSupported() //overrides java.io.FilterInputStream { // Operation not supported return (false); } /*************************************************************************** * This operation is not supported. * * @param limit * Maximum bytes that can be read before invalidating the position marker. * * @since 1.1, 2005-06-29 */ public void mark(int limit) //overrides java.io.FilterInputStream { // Operation not supported } /*************************************************************************** * This operation is not supported. * * @throws IOException * Always thrown. * * @since 1.1, 2005-06-29 */ public void reset() throws IOException //overrides java.io.FilterInputStream { // Operation not supported throw new IOException("Mark/reset operation not supported"); } /*************************************************************************** * Determine the total number of bytes read from the underlying input stream. * * @return * The actual number of bytes read from the underlying input stream prior to * being compressed. * * @since 1.2, 2005-06-30 */ public long getTotalIn() { return (m_inBytes); } /*************************************************************************** * Determine the total number of bytes read from this compressed input * stream. * * @return * The number of bytes read from this input stream after being compressed. * * @since 1.2, 2005-06-30 */ public long getTotalOut() { return (m_outBytes); } /*************************************************************************** * Finalization. * * @since 1.6, 2006-02-13 */ protected synchronized void finalize() throws Throwable //overrides java.io.FilterInputStream { Deflater defl; // Shut down the compressor m_buf = null; defl = m_defl; m_defl = null; if (defl != null && m_isDefault) defl.end(); // Cascade super.finalize(); super.in = null; } } // End DeflaterInputStream.java