COMPRESS-327 write zip archives to arbitrary SeekableByteChannels
Project: http://git-wip-us.apache.org/repos/asf/commons-compress/repo Commit: http://git-wip-us.apache.org/repos/asf/commons-compress/commit/7e35f57a Tree: http://git-wip-us.apache.org/repos/asf/commons-compress/tree/7e35f57a Diff: http://git-wip-us.apache.org/repos/asf/commons-compress/diff/7e35f57a Branch: refs/heads/master Commit: 7e35f57ae9ba9e716da122dfa0bec418b1e2b6e8 Parents: dec527d Author: Stefan Bodewig <bode...@apache.org> Authored: Wed Oct 12 19:01:00 2016 +0200 Committer: Stefan Bodewig <bode...@apache.org> Committed: Wed Oct 12 19:01:00 2016 +0200 ---------------------------------------------------------------------- .../archivers/zip/StreamCompressor.java | 30 +++++++ .../archivers/zip/ZipArchiveOutputStream.java | 91 ++++++++++++-------- 2 files changed, 86 insertions(+), 35 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/commons-compress/blob/7e35f57a/src/main/java/org/apache/commons/compress/archivers/zip/StreamCompressor.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/StreamCompressor.java b/src/main/java/org/apache/commons/compress/archivers/zip/StreamCompressor.java index e54781e..218f54a 100644 --- a/src/main/java/org/apache/commons/compress/archivers/zip/StreamCompressor.java +++ b/src/main/java/org/apache/commons/compress/archivers/zip/StreamCompressor.java @@ -24,6 +24,8 @@ import java.io.DataOutput; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.SeekableByteChannel; import java.util.zip.CRC32; import java.util.zip.Deflater; import java.util.zip.ZipEntry; @@ -97,6 +99,18 @@ public abstract class StreamCompressor implements Closeable { /** * Create a stream compressor with the given compression level. * + * @param os The SeekableByteChannel to receive output + * @param deflater The deflater to use for the compressor + * @return A stream compressor + * @since 1.13 + */ + static StreamCompressor create(final SeekableByteChannel os, final Deflater deflater) { + return new SeekableByteChannelCompressor(deflater, os); + } + + /** + * Create a stream compressor with the given compression level. + * * @param compressionLevel The {@link Deflater} compression level * @param bs The ScatterGatherBackingStore to receive output * @return A stream compressor @@ -307,4 +321,20 @@ public abstract class StreamCompressor implements Closeable { raf.write(data, offset, length); } } + + private static final class SeekableByteChannelCompressor extends StreamCompressor { + private final SeekableByteChannel channel; + + public SeekableByteChannelCompressor(final Deflater deflater, + final SeekableByteChannel channel) { + super(deflater); + this.channel = channel; + } + + @Override + protected final void writeOut(final byte[] data, final int offset, final int length) + throws IOException { + channel.write(ByteBuffer.wrap(data, offset, length)); + } + } } http://git-wip-us.apache.org/repos/asf/commons-compress/blob/7e35f57a/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveOutputStream.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveOutputStream.java b/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveOutputStream.java index a184d97..22e924e 100644 --- a/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveOutputStream.java +++ b/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveOutputStream.java @@ -23,9 +23,12 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.RandomAccessFile; import java.nio.ByteBuffer; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; import java.util.Calendar; +import java.util.EnumSet; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -55,11 +58,11 @@ import static org.apache.commons.compress.archivers.zip.ZipShort.putShort; * attributes and extra fields with different layouts for local file * data and central directory entries. * - * <p>This class will try to use {@link java.io.RandomAccessFile - * RandomAccessFile} when you know that the output is going to go to a - * file.</p> + * <p>This class will try to use {@link + * java.nio.channels.SeekableByteChannel} when you know that the + * output is going to go to a file.</p> * - * <p>If RandomAccessFile cannot be used, this implementation will use + * <p>If SeekableByteChannel cannot be used, this implementation will use * a Data Descriptor to store size and CRC information for {@link * #DEFLATED DEFLATED} entries, this means, you don't need to * calculate them yourself. Unfortunately this is not possible for @@ -71,7 +74,7 @@ import static org.apache.commons.compress.archivers.zip.ZipShort.putShort; * extensions and thus individual entries and archives larger than 4 * GB or with more than 65536 entries in most cases but explicit * control is provided via {@link #setUseZip64}. If the stream can not - * user RandomAccessFile and you try to write a ZipArchiveEntry of + * use SeekableByteChannel and you try to write a ZipArchiveEntry of * unknown size then Zip64 extensions will be disabled by default.</p> * * @NotThreadSafe @@ -230,7 +233,7 @@ public class ZipArchiveOutputStream extends ArchiveOutputStream { /** * Optional random access output. */ - private final RandomAccessFile raf; + private final SeekableByteChannel channel; private final OutputStream out; @@ -268,7 +271,7 @@ public class ZipArchiveOutputStream extends ArchiveOutputStream { */ public ZipArchiveOutputStream(final OutputStream out) { this.out = out; - this.raf = null; + this.channel = null; def = new Deflater(level, true); streamCompressor = StreamCompressor.create(out, def); } @@ -280,20 +283,38 @@ public class ZipArchiveOutputStream extends ArchiveOutputStream { * @throws IOException on error */ public ZipArchiveOutputStream(final File file) throws IOException { + def = new Deflater(level, true); OutputStream o = null; - RandomAccessFile _raf = null; + SeekableByteChannel _channel = null; + StreamCompressor _streamCompressor = null; try { - _raf = new RandomAccessFile(file, "rw"); - _raf.setLength(0); + _channel = Files.newByteChannel(file.toPath(), + EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE, + StandardOpenOption.READ, + StandardOpenOption.TRUNCATE_EXISTING)); + _streamCompressor = StreamCompressor.create(_channel, def); } catch (final IOException e) { - IOUtils.closeQuietly(_raf); - _raf = null; + IOUtils.closeQuietly(_channel); + _channel = null; o = new FileOutputStream(file); + _streamCompressor = StreamCompressor.create(o, def); } - def = new Deflater(level, true); - streamCompressor = StreamCompressor.create(_raf, def); out = o; - raf = _raf; + channel = _channel; + streamCompressor = _streamCompressor; + } + + /** + * Creates a new ZIP OutputStream writing to a SeekableByteChannel. + * @param channel the channel to zip to + * @throws IOException on error + * @since 1.13 + */ + public ZipArchiveOutputStream(SeekableByteChannel channel) throws IOException { + this.channel = channel; + def = new Deflater(level, true); + streamCompressor = StreamCompressor.create(channel, def); + out = null; } /** @@ -306,7 +327,7 @@ public class ZipArchiveOutputStream extends ArchiveOutputStream { * @return true if seekable */ public boolean isSeekable() { - return raf != null; + return channel != null; } /** @@ -506,7 +527,7 @@ public class ZipArchiveOutputStream extends ArchiveOutputStream { } private void closeEntry(final boolean actuallyNeedsZip64, final boolean phased) throws IOException { - if (!phased && raf != null) { + if (!phased && channel != null) { rewriteSizesAndCrc(actuallyNeedsZip64); } @@ -585,7 +606,7 @@ public class ZipArchiveOutputStream extends ArchiveOutputStream { entry.entry.setCompressedSize(bytesWritten); entry.entry.setCrc(crc); - } else if (raf == null) { + } else if (channel == null) { if (entry.entry.getCrc() != crc) { throw new ZipException("bad CRC checksum for entry " + entry.entry.getName() + ": " @@ -640,9 +661,9 @@ public class ZipArchiveOutputStream extends ArchiveOutputStream { */ private void rewriteSizesAndCrc(final boolean actuallyNeedsZip64) throws IOException { - final long save = raf.getFilePointer(); + final long save = channel.position(); - raf.seek(entry.localDataStart); + channel.position(entry.localDataStart); writeOut(ZipLong.getBytes(entry.entry.getCrc())); if (!hasZip64Extra(entry.entry) || !actuallyNeedsZip64) { writeOut(ZipLong.getBytes(entry.entry.getCompressedSize())); @@ -656,8 +677,8 @@ public class ZipArchiveOutputStream extends ArchiveOutputStream { final ByteBuffer name = getName(entry.entry); final int nameLen = name.limit() - name.position(); // seek to ZIP64 extra, skip header and size information - raf.seek(entry.localDataStart + 3 * WORD + 2 * SHORT - + nameLen + 2 * SHORT); + channel.position(entry.localDataStart + 3 * WORD + 2 * SHORT + + nameLen + 2 * SHORT); // inside the ZIP64 extra uncompressed size comes // first, unlike the LFH, CD or data descriptor writeOut(ZipEightByteInteger.getBytes(entry.entry.getSize())); @@ -666,7 +687,7 @@ public class ZipArchiveOutputStream extends ArchiveOutputStream { if (!actuallyNeedsZip64) { // do some cleanup: // * rewrite version needed to extract - raf.seek(entry.localDataStart - 5 * SHORT); + channel.position(entry.localDataStart - 5 * SHORT); writeOut(ZipShort.getBytes(INITIAL_VERSION)); // * remove ZIP64 extra so it doesn't get written @@ -682,7 +703,7 @@ public class ZipArchiveOutputStream extends ArchiveOutputStream { } } } - raf.seek(save); + channel.position(save); } /** @@ -778,7 +799,7 @@ public class ZipArchiveOutputStream extends ArchiveOutputStream { private void validateSizeInformation(final Zip64Mode effectiveMode) throws ZipException { // Size/CRC not required if RandomAccessFile is used - if (entry.entry.getMethod() == STORED && raf == null) { + if (entry.entry.getMethod() == STORED && channel == null) { if (entry.entry.getSize() == ArchiveEntry.SIZE_UNKNOWN) { throw new ZipException("uncompressed size is required for" + " STORED method when not writing to a" @@ -818,7 +839,7 @@ public class ZipArchiveOutputStream extends ArchiveOutputStream { || entry.getSize() >= ZIP64_MAGIC || entry.getCompressedSize() >= ZIP64_MAGIC || (entry.getSize() == ArchiveEntry.SIZE_UNKNOWN - && raf != null && mode != Zip64Mode.Never); + && channel != null && mode != Zip64Mode.Never); } /** @@ -1036,7 +1057,7 @@ public class ZipArchiveOutputStream extends ArchiveOutputStream { // CRC if (phased){ putLong(ze.getCrc(), buf, LFH_CRC_OFFSET); - } else if (zipMethod == DEFLATED || raf != null) { + } else if (zipMethod == DEFLATED || channel != null) { System.arraycopy(LZERO, 0, buf, LFH_CRC_OFFSET, WORD); } else { putLong(ze.getCrc(), buf, LFH_CRC_OFFSET); @@ -1053,7 +1074,7 @@ public class ZipArchiveOutputStream extends ArchiveOutputStream { } else if (phased) { putLong(ze.getCompressedSize(), buf, LFH_COMPRESSED_SIZE_OFFSET); putLong(ze.getSize(), buf, LFH_ORIGINAL_SIZE_OFFSET); - } else if (zipMethod == DEFLATED || raf != null) { + } else if (zipMethod == DEFLATED || channel != null) { System.arraycopy(LZERO, 0, buf, LFH_COMPRESSED_SIZE_OFFSET, WORD); System.arraycopy(LZERO, 0, buf, LFH_ORIGINAL_SIZE_OFFSET, WORD); } else { // Stored @@ -1115,7 +1136,7 @@ public class ZipArchiveOutputStream extends ArchiveOutputStream { * @throws IOException on error */ protected void writeDataDescriptor(final ZipArchiveEntry ze) throws IOException { - if (ze.getMethod() != DEFLATED || raf != null) { + if (ze.getMethod() != DEFLATED || channel != null) { return; } writeCounted(DD_SIG); @@ -1436,7 +1457,7 @@ public class ZipArchiveOutputStream extends ArchiveOutputStream { } private boolean isDeflatedToOutputStream(final int zipMethod) { - return zipMethod == DEFLATED && raf == null; + return zipMethod == DEFLATED && channel == null; } @@ -1481,7 +1502,7 @@ public class ZipArchiveOutputStream extends ArchiveOutputStream { System.err.println("Adding z64 for " + ze.getName() + ", method: " + ze.getMethod() + " (" + (ze.getMethod() == STORED) + ")" - + ", raf: " + (raf != null)); + + ", channel: " + (channel != null)); */ z64 = new Zip64ExtendedInformationExtraField(); } @@ -1513,7 +1534,7 @@ public class ZipArchiveOutputStream extends ArchiveOutputStream { */ private Zip64Mode getEffectiveZip64Mode(final ZipArchiveEntry ze) { if (zip64Mode != Zip64Mode.AsNeeded - || raf != null + || channel != null || ze.getMethod() != DEFLATED || ze.getSize() != ArchiveEntry.SIZE_UNKNOWN) { return zip64Mode; @@ -1539,8 +1560,8 @@ public class ZipArchiveOutputStream extends ArchiveOutputStream { * corrupt archives so they can clean up any temporary files.</p> */ void destroy() throws IOException { - if (raf != null) { - raf.close(); + if (channel != null) { + channel.close(); } if (out != null) { out.close();