Java Source Code: net.i2p.util.ResettableGZIPOutputStream


   1: package net.i2p.util;
   2: 
   3: import java.io.ByteArrayOutputStream;
   4: import java.io.ByteArrayInputStream;
   5: import java.io.IOException;
   6: import java.io.OutputStream;
   7: 
   8: import java.util.zip.CRC32;
   9: import java.util.zip.Deflater;
  10: import java.util.zip.DeflaterOutputStream;
  11: import java.util.zip.GZIPOutputStream;
  12: import java.util.zip.GZIPInputStream;
  13: import net.i2p.data.DataHelper;
  14: 
  15: /**
  16:  * GZIP implementation per 
  17:  * <a href="http://www.faqs.org/rfcs/rfc1952.html">RFC 1952</a>, reusing 
  18:  * java's standard CRC32 and Deflater implementations.  The main difference
  19:  * is that this implementation allows its state to be reset to initial 
  20:  * values, and hence reused, while the standard GZIPOutputStream writes the
  21:  * GZIP header to the stream on instantiation, rather than on first write. 
  22:  *
  23:  */
  24:	  public class ResettableGZIPOutputStream extends DeflaterOutputStream {
  25:    /** has the header been written out yet? */
  26:    private boolean _headerWritten;
  27:    /** how much data is in the uncompressed stream? */
  28:    private long _writtenSize;
  29:    private CRC32 _crc32;
  30:    private static final boolean DEBUG = false;
  31:    
  32:	      public ResettableGZIPOutputStream(OutputStream o) {
  33:        super(o, new Deflater(9, true));
  34:        _headerWritten = false;
  35:        _crc32 = new CRC32();
  36:    }
  37:    /**
  38:     * Reinitialze everything so we can write a brand new gzip output stream
  39:     * again.
  40:     */
  41:	      public void reset() { 
  42:        if (DEBUG)
  43:            System.out.println("Resetting (writtenSize=" + _writtenSize + ")");
  44:        def.reset();
  45:        _crc32.reset();
  46:        _writtenSize = 0;
  47:        _headerWritten = false;
  48:    }
  49:    
  50:	      private static final byte[] HEADER = new byte[] {
  51:        (byte)0x1F, (byte)0x8b, // magic bytes 
  52:        0x08,                   // compression format == DEFLATE
  53:        0x00,                   // flags (NOT using CRC16, filename, etc)
  54:        0x00, 0x00, 0x00, 0x00, // no modification time available (don't leak this!)
  55:        0x02,                   // maximum compression
  56:        (byte)0xFF              // unknown creator OS (!!!)
  57:    };
  58:    
  59:    /**
  60:     * obviously not threadsafe, but its a stream, thats standard
  61:     */
  62:	      private void ensureHeaderIsWritten() throws IOException {
  63:        if (_headerWritten) return;
  64:        if (DEBUG) System.out.println("Writing header");
  65:        out.write(HEADER);
  66:        _headerWritten = true;
  67:    }
  68:    
  69:	      private void writeFooter() throws IOException {
  70:        // damn RFC writing their bytes backwards...
  71:        long crcVal = _crc32.getValue();
  72:        out.write((int)(crcVal & 0xFF));
  73:        out.write((int)((crcVal >>> 8) & 0xFF));
  74:        out.write((int)((crcVal >>> 16) & 0xFF));
  75:        out.write((int)((crcVal >>> 24) & 0xFF));
  76:        
  77:        long sizeVal = _writtenSize; // % (1 << 31) // *redundant*
  78:        out.write((int)(sizeVal & 0xFF));
  79:        out.write((int)((sizeVal >>> 8) & 0xFF));
  80:        out.write((int)((sizeVal >>> 16) & 0xFF));
  81:        out.write((int)((sizeVal >>> 24) & 0xFF));
  82:        out.flush();
  83:	          if (DEBUG) {
  84:            System.out.println("Footer written: crcVal=" + crcVal + " sizeVal=" + sizeVal + " written=" + _writtenSize);
  85:            System.out.println("size hex: " + Long.toHexString(sizeVal));
  86:            System.out.print(  "size2 hex:" + Long.toHexString((int)(sizeVal & 0xFF)));
  87:            System.out.print(  Long.toHexString((int)((sizeVal >>> 8) & 0xFF)));
  88:            System.out.print(  Long.toHexString((int)((sizeVal >>> 16) & 0xFF)));
  89:            System.out.print(  Long.toHexString((int)((sizeVal >>> 24) & 0xFF)));
  90:            System.out.println();
  91:        }
  92:    }
  93:    
  94:	      public void close() throws IOException {
  95:        finish();
  96:        super.close();
  97:    }
  98:	      public void finish() throws IOException {
  99:        ensureHeaderIsWritten();
 100:        super.finish();
 101:        writeFooter();
 102:    }
 103:    
 104:	      public void write(int b) throws IOException {
 105:        ensureHeaderIsWritten();
 106:        _crc32.update(b);
 107:        _writtenSize++;
 108:        super.write(b);
 109:    }
 110:	      public void write(byte buf[]) throws IOException {
 111:        write(buf, 0, buf.length);
 112:    }
 113:	      public void write(byte buf[], int off, int len) throws IOException {
 114:        ensureHeaderIsWritten();
 115:        _crc32.update(buf, off, len);
 116:        _writtenSize += len;
 117:        super.write(buf, off, len);
 118:    }
 119:    
 120:	      public static void main(String args[]) {
 121:        for (int i = 0; i < 2; i++)
 122:            test();
 123:    }
 124:	      private static void test() {
 125:        byte b[] = "hi, how are you today?".getBytes();
 126:	          try { 
 127:            ByteArrayOutputStream baos = new ByteArrayOutputStream(64);
 128:            ResettableGZIPOutputStream o = new ResettableGZIPOutputStream(baos);
 129:            o.write(b);
 130:            o.finish();
 131:            o.flush();
 132:            byte compressed[] = baos.toByteArray();
 133:            
 134:            ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
 135:            SnoopGZIPOutputStream gzo = new SnoopGZIPOutputStream(baos2);
 136:            gzo.write(b);
 137:            gzo.finish();
 138:            gzo.flush();
 139:            long value = gzo.getCRC().getValue();
 140:            byte compressed2[] = baos2.toByteArray();
 141:            System.out.println("CRC32 values: Resettable = " + o._crc32.getValue() 
 142:                               + " GZIP = " + value);
 143:            
 144:            System.out.print("Resettable compressed data: ");
 145:            for (int i = 0; i < compressed.length; i++)
 146:                System.out.print(Integer.toHexString(compressed[i] & 0xFF) + " ");
 147:            System.out.println();
 148:            System.out.print("      GZIP compressed data: ");
 149:            for (int i = 0; i < compressed2.length; i++)
 150:                System.out.print(Integer.toHexString(compressed2[i] & 0xFF) + " ");
 151:            System.out.println();
 152:            
 153:            GZIPInputStream in = new GZIPInputStream(new ByteArrayInputStream(compressed));
 154:            byte rv[] = new byte[128];
 155:            int read = in.read(rv);
 156:            if (!DataHelper.eq(rv, 0, b, 0, b.length))
 157:                throw new RuntimeException("foo, read=" + read);
 158:            else
 159:                System.out.println("match, w00t");
 160:        } catch (Exception e) { e.printStackTrace(); }
 161:    }
 162:    
 163:    /** just for testing/verification, expose the CRC32 values */
 164:	      private static final class SnoopGZIPOutputStream extends GZIPOutputStream {
 165:	          public SnoopGZIPOutputStream(OutputStream o) throws IOException {
 166:            super(o);
 167:        }
 168:        public CRC32 getCRC() { return crc; }
 169:    }
 170:}