//============================================================================== // InflaterOutputStream.java //============================================================================== package tribble.io; // System imports import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FilterOutputStream; 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.DataFormatException; import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; import java.util.zip.ZipException; // Local imports // (None) /******************************************************************************* * Implements an output stream filter for uncompressing data stored 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 decompresses ("inflates") data like the former class, but writes the * decompressed data to an output stream like the latter class. * * * @version $Revision: 1.6 $ $Date: 2006/02/14 00:05:53 $ * @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 DeflaterInputStream * @see java.util.zip.DeflaterOutputStream * @see java.util.zip.InflaterInputStream * @see java.util.zip.InflaterOutputStream */ public class InflaterOutputStream extends java.io.FilterOutputStream { // Identification /** Revision information. */ static final String REV = "@(#)tribble/io/InflaterOutputStream.java $Revision: 1.6 $ $Date: 2006/02/14 00:05:53 $\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; /** Partial write block size. */ private static final int PART_LEN = 512; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // 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 DeflaterInputStream} class. * * *

* Usage *

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

* Options *

*

*
-j *
Use the java.util.zip.InflaterInputStream 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 DeflaterInputStream#main DeflaterInputStream.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("Compress a file."); System.out.println(); System.out.println("usage: " + InflaterOutputStream.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); } // Inflate the input file buf = new byte[IO_BUFLEN]; if (useThis) { InflaterOutputStream infl; // Initialize the file inflater infl = new InflaterOutputStream(out); // Inflate 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; // Decompress and write the data to the output stream infl.write(buf, 0, len); out.flush(); } // Finish up infl.finish(); if (in != System.in) infl.close(); } else { InflaterInputStream infl; // Initialize the file inflater infl = new InflaterInputStream(in); // Inflate the contents of the input file for (;;) { int len; // Read and decompress some data from the input stream len = infl.read(buf, 0, buf.length); if (len < 0) break; // Write the decompressed data to the output stream out.write(buf, 0, len); out.flush(); } // Finish up if (out != System.out) infl.close(); } // Clean up if (in != System.in) in.close(); out.flush(); if (out != System.out) out.close(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Variables /** Decompressor for this stream. */ protected Inflater m_infl; /** Output buffer for writing compressed data. */ protected byte[] m_buf; /** Total bytes written to this output stream. */ protected long m_inBytes; /** Total bytes written to the underlying output stream. */ protected long m_outBytes; /** Temporary write buffer. */ private byte[] m_wbuf = new byte[1]; /** Default decompressor is used. */ private boolean m_isDefault; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Constructors /*************************************************************************** * Construct a new output stream with a default decompressor and buffer size. * * @param out * Output stream to write the uncompressed data to. * * @since 1.1, 2005-06-29 */ public InflaterOutputStream(OutputStream out) { // Initialize this(out, new Inflater(), DFL_BUFLEN); m_isDefault = true; } /*************************************************************************** * Construct a new output stream with the specified decompressor and a * default buffer size. * * @param out * Output stream to write the uncompressed data to. * * @param infl * Decompressor ("inflater") for this stream. * * @since 1.1, 2005-06-29 */ public InflaterOutputStream(OutputStream out, Inflater infl) { // Initialize this(out, infl, DFL_BUFLEN); } /*************************************************************************** * Construct a new output stream with the specified decompressor and buffer * size. * * @param out * Output stream to write the uncompressed data to. * * @param infl * Decompressor ("inflater") for this stream. * * @param bufLen * Decompression buffer size. * * @throws NullPointerException (unchecked) * Thrown if out or infl is null. * * @throws IllegalArgumentException (unchecked) * Thrown if bufLen is less than 1. * * @since 1.1, 2005-06-29 */ public InflaterOutputStream(OutputStream out, Inflater infl, int bufLen) { // Initialize super(out); // Sanity checks if (out == null) throw new NullPointerException(); if (infl == null) throw new NullPointerException("Null inflater"); if (bufLen < 1) throw new IllegalArgumentException("Buffer size < 1"); // Initialize m_infl = infl; m_buf = new byte[bufLen]; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Methods /*************************************************************************** * Write any remaining uncompressed data to this output stream and close the * underlying output stream. * *

* 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-30 */ public void close() throws IOException //overrides java.io.FilterOutputStream { OutputStream outp; Inflater infl; // Sanity check if (super.out == null) return; // Complete the uncompressed output flush(); // Clean up outp = super.out; super.out = null; infl = m_infl; m_infl = null; m_buf = null; if (m_isDefault) infl.end(); outp.close(); } /*************************************************************************** * Flush this output stream, forcing any buffered output bytes to be written * to the stream. * * @throws IOException * Thrown if an I/O error occurs. * * @throws NullPointerException (unchecked) * Thrown if the output stream is closed. * * @since 1.2, 2005-07-02 */ public void flush() throws IOException //overrides java.io.FilterOutputStream { // Sanity check if (super.out == null) throw new NullPointerException(); // Finish decompressing and writing pending output data if (!m_infl.finished()) { try { while (!m_infl.finished() && !m_infl.needsInput()) { int n; // Decompress pending output data n = m_infl.inflate(m_buf, 0, m_buf.length); if (n < 1) break; // Write the decompressed output data block super.out.write(m_buf, 0, n); m_outBytes += n; } super.out.flush(); } catch (DataFormatException ex) { String msg; // Improperly formatted compressed (ZIP) data msg = ex.getMessage(); if (msg == null) msg = "Invalid ZLIB data format"; throw new ZipException(msg); } } } /*************************************************************************** * Finish writing uncompressed data to this output stream. * Does not close the underlying stream. This is useful when applying * multiple filters in succession to the same output stream. * * @throws IOException * Thrown if an I/O error occurs. * * @throws NullPointerException (unchecked) * Thrown if the output stream is closed. * * @see #flush * @see #close * * @since 1.5, 2005-07-25 */ public void finish() throws IOException { // Sanity check if (super.out == null) throw new NullPointerException(); // Finish decompressing and writing pending output data flush(); m_infl.end(); } /*************************************************************************** * Write data to this uncompressed output stream. * * @param b * A single byte of compresses data to decompress and write to the output * stream. * * @throws ZipException * Thrown if a compression (ZIP) format error occurs. * * @throws IOException * Thrown if an I/O error occurs. * * @throws NullPointerException (unchecked) * Thrown if the output stream is closed. * * @since 1.1, 2005-06-30 */ public void write(int b) throws IOException //overrides java.io.FilterOutputStream { // Write a single byte of data m_wbuf[0] = (byte) b; write(m_wbuf, 0, 1); } /*************************************************************************** * Write data to this uncompressed output stream. * * @param buf * Buffer containing compressed data to decompress and write to the output * stream. * * @throws ZipException * Thrown if a compression (ZIP) format error occurs. * * @throws IOException * Thrown if an I/O error occurs. * * @throws NullPointerException (unchecked) * Thrown if the output stream is closed. * * @since 1.1, 2005-06-30 */ public void write(/*const*/ byte[] buf) throws IOException //overrides java.io.FilterOutputStream { // Write a block of uncompressed data write(buf, 0, buf.length); } /*************************************************************************** * Write data to this uncompressed output stream. * * @param buf * Buffer containing compressed data to decompress and write to the output * stream. * * @param off * Starting offset of the compressed data within buf. * * @param len * Number of bytes to decompress from buf. * * @throws ZipException * Thrown if a compression (ZIP) format error occurs. * * @throws IOException * Thrown if an I/O error occurs. * * @throws NullPointerException (unchecked) * Thrown if the output stream is closed. * * @since 1.1, 2005-06-29 */ public void write(/*const*/ byte[] buf, int off, int len) throws IOException //overrides java.io.FilterOutputStream { // Sanity checks if (super.out == null) throw new NullPointerException(); if ((off | len | (off + len)) < 0) throw new IndexOutOfBoundsException(); if ((buf.length - (off + len)) < 0) throw new IndexOutOfBoundsException(); // Write decompressed data to the output stream try { for (;;) { int n; // Supply the decompressor with output data if (m_infl.needsInput()) { int part; // Check for the end of the decompression output if (len < 1) break; // Fill the decompression buffer with some output data; // this may reduce excessive memory allocations // within the decompressor part = (len < PART_LEN ? len : PART_LEN); m_infl.setInput(buf, off, part); off += part; len -= part; m_inBytes += part; } // Decompress blocks of output data do { // Decompress a block of output data n = m_infl.inflate(m_buf, 0, m_buf.length); if (n > 0) { // Write the decompressed output data block super.out.write(m_buf, 0, n); m_outBytes += n; } } while (n > 0); // Check the decompressor if (m_infl.finished()) break; if (m_infl.needsDictionary()) throw new ZipException("ZLIB dictionary missing"); } } catch (DataFormatException ex) { String msg; // Improperly formatted compressed (ZIP) data msg = ex.getMessage(); if (msg == null) msg = "Invalid ZLIB data format"; throw new ZipException(msg); } } /*************************************************************************** * Determine the total number of bytes written to this decompressed output * stream. * * @return * The actual number of bytes written to this output stream prior to being * decompressed. * * @since 1.2, 2005-06-30 */ public long getTotalIn() { return (m_inBytes); } /*************************************************************************** * Determine the total number of bytes written to the underlying output * stream. * * @return * The number of bytes written to the underlying output stream after being * decompressed. * * @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.FilterOutputStream { Inflater infl; // Shut down the decompressor m_buf = null; infl = m_infl; m_infl = null; if (infl != null && m_isDefault) infl.end(); // Cascade super.finalize(); super.out = null; } } // End InflaterOutputStream.java