1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 package org.apache.commons.compress.archivers.zip; 19 20 import org.apache.commons.compress.parallel.ScatterGatherBackingStore; 21 22 import java.io.Closeable; 23 import java.io.DataOutput; 24 import java.io.IOException; 25 import java.io.InputStream; 26 import java.io.OutputStream; 27 import java.nio.ByteBuffer; 28 import java.nio.channels.SeekableByteChannel; 29 import java.util.zip.CRC32; 30 import java.util.zip.Deflater; 31 import java.util.zip.ZipEntry; 32 33 /** 34 * Encapsulates a {@link Deflater} and crc calculator, handling multiple types of output streams. 35 * Currently {@link java.util.zip.ZipEntry#DEFLATED} and {@link java.util.zip.ZipEntry#STORED} are the only 36 * supported compression methods. 37 * 38 * @since 1.10 39 */ 40 public abstract class StreamCompressor implements Closeable { 41 42 /* 43 * Apparently Deflater.setInput gets slowed down a lot on Sun JVMs 44 * when it gets handed a really big buffer. See 45 * https://issues.apache.org/bugzilla/show_bug.cgi?id=45396 46 * 47 * Using a buffer size of 8 kB proved to be a good compromise 48 */ 49 private static final int DEFLATER_BLOCK_SIZE = 8192; 50 51 private final Deflater def; 52 53 private final CRC32 crc = new CRC32(); 54 55 private long writtenToOutputStreamForLastEntry = 0; 56 private long sourcePayloadLength = 0; 57 private long totalWrittenToOutputStream = 0; 58 59 private static final int BUFFER_SIZE = 4096; 60 private final byte[] outputBuffer = new byte[BUFFER_SIZE]; 61 private final byte[] readerBuf = new byte[BUFFER_SIZE]; 62 StreamCompressor(final Deflater deflater)63 StreamCompressor(final Deflater deflater) { 64 this.def = deflater; 65 } 66 67 /** 68 * Create a stream compressor with the given compression level. 69 * 70 * @param os The stream to receive output 71 * @param deflater The deflater to use 72 * @return A stream compressor 73 */ create(final OutputStream os, final Deflater deflater)74 static StreamCompressor create(final OutputStream os, final Deflater deflater) { 75 return new OutputStreamCompressor(deflater, os); 76 } 77 78 /** 79 * Create a stream compressor with the default compression level. 80 * 81 * @param os The stream to receive output 82 * @return A stream compressor 83 */ create(final OutputStream os)84 static StreamCompressor create(final OutputStream os) { 85 return create(os, new Deflater(Deflater.DEFAULT_COMPRESSION, true)); 86 } 87 88 /** 89 * Create a stream compressor with the given compression level. 90 * 91 * @param os The DataOutput to receive output 92 * @param deflater The deflater to use for the compressor 93 * @return A stream compressor 94 */ create(final DataOutput os, final Deflater deflater)95 static StreamCompressor create(final DataOutput os, final Deflater deflater) { 96 return new DataOutputCompressor(deflater, os); 97 } 98 99 /** 100 * Create a stream compressor with the given compression level. 101 * 102 * @param os The SeekableByteChannel to receive output 103 * @param deflater The deflater to use for the compressor 104 * @return A stream compressor 105 * @since 1.13 106 */ create(final SeekableByteChannel os, final Deflater deflater)107 static StreamCompressor create(final SeekableByteChannel os, final Deflater deflater) { 108 return new SeekableByteChannelCompressor(deflater, os); 109 } 110 111 /** 112 * Create a stream compressor with the given compression level. 113 * 114 * @param compressionLevel The {@link Deflater} compression level 115 * @param bs The ScatterGatherBackingStore to receive output 116 * @return A stream compressor 117 */ create(final int compressionLevel, final ScatterGatherBackingStore bs)118 public static StreamCompressor create(final int compressionLevel, final ScatterGatherBackingStore bs) { 119 final Deflater deflater = new Deflater(compressionLevel, true); 120 return new ScatterGatherBackingStoreCompressor(deflater, bs); 121 } 122 123 /** 124 * Create a stream compressor with the default compression level. 125 * 126 * @param bs The ScatterGatherBackingStore to receive output 127 * @return A stream compressor 128 */ create(final ScatterGatherBackingStore bs)129 public static StreamCompressor create(final ScatterGatherBackingStore bs) { 130 return create(Deflater.DEFAULT_COMPRESSION, bs); 131 } 132 133 /** 134 * The crc32 of the last deflated file 135 * 136 * @return the crc32 137 */ 138 getCrc32()139 public long getCrc32() { 140 return crc.getValue(); 141 } 142 143 /** 144 * Return the number of bytes read from the source stream 145 * 146 * @return The number of bytes read, never negative 147 */ getBytesRead()148 public long getBytesRead() { 149 return sourcePayloadLength; 150 } 151 152 /** 153 * The number of bytes written to the output for the last entry 154 * 155 * @return The number of bytes, never negative 156 */ getBytesWrittenForLastEntry()157 public long getBytesWrittenForLastEntry() { 158 return writtenToOutputStreamForLastEntry; 159 } 160 161 /** 162 * The total number of bytes written to the output for all files 163 * 164 * @return The number of bytes, never negative 165 */ getTotalBytesWritten()166 public long getTotalBytesWritten() { 167 return totalWrittenToOutputStream; 168 } 169 170 171 /** 172 * Deflate the given source using the supplied compression method 173 * 174 * @param source The source to compress 175 * @param method The #ZipArchiveEntry compression method 176 * @throws IOException When failures happen 177 */ 178 deflate(final InputStream source, final int method)179 public void deflate(final InputStream source, final int method) throws IOException { 180 reset(); 181 int length; 182 183 while ((length = source.read(readerBuf, 0, readerBuf.length)) >= 0) { 184 write(readerBuf, 0, length, method); 185 } 186 if (method == ZipEntry.DEFLATED) { 187 flushDeflater(); 188 } 189 } 190 191 /** 192 * Writes bytes to ZIP entry. 193 * 194 * @param b the byte array to write 195 * @param offset the start position to write from 196 * @param length the number of bytes to write 197 * @param method the comrpession method to use 198 * @return the number of bytes written to the stream this time 199 * @throws IOException on error 200 */ write(final byte[] b, final int offset, final int length, final int method)201 long write(final byte[] b, final int offset, final int length, final int method) throws IOException { 202 final long current = writtenToOutputStreamForLastEntry; 203 crc.update(b, offset, length); 204 if (method == ZipEntry.DEFLATED) { 205 writeDeflated(b, offset, length); 206 } else { 207 writeCounted(b, offset, length); 208 } 209 sourcePayloadLength += length; 210 return writtenToOutputStreamForLastEntry - current; 211 } 212 213 reset()214 void reset() { 215 crc.reset(); 216 def.reset(); 217 sourcePayloadLength = 0; 218 writtenToOutputStreamForLastEntry = 0; 219 } 220 221 @Override close()222 public void close() throws IOException { 223 def.end(); 224 } 225 flushDeflater()226 void flushDeflater() throws IOException { 227 def.finish(); 228 while (!def.finished()) { 229 deflate(); 230 } 231 } 232 writeDeflated(final byte[] b, final int offset, final int length)233 private void writeDeflated(final byte[] b, final int offset, final int length) 234 throws IOException { 235 if (length > 0 && !def.finished()) { 236 if (length <= DEFLATER_BLOCK_SIZE) { 237 def.setInput(b, offset, length); 238 deflateUntilInputIsNeeded(); 239 } else { 240 final int fullblocks = length / DEFLATER_BLOCK_SIZE; 241 for (int i = 0; i < fullblocks; i++) { 242 def.setInput(b, offset + i * DEFLATER_BLOCK_SIZE, 243 DEFLATER_BLOCK_SIZE); 244 deflateUntilInputIsNeeded(); 245 } 246 final int done = fullblocks * DEFLATER_BLOCK_SIZE; 247 if (done < length) { 248 def.setInput(b, offset + done, length - done); 249 deflateUntilInputIsNeeded(); 250 } 251 } 252 } 253 } 254 deflateUntilInputIsNeeded()255 private void deflateUntilInputIsNeeded() throws IOException { 256 while (!def.needsInput()) { 257 deflate(); 258 } 259 } 260 deflate()261 void deflate() throws IOException { 262 final int len = def.deflate(outputBuffer, 0, outputBuffer.length); 263 if (len > 0) { 264 writeCounted(outputBuffer, 0, len); 265 } 266 } 267 writeCounted(final byte[] data)268 public void writeCounted(final byte[] data) throws IOException { 269 writeCounted(data, 0, data.length); 270 } 271 writeCounted(final byte[] data, final int offset, final int length)272 public void writeCounted(final byte[] data, final int offset, final int length) throws IOException { 273 writeOut(data, offset, length); 274 writtenToOutputStreamForLastEntry += length; 275 totalWrittenToOutputStream += length; 276 } 277 writeOut(byte[] data, int offset, int length)278 protected abstract void writeOut(byte[] data, int offset, int length) throws IOException; 279 280 private static final class ScatterGatherBackingStoreCompressor extends StreamCompressor { 281 private final ScatterGatherBackingStore bs; 282 ScatterGatherBackingStoreCompressor(final Deflater deflater, final ScatterGatherBackingStore bs)283 public ScatterGatherBackingStoreCompressor(final Deflater deflater, final ScatterGatherBackingStore bs) { 284 super(deflater); 285 this.bs = bs; 286 } 287 288 @Override writeOut(final byte[] data, final int offset, final int length)289 protected final void writeOut(final byte[] data, final int offset, final int length) 290 throws IOException { 291 bs.writeOut(data, offset, length); 292 } 293 } 294 295 private static final class OutputStreamCompressor extends StreamCompressor { 296 private final OutputStream os; 297 OutputStreamCompressor(final Deflater deflater, final OutputStream os)298 public OutputStreamCompressor(final Deflater deflater, final OutputStream os) { 299 super(deflater); 300 this.os = os; 301 } 302 303 @Override writeOut(final byte[] data, final int offset, final int length)304 protected final void writeOut(final byte[] data, final int offset, final int length) 305 throws IOException { 306 os.write(data, offset, length); 307 } 308 } 309 310 private static final class DataOutputCompressor extends StreamCompressor { 311 private final DataOutput raf; 312 DataOutputCompressor(final Deflater deflater, final DataOutput raf)313 public DataOutputCompressor(final Deflater deflater, final DataOutput raf) { 314 super(deflater); 315 this.raf = raf; 316 } 317 318 @Override writeOut(final byte[] data, final int offset, final int length)319 protected final void writeOut(final byte[] data, final int offset, final int length) 320 throws IOException { 321 raf.write(data, offset, length); 322 } 323 } 324 325 private static final class SeekableByteChannelCompressor extends StreamCompressor { 326 private final SeekableByteChannel channel; 327 SeekableByteChannelCompressor(final Deflater deflater, final SeekableByteChannel channel)328 public SeekableByteChannelCompressor(final Deflater deflater, 329 final SeekableByteChannel channel) { 330 super(deflater); 331 this.channel = channel; 332 } 333 334 @Override writeOut(final byte[] data, final int offset, final int length)335 protected final void writeOut(final byte[] data, final int offset, final int length) 336 throws IOException { 337 channel.write(ByteBuffer.wrap(data, offset, length)); 338 } 339 } 340 } 341