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 */ 067 public GzipCompressorOutputStream(OutputStream out) throws IOException { 068 this(out, new GzipParameters()); 069 } 070 071 /** 072 * Creates a gzip compressed output stream with the specified parameters. 073 * 074 * @since 1.7 075 */ 076 public GzipCompressorOutputStream(OutputStream out, GzipParameters parameters) throws IOException { 077 this.out = out; 078 this.deflater = new Deflater(parameters.getCompressionLevel(), true); 079 080 writeHeader(parameters); 081 } 082 083 private void writeHeader(GzipParameters parameters) throws IOException { 084 String filename = parameters.getFilename(); 085 String comment = parameters.getComment(); 086 087 ByteBuffer buffer = ByteBuffer.allocate(10); 088 buffer.order(ByteOrder.LITTLE_ENDIAN); 089 buffer.putShort((short) GZIPInputStream.GZIP_MAGIC); 090 buffer.put((byte) Deflater.DEFLATED); // compression method (8: deflate) 091 buffer.put((byte) ((filename != null ? FNAME : 0) | (comment != null ? FCOMMENT : 0))); // flags 092 buffer.putInt((int) (parameters.getModificationTime() / 1000)); 093 094 // extra flags 095 int compressionLevel = parameters.getCompressionLevel(); 096 if (compressionLevel == Deflater.BEST_COMPRESSION) { 097 buffer.put((byte) 2); 098 } else if (compressionLevel == Deflater.BEST_SPEED) { 099 buffer.put((byte) 4); 100 } else { 101 buffer.put((byte) 0); 102 } 103 104 buffer.put((byte) parameters.getOperatingSystem()); 105 106 out.write(buffer.array()); 107 108 if (filename != null) { 109 out.write(filename.getBytes(CharsetNames.ISO_8859_1)); 110 out.write(0); 111 } 112 113 if (comment != null) { 114 out.write(comment.getBytes(CharsetNames.ISO_8859_1)); 115 out.write(0); 116 } 117 } 118 119 private void writeTrailer() throws IOException { 120 ByteBuffer buffer = ByteBuffer.allocate(8); 121 buffer.order(ByteOrder.LITTLE_ENDIAN); 122 buffer.putInt((int) crc.getValue()); 123 buffer.putInt(deflater.getTotalIn()); 124 125 out.write(buffer.array()); 126 } 127 128 @Override 129 public void write(int b) throws IOException { 130 write(new byte[]{(byte) (b & 0xff)}, 0, 1); 131 } 132 133 /** 134 * {@inheritDoc} 135 * 136 * @since 1.1 137 */ 138 @Override 139 public void write(byte[] buffer) throws IOException { 140 write(buffer, 0, buffer.length); 141 } 142 143 /** 144 * {@inheritDoc} 145 * 146 * @since 1.1 147 */ 148 @Override 149 public void write(byte[] buffer, int offset, int length) throws IOException { 150 if (deflater.finished()) { 151 throw new IOException("Cannot write more data, the end of the compressed data stream has been reached"); 152 153 } else if (length > 0) { 154 deflater.setInput(buffer, offset, length); 155 156 while (!deflater.needsInput()) { 157 deflate(); 158 } 159 160 crc.update(buffer, offset, length); 161 } 162 } 163 164 private void deflate() throws IOException { 165 int length = deflater.deflate(deflateBuffer, 0, deflateBuffer.length); 166 if (length > 0) { 167 out.write(deflateBuffer, 0, length); 168 } 169 } 170 171 /** 172 * Finishes writing compressed data to the underlying stream without closing it. 173 * 174 * @since 1.7 175 */ 176 public void finish() throws IOException { 177 if (!deflater.finished()) { 178 deflater.finish(); 179 180 while (!deflater.finished()) { 181 deflate(); 182 } 183 184 writeTrailer(); 185 } 186 } 187 188 /** 189 * {@inheritDoc} 190 * 191 * @since 1.7 192 */ 193 @Override 194 public void flush() throws IOException { 195 out.flush(); 196 } 197 198 @Override 199 public void close() throws IOException { 200 if (!closed) { 201 finish(); 202 deflater.end(); 203 out.close(); 204 closed = true; 205 } 206 } 207 208}