001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.commons.compress.compressors.gzip; 020 021import java.io.IOException; 022import java.io.OutputStream; 023import java.nio.ByteBuffer; 024import java.nio.ByteOrder; 025import java.util.zip.CRC32; 026import java.util.zip.Deflater; 027import java.util.zip.GZIPInputStream; 028import java.util.zip.GZIPOutputStream; 029 030import org.apache.commons.compress.compressors.CompressorOutputStream; 031import org.apache.commons.compress.utils.CharsetNames; 032 033/** 034 * Compressed output stream using the gzip format. This implementation improves 035 * over the standard {@link GZIPOutputStream} class by allowing 036 * the configuration of the compression level and the header metadata (filename, 037 * comment, modification time, operating system and extra flags). 038 * 039 * @see <a href="http://tools.ietf.org/html/rfc1952">GZIP File Format Specification</a> 040 */ 041public class GzipCompressorOutputStream extends CompressorOutputStream { 042 043 /** Header flag indicating a file name follows the header */ 044 private static final int FNAME = 1 << 3; 045 046 /** Header flag indicating a comment follows the header */ 047 private static final int FCOMMENT = 1 << 4; 048 049 /** The underlying stream */ 050 private final OutputStream out; 051 052 /** Deflater used to compress the data */ 053 private final Deflater deflater; 054 055 /** The buffer receiving the compressed data from the deflater */ 056 private final byte[] deflateBuffer = new byte[512]; 057 058 /** Indicates if the stream has been closed */ 059 private boolean closed; 060 061 /** The checksum of the uncompressed data */ 062 private final CRC32 crc = new CRC32(); 063 064 /** 065 * Creates a gzip compressed output stream with the default parameters. 066 * @param out the stream to compress to 067 * @throws IOException if writing fails 068 */ 069 public GzipCompressorOutputStream(final OutputStream out) throws IOException { 070 this(out, new GzipParameters()); 071 } 072 073 /** 074 * Creates a gzip compressed output stream with the specified parameters. 075 * @param out the stream to compress to 076 * @param parameters the parameters to use 077 * @throws IOException if writing fails 078 * 079 * @since 1.7 080 */ 081 public GzipCompressorOutputStream(final OutputStream out, final GzipParameters parameters) throws IOException { 082 this.out = out; 083 this.deflater = new Deflater(parameters.getCompressionLevel(), true); 084 085 writeHeader(parameters); 086 } 087 088 private void writeHeader(final GzipParameters parameters) throws IOException { 089 final String filename = parameters.getFilename(); 090 final String comment = parameters.getComment(); 091 092 final ByteBuffer buffer = ByteBuffer.allocate(10); 093 buffer.order(ByteOrder.LITTLE_ENDIAN); 094 buffer.putShort((short) GZIPInputStream.GZIP_MAGIC); 095 buffer.put((byte) Deflater.DEFLATED); // compression method (8: deflate) 096 buffer.put((byte) ((filename != null ? FNAME : 0) | (comment != null ? FCOMMENT : 0))); // flags 097 buffer.putInt((int) (parameters.getModificationTime() / 1000)); 098 099 // extra flags 100 final int compressionLevel = parameters.getCompressionLevel(); 101 if (compressionLevel == Deflater.BEST_COMPRESSION) { 102 buffer.put((byte) 2); 103 } else if (compressionLevel == Deflater.BEST_SPEED) { 104 buffer.put((byte) 4); 105 } else { 106 buffer.put((byte) 0); 107 } 108 109 buffer.put((byte) parameters.getOperatingSystem()); 110 111 out.write(buffer.array()); 112 113 if (filename != null) { 114 out.write(filename.getBytes(CharsetNames.ISO_8859_1)); 115 out.write(0); 116 } 117 118 if (comment != null) { 119 out.write(comment.getBytes(CharsetNames.ISO_8859_1)); 120 out.write(0); 121 } 122 } 123 124 private void writeTrailer() throws IOException { 125 final ByteBuffer buffer = ByteBuffer.allocate(8); 126 buffer.order(ByteOrder.LITTLE_ENDIAN); 127 buffer.putInt((int) crc.getValue()); 128 buffer.putInt(deflater.getTotalIn()); 129 130 out.write(buffer.array()); 131 } 132 133 @Override 134 public void write(final int b) throws IOException { 135 write(new byte[]{(byte) (b & 0xff)}, 0, 1); 136 } 137 138 /** 139 * {@inheritDoc} 140 * 141 * @since 1.1 142 */ 143 @Override 144 public void write(final byte[] buffer) throws IOException { 145 write(buffer, 0, buffer.length); 146 } 147 148 /** 149 * {@inheritDoc} 150 * 151 * @since 1.1 152 */ 153 @Override 154 public void write(final byte[] buffer, final int offset, final int length) throws IOException { 155 if (deflater.finished()) { 156 throw new IOException("Cannot write more data, the end of the compressed data stream has been reached"); 157 158 } else if (length > 0) { 159 deflater.setInput(buffer, offset, length); 160 161 while (!deflater.needsInput()) { 162 deflate(); 163 } 164 165 crc.update(buffer, offset, length); 166 } 167 } 168 169 private void deflate() throws IOException { 170 final int length = deflater.deflate(deflateBuffer, 0, deflateBuffer.length); 171 if (length > 0) { 172 out.write(deflateBuffer, 0, length); 173 } 174 } 175 176 /** 177 * Finishes writing compressed data to the underlying stream without closing it. 178 * 179 * @since 1.7 180 * @throws IOException on error 181 */ 182 public void finish() throws IOException { 183 if (!deflater.finished()) { 184 deflater.finish(); 185 186 while (!deflater.finished()) { 187 deflate(); 188 } 189 190 writeTrailer(); 191 } 192 } 193 194 /** 195 * {@inheritDoc} 196 * 197 * @since 1.7 198 */ 199 @Override 200 public void flush() throws IOException { 201 out.flush(); 202 } 203 204 @Override 205 public void close() throws IOException { 206 if (!closed) { 207 finish(); 208 deflater.end(); 209 out.close(); 210 closed = true; 211 } 212 } 213 214}