001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 * 017 */ 018package org.apache.commons.compress.archivers.zip; 019 020import org.apache.commons.compress.parallel.ScatterGatherBackingStore; 021 022import java.io.Closeable; 023import java.io.DataOutput; 024import java.io.IOException; 025import java.io.InputStream; 026import java.io.OutputStream; 027import java.util.zip.CRC32; 028import java.util.zip.Deflater; 029import java.util.zip.ZipEntry; 030 031/** 032 * Encapsulates a {@link Deflater} and crc calculator, handling multiple types of output streams. 033 * Currently {@link java.util.zip.ZipEntry#DEFLATED} and {@link java.util.zip.ZipEntry#STORED} are the only 034 * supported compression methods. 035 * 036 * @since 1.10 037 */ 038public abstract class StreamCompressor implements Closeable { 039 040 /* 041 * Apparently Deflater.setInput gets slowed down a lot on Sun JVMs 042 * when it gets handed a really big buffer. See 043 * https://issues.apache.org/bugzilla/show_bug.cgi?id=45396 044 * 045 * Using a buffer size of 8 kB proved to be a good compromise 046 */ 047 private static final int DEFLATER_BLOCK_SIZE = 8192; 048 049 private final Deflater def; 050 051 private final CRC32 crc = new CRC32(); 052 053 private long writtenToOutputStreamForLastEntry = 0; 054 private long sourcePayloadLength = 0; 055 private long totalWrittenToOutputStream = 0; 056 057 private static final int bufferSize = 4096; 058 private final byte[] outputBuffer = new byte[bufferSize]; 059 private final byte[] readerBuf = new byte[bufferSize]; 060 061 StreamCompressor(final Deflater deflater) { 062 this.def = deflater; 063 } 064 065 /** 066 * Create a stream compressor with the given compression level. 067 * 068 * @param os The stream to receive output 069 * @param deflater The deflater to use 070 * @return A stream compressor 071 */ 072 static StreamCompressor create(final OutputStream os, final Deflater deflater) { 073 return new OutputStreamCompressor(deflater, os); 074 } 075 076 /** 077 * Create a stream compressor with the default compression level. 078 * 079 * @param os The stream to receive output 080 * @return A stream compressor 081 */ 082 static StreamCompressor create(final OutputStream os) { 083 return create(os, new Deflater(Deflater.DEFAULT_COMPRESSION, true)); 084 } 085 086 /** 087 * Create a stream compressor with the given compression level. 088 * 089 * @param os The DataOutput to receive output 090 * @param deflater The deflater to use for the compressor 091 * @return A stream compressor 092 */ 093 static StreamCompressor create(final DataOutput os, final Deflater deflater) { 094 return new DataOutputCompressor(deflater, os); 095 } 096 097 /** 098 * Create a stream compressor with the given compression level. 099 * 100 * @param compressionLevel The {@link Deflater} compression level 101 * @param bs The ScatterGatherBackingStore to receive output 102 * @return A stream compressor 103 */ 104 public static StreamCompressor create(final int compressionLevel, final ScatterGatherBackingStore bs) { 105 final Deflater deflater = new Deflater(compressionLevel, true); 106 return new ScatterGatherBackingStoreCompressor(deflater, bs); 107 } 108 109 /** 110 * Create a stream compressor with the default compression level. 111 * 112 * @param bs The ScatterGatherBackingStore to receive output 113 * @return A stream compressor 114 */ 115 public static StreamCompressor create(final ScatterGatherBackingStore bs) { 116 return create(Deflater.DEFAULT_COMPRESSION, bs); 117 } 118 119 /** 120 * The crc32 of the last deflated file 121 * 122 * @return the crc32 123 */ 124 125 public long getCrc32() { 126 return crc.getValue(); 127 } 128 129 /** 130 * Return the number of bytes read from the source stream 131 * 132 * @return The number of bytes read, never negative 133 */ 134 public long getBytesRead() { 135 return sourcePayloadLength; 136 } 137 138 /** 139 * The number of bytes written to the output for the last entry 140 * 141 * @return The number of bytes, never negative 142 */ 143 public long getBytesWrittenForLastEntry() { 144 return writtenToOutputStreamForLastEntry; 145 } 146 147 /** 148 * The total number of bytes written to the output for all files 149 * 150 * @return The number of bytes, never negative 151 */ 152 public long getTotalBytesWritten() { 153 return totalWrittenToOutputStream; 154 } 155 156 157 /** 158 * Deflate the given source using the supplied compression method 159 * 160 * @param source The source to compress 161 * @param method The #ZipArchiveEntry compression method 162 * @throws IOException When failures happen 163 */ 164 165 public void deflate(final InputStream source, final int method) throws IOException { 166 reset(); 167 int length; 168 169 while ((length = source.read(readerBuf, 0, readerBuf.length)) >= 0) { 170 write(readerBuf, 0, length, method); 171 } 172 if (method == ZipEntry.DEFLATED) { 173 flushDeflater(); 174 } 175 } 176 177 /** 178 * Writes bytes to ZIP entry. 179 * 180 * @param b the byte array to write 181 * @param offset the start position to write from 182 * @param length the number of bytes to write 183 * @param method the comrpession method to use 184 * @return the number of bytes written to the stream this time 185 * @throws IOException on error 186 */ 187 long write(final byte[] b, final int offset, final int length, final int method) throws IOException { 188 final long current = writtenToOutputStreamForLastEntry; 189 crc.update(b, offset, length); 190 if (method == ZipEntry.DEFLATED) { 191 writeDeflated(b, offset, length); 192 } else { 193 writeCounted(b, offset, length); 194 } 195 sourcePayloadLength += length; 196 return writtenToOutputStreamForLastEntry - current; 197 } 198 199 200 void reset() { 201 crc.reset(); 202 def.reset(); 203 sourcePayloadLength = 0; 204 writtenToOutputStreamForLastEntry = 0; 205 } 206 207 @Override 208 public void close() throws IOException { 209 def.end(); 210 } 211 212 void flushDeflater() throws IOException { 213 def.finish(); 214 while (!def.finished()) { 215 deflate(); 216 } 217 } 218 219 private void writeDeflated(final byte[] b, final int offset, final int length) 220 throws IOException { 221 if (length > 0 && !def.finished()) { 222 if (length <= DEFLATER_BLOCK_SIZE) { 223 def.setInput(b, offset, length); 224 deflateUntilInputIsNeeded(); 225 } else { 226 final int fullblocks = length / DEFLATER_BLOCK_SIZE; 227 for (int i = 0; i < fullblocks; i++) { 228 def.setInput(b, offset + i * DEFLATER_BLOCK_SIZE, 229 DEFLATER_BLOCK_SIZE); 230 deflateUntilInputIsNeeded(); 231 } 232 final int done = fullblocks * DEFLATER_BLOCK_SIZE; 233 if (done < length) { 234 def.setInput(b, offset + done, length - done); 235 deflateUntilInputIsNeeded(); 236 } 237 } 238 } 239 } 240 241 private void deflateUntilInputIsNeeded() throws IOException { 242 while (!def.needsInput()) { 243 deflate(); 244 } 245 } 246 247 void deflate() throws IOException { 248 final int len = def.deflate(outputBuffer, 0, outputBuffer.length); 249 if (len > 0) { 250 writeCounted(outputBuffer, 0, len); 251 } 252 } 253 254 public void writeCounted(final byte[] data) throws IOException { 255 writeCounted(data, 0, data.length); 256 } 257 258 public void writeCounted(final byte[] data, final int offset, final int length) throws IOException { 259 writeOut(data, offset, length); 260 writtenToOutputStreamForLastEntry += length; 261 totalWrittenToOutputStream += length; 262 } 263 264 protected abstract void writeOut(byte[] data, int offset, int length) throws IOException; 265 266 private static final class ScatterGatherBackingStoreCompressor extends StreamCompressor { 267 private final ScatterGatherBackingStore bs; 268 269 public ScatterGatherBackingStoreCompressor(final Deflater deflater, final ScatterGatherBackingStore bs) { 270 super(deflater); 271 this.bs = bs; 272 } 273 274 @Override 275 protected final void writeOut(final byte[] data, final int offset, final int length) 276 throws IOException { 277 bs.writeOut(data, offset, length); 278 } 279 } 280 281 private static final class OutputStreamCompressor extends StreamCompressor { 282 private final OutputStream os; 283 284 public OutputStreamCompressor(final Deflater deflater, final OutputStream os) { 285 super(deflater); 286 this.os = os; 287 } 288 289 @Override 290 protected final void writeOut(final byte[] data, final int offset, final int length) 291 throws IOException { 292 os.write(data, offset, length); 293 } 294 } 295 296 private static final class DataOutputCompressor extends StreamCompressor { 297 private final DataOutput raf; 298 299 public DataOutputCompressor(final Deflater deflater, final DataOutput raf) { 300 super(deflater); 301 this.raf = raf; 302 } 303 304 @Override 305 protected final void writeOut(final byte[] data, final int offset, final int length) 306 throws IOException { 307 raf.write(data, offset, length); 308 } 309 } 310}