package com.limegroup.gnutella.util;

import java.io.IOException;
import java.io.OutputStream;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;

/**
 * Simulates zlib's Z_PARTIAL_FLUSH and Z_SYNC_FLUSH behaviour.
 * This is a workaround for the following bugParade bugs:<br>
 * http://developer.java.sun.com/developer/bugParade/bugs/4255743.html <br>
 * http://developer.java.sun.com/developer/bugParade/bugs/4206909.html <br>
 * The code was taken from the comments at those respective pages and
 * modified slightly.
 */
public final class CompressingOutputStream extends DeflaterOutputStream {
    
    public CompressingOutputStream (final OutputStream out, final Deflater flate) {
      super(out, flate);
    }

    private static final byte [] EMPTYBYTEARRAY = new byte [0];
    /**
     * Insure all remaining data will be output.
     */
    public void flush() throws IOException {
        if( def.finished() ) return;
        
        /**
         * Now this is tricky: We force the Deflater to flush
         * its data by switching compression level.
         * As yet, a perplexingly simple workaround for 
         * http://developer.java.sun.com/developer/bugParade/bugs/4255743.html 
         */
        def.setInput(EMPTYBYTEARRAY, 0, 0);

        def.setLevel(Deflater.NO_COMPRESSION);
        deflate();

        def.setLevel(Deflater.DEFAULT_COMPRESSION);
        deflate();

        super.flush();
    }
    
    protected void deflate() throws IOException {
        try {
            // DO NOT CALL super.deflate(), it is wrong.
            // It incorrectly assumes that its buffer will be large enough
            // to hold all data from a single deflate call.  That is wrong.
            // We need to loop until deflate returns <= 0, saying it couldn't
            // deflate.
            int deflated;
            while( (deflated = def.deflate(buf, 0, buf.length)) > 0)
                out.write(buf, 0, deflated);
        } catch(NullPointerException e) {
            //This will happen if 'end' was called on the deflater
            //while we were deflating.
            throw new IOException("deflater was ended");
        }
    }
} // class
