blob: 21be4369b41c298c118e63aba709ff3d192815f9 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.commons.compress.archivers.sevenz;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.SeekableByteChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.zip.CRC32;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.utils.CountingOutputStream;
/**
* Writes a 7z file.
* @since 1.6
*/
public class SevenZOutputFile implements Closeable {
private final SeekableByteChannel channel;
private final List<SevenZArchiveEntry> files = new ArrayList<>();
private int numNonEmptyStreams;
private final CRC32 crc32 = new CRC32();
private final CRC32 compressedCrc32 = new CRC32();
private long fileBytesWritten;
private boolean finished;
private CountingOutputStream currentOutputStream;
private CountingOutputStream[] additionalCountingStreams;
private Iterable<? extends SevenZMethodConfiguration> contentMethods =
Collections.singletonList(new SevenZMethodConfiguration(SevenZMethod.LZMA2));
private final Map<SevenZArchiveEntry, long[]> additionalSizes = new HashMap<>();
/**
* Opens file to write a 7z archive to.
*
* @param fileName the file to write to
* @throws IOException if opening the file fails
*/
public SevenZOutputFile(final File fileName) throws IOException {
this(Files.newByteChannel(fileName.toPath(),
EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING)));
}
/**
* Prepares channel to write a 7z archive to.
*
* <p>{@link
* org.apache.commons.compress.utils.SeekableInMemoryByteChannel}
* allows you to write to an in-memory archive.</p>
*
* @param channel the channel to write to
* @throws IOException if the channel cannot be positioned properly
* @since 1.13
*/
public SevenZOutputFile(final SeekableByteChannel channel) throws IOException {
this.channel = channel;
channel.position(SevenZFile.SIGNATURE_HEADER_SIZE);
}
/**
* Sets the default compression method to use for entry contents - the
* default is LZMA2.
*
* <p>Currently only {@link SevenZMethod#COPY}, {@link
* SevenZMethod#LZMA2}, {@link SevenZMethod#BZIP2} and {@link
* SevenZMethod#DEFLATE} are supported.</p>
*
* <p>This is a short form for passing a single-element iterable
* to {@link #setContentMethods}.</p>
* @param method the default compression method
*/
public void setContentCompression(final SevenZMethod method) {
setContentMethods(Collections.singletonList(new SevenZMethodConfiguration(method)));
}
/**
* Sets the default (compression) methods to use for entry contents - the
* default is LZMA2.
*
* <p>Currently only {@link SevenZMethod#COPY}, {@link
* SevenZMethod#LZMA2}, {@link SevenZMethod#BZIP2} and {@link
* SevenZMethod#DEFLATE} are supported.</p>
*
* <p>The methods will be consulted in iteration order to create
* the final output.</p>
*
* @since 1.8
* @param methods the default (compression) methods
*/
public void setContentMethods(final Iterable<? extends SevenZMethodConfiguration> methods) {
this.contentMethods = reverse(methods);
}
/**
* Closes the archive, calling {@link #finish} if necessary.
*
* @throws IOException on error
*/
@Override
public void close() throws IOException {
try {
if (!finished) {
finish();
}
} finally {
channel.close();
}
}
/**
* Create an archive entry using the inputFile and entryName provided.
*
* @param inputFile file to create an entry from
* @param entryName the name to use
* @return the ArchiveEntry set up with details from the file
*
* @throws IOException on error
*/
public SevenZArchiveEntry createArchiveEntry(final File inputFile,
final String entryName) throws IOException {
final SevenZArchiveEntry entry = new SevenZArchiveEntry();
entry.setDirectory(inputFile.isDirectory());
entry.setName(entryName);
entry.setLastModifiedDate(new Date(inputFile.lastModified()));
return entry;
}
/**
* Create an archive entry using the inputPath and entryName provided.
*
* @param inputPath path to create an entry from
* @param entryName the name to use
* @param options options indicating how symbolic links are handled.
* @return the ArchiveEntry set up with details from the file
*
* @throws IOException on error
* @since 1.21
*/
public SevenZArchiveEntry createArchiveEntry(final Path inputPath,
final String entryName, final LinkOption... options) throws IOException {
final SevenZArchiveEntry entry = new SevenZArchiveEntry();
entry.setDirectory(Files.isDirectory(inputPath, options));
entry.setName(entryName);
entry.setLastModifiedDate(new Date(Files.getLastModifiedTime(inputPath, options).toMillis()));
return entry;
}
/**
* Records an archive entry to add.
*
* The caller must then write the content to the archive and call
* {@link #closeArchiveEntry()} to complete the process.
*
* @param archiveEntry describes the entry
* @throws IOException on error
*/
public void putArchiveEntry(final ArchiveEntry archiveEntry) throws IOException {
final SevenZArchiveEntry entry = (SevenZArchiveEntry) archiveEntry;
files.add(entry);
}
/**
* Closes the archive entry.
* @throws IOException on error
*/
public void closeArchiveEntry() throws IOException {
if (currentOutputStream != null) {
currentOutputStream.flush();
currentOutputStream.close();
}
final SevenZArchiveEntry entry = files.get(files.size() - 1);
if (fileBytesWritten > 0) { // this implies currentOutputStream != null
entry.setHasStream(true);
++numNonEmptyStreams;
entry.setSize(currentOutputStream.getBytesWritten()); //NOSONAR
entry.setCompressedSize(fileBytesWritten);
entry.setCrcValue(crc32.getValue());
entry.setCompressedCrcValue(compressedCrc32.getValue());
entry.setHasCrc(true);
if (additionalCountingStreams != null) {
final long[] sizes = new long[additionalCountingStreams.length];
for (int i = 0; i < additionalCountingStreams.length; i++) {
sizes[i] = additionalCountingStreams[i].getBytesWritten();
}
additionalSizes.put(entry, sizes);
}
} else {
entry.setHasStream(false);
entry.setSize(0);
entry.setCompressedSize(0);
entry.setHasCrc(false);
}
currentOutputStream = null;
additionalCountingStreams = null;
crc32.reset();
compressedCrc32.reset();
fileBytesWritten = 0;
}
/**
* Writes a byte to the current archive entry.
* @param b The byte to be written.
* @throws IOException on error
*/
public void write(final int b) throws IOException {
getCurrentOutputStream().write(b);
}
/**
* Writes a byte array to the current archive entry.
* @param b The byte array to be written.
* @throws IOException on error
*/
public void write(final byte[] b) throws IOException {
write(b, 0, b.length);
}
/**
* Writes part of a byte array to the current archive entry.
* @param b The byte array to be written.
* @param off offset into the array to start writing from
* @param len number of bytes to write
* @throws IOException on error
*/
public void write(final byte[] b, final int off, final int len) throws IOException {
if (len > 0) {
getCurrentOutputStream().write(b, off, len);
}
}
/**
* Writes all of the given input stream to the current archive entry.
* @param inputStream the data source.
* @throws IOException if an I/O error occurs.
* @since 1.21
*/
public void write(final InputStream inputStream) throws IOException {
final byte[] buffer = new byte[8024];
int n = 0;
while (-1 != (n = inputStream.read(buffer))) {
write(buffer, 0, n);
}
}
/**
* Writes all of the given input stream to the current archive entry.
* @param path the data source.
* @param options options specifying how the file is opened.
* @throws IOException if an I/O error occurs.
* @since 1.21
*/
public void write(final Path path, final OpenOption... options) throws IOException {
try (InputStream in = new BufferedInputStream(Files.newInputStream(path, options))) {
write(in);
}
}
/**
* Finishes the addition of entries to this archive, without closing it.
*
* @throws IOException if archive is already closed.
*/
public void finish() throws IOException {
if (finished) {
throw new IOException("This archive has already been finished");
}
finished = true;
final long headerPosition = channel.position();
final ByteArrayOutputStream headerBaos = new ByteArrayOutputStream();
final DataOutputStream header = new DataOutputStream(headerBaos);
writeHeader(header);
header.flush();
final byte[] headerBytes = headerBaos.toByteArray();
channel.write(ByteBuffer.wrap(headerBytes));
final CRC32 crc32 = new CRC32();
crc32.update(headerBytes);
final ByteBuffer bb = ByteBuffer.allocate(SevenZFile.sevenZSignature.length
+ 2 /* version */
+ 4 /* start header CRC */
+ 8 /* next header position */
+ 8 /* next header length */
+ 4 /* next header CRC */)
.order(ByteOrder.LITTLE_ENDIAN);
// signature header
channel.position(0);
bb.put(SevenZFile.sevenZSignature);
// version
bb.put((byte) 0).put((byte) 2);
// placeholder for start header CRC
bb.putInt(0);
// start header
bb.putLong(headerPosition - SevenZFile.SIGNATURE_HEADER_SIZE)
.putLong(0xffffFFFFL & headerBytes.length)
.putInt((int) crc32.getValue());
crc32.reset();
crc32.update(bb.array(), SevenZFile.sevenZSignature.length + 6, 20);
bb.putInt(SevenZFile.sevenZSignature.length + 2, (int) crc32.getValue());
bb.flip();
channel.write(bb);
}
/*
* Creation of output stream is deferred until data is actually
* written as some codecs might write header information even for
* empty streams and directories otherwise.
*/
private OutputStream getCurrentOutputStream() throws IOException {
if (currentOutputStream == null) {
currentOutputStream = setupFileOutputStream();
}
return currentOutputStream;
}
private CountingOutputStream setupFileOutputStream() throws IOException {
if (files.isEmpty()) {
throw new IllegalStateException("No current 7z entry");
}
// doesn't need to be closed, just wraps the instance field channel
OutputStream out = new OutputStreamWrapper(); // NOSONAR
final ArrayList<CountingOutputStream> moreStreams = new ArrayList<>();
boolean first = true;
for (final SevenZMethodConfiguration m : getContentMethods(files.get(files.size() - 1))) {
if (!first) {
final CountingOutputStream cos = new CountingOutputStream(out);
moreStreams.add(cos);
out = cos;
}
out = Coders.addEncoder(out, m.getMethod(), m.getOptions());
first = false;
}
if (!moreStreams.isEmpty()) {
additionalCountingStreams = moreStreams.toArray(new CountingOutputStream[0]);
}
return new CountingOutputStream(out) {
@Override
public void write(final int b) throws IOException {
super.write(b);
crc32.update(b);
}
@Override
public void write(final byte[] b) throws IOException {
super.write(b);
crc32.update(b);
}
@Override
public void write(final byte[] b, final int off, final int len)
throws IOException {
super.write(b, off, len);
crc32.update(b, off, len);
}
};
}
private Iterable<? extends SevenZMethodConfiguration> getContentMethods(final SevenZArchiveEntry entry) {
final Iterable<? extends SevenZMethodConfiguration> ms = entry.getContentMethods();
return ms == null ? contentMethods : ms;
}
private void writeHeader(final DataOutput header) throws IOException {
header.write(NID.kHeader);
header.write(NID.kMainStreamsInfo);
writeStreamsInfo(header);
writeFilesInfo(header);
header.write(NID.kEnd);
}
private void writeStreamsInfo(final DataOutput header) throws IOException {
if (numNonEmptyStreams > 0) {
writePackInfo(header);
writeUnpackInfo(header);
}
writeSubStreamsInfo(header);
header.write(NID.kEnd);
}
private void writePackInfo(final DataOutput header) throws IOException {
header.write(NID.kPackInfo);
writeUint64(header, 0);
writeUint64(header, 0xffffFFFFL & numNonEmptyStreams);
header.write(NID.kSize);
for (final SevenZArchiveEntry entry : files) {
if (entry.hasStream()) {
writeUint64(header, entry.getCompressedSize());
}
}
header.write(NID.kCRC);
header.write(1); // "allAreDefined" == true
for (final SevenZArchiveEntry entry : files) {
if (entry.hasStream()) {
header.writeInt(Integer.reverseBytes((int) entry.getCompressedCrcValue()));
}
}
header.write(NID.kEnd);
}
private void writeUnpackInfo(final DataOutput header) throws IOException {
header.write(NID.kUnpackInfo);
header.write(NID.kFolder);
writeUint64(header, numNonEmptyStreams);
header.write(0);
for (final SevenZArchiveEntry entry : files) {
if (entry.hasStream()) {
writeFolder(header, entry);
}
}
header.write(NID.kCodersUnpackSize);
for (final SevenZArchiveEntry entry : files) {
if (entry.hasStream()) {
final long[] moreSizes = additionalSizes.get(entry);
if (moreSizes != null) {
for (final long s : moreSizes) {
writeUint64(header, s);
}
}
writeUint64(header, entry.getSize());
}
}
header.write(NID.kCRC);
header.write(1); // "allAreDefined" == true
for (final SevenZArchiveEntry entry : files) {
if (entry.hasStream()) {
header.writeInt(Integer.reverseBytes((int) entry.getCrcValue()));
}
}
header.write(NID.kEnd);
}
private void writeFolder(final DataOutput header, final SevenZArchiveEntry entry) throws IOException {
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
int numCoders = 0;
for (final SevenZMethodConfiguration m : getContentMethods(entry)) {
numCoders++;
writeSingleCodec(m, bos);
}
writeUint64(header, numCoders);
header.write(bos.toByteArray());
for (long i = 0; i < numCoders - 1; i++) {
writeUint64(header, i + 1);
writeUint64(header, i);
}
}
private void writeSingleCodec(final SevenZMethodConfiguration m, final OutputStream bos) throws IOException {
final byte[] id = m.getMethod().getId();
final byte[] properties = Coders.findByMethod(m.getMethod())
.getOptionsAsProperties(m.getOptions());
int codecFlags = id.length;
if (properties.length > 0) {
codecFlags |= 0x20;
}
bos.write(codecFlags);
bos.write(id);
if (properties.length > 0) {
bos.write(properties.length);
bos.write(properties);
}
}
private void writeSubStreamsInfo(final DataOutput header) throws IOException {
header.write(NID.kSubStreamsInfo);
//
// header.write(NID.kCRC);
// header.write(1);
// for (final SevenZArchiveEntry entry : files) {
// if (entry.getHasCrc()) {
// header.writeInt(Integer.reverseBytes(entry.getCrc()));
// }
// }
//
header.write(NID.kEnd);
}
private void writeFilesInfo(final DataOutput header) throws IOException {
header.write(NID.kFilesInfo);
writeUint64(header, files.size());
writeFileEmptyStreams(header);
writeFileEmptyFiles(header);
writeFileAntiItems(header);
writeFileNames(header);
writeFileCTimes(header);
writeFileATimes(header);
writeFileMTimes(header);
writeFileWindowsAttributes(header);
header.write(NID.kEnd);
}
private void writeFileEmptyStreams(final DataOutput header) throws IOException {
boolean hasEmptyStreams = false;
for (final SevenZArchiveEntry entry : files) {
if (!entry.hasStream()) {
hasEmptyStreams = true;
break;
}
}
if (hasEmptyStreams) {
header.write(NID.kEmptyStream);
final BitSet emptyStreams = new BitSet(files.size());
for (int i = 0; i < files.size(); i++) {
emptyStreams.set(i, !files.get(i).hasStream());
}
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final DataOutputStream out = new DataOutputStream(baos);
writeBits(out, emptyStreams, files.size());
out.flush();
final byte[] contents = baos.toByteArray();
writeUint64(header, contents.length);
header.write(contents);
}
}
private void writeFileEmptyFiles(final DataOutput header) throws IOException {
boolean hasEmptyFiles = false;
int emptyStreamCounter = 0;
final BitSet emptyFiles = new BitSet(0);
for (final SevenZArchiveEntry file1 : files) {
if (!file1.hasStream()) {
final boolean isDir = file1.isDirectory();
emptyFiles.set(emptyStreamCounter++, !isDir);
hasEmptyFiles |= !isDir;
}
}
if (hasEmptyFiles) {
header.write(NID.kEmptyFile);
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final DataOutputStream out = new DataOutputStream(baos);
writeBits(out, emptyFiles, emptyStreamCounter);
out.flush();
final byte[] contents = baos.toByteArray();
writeUint64(header, contents.length);
header.write(contents);
}
}
private void writeFileAntiItems(final DataOutput header) throws IOException {
boolean hasAntiItems = false;
final BitSet antiItems = new BitSet(0);
int antiItemCounter = 0;
for (final SevenZArchiveEntry file1 : files) {
if (!file1.hasStream()) {
final boolean isAnti = file1.isAntiItem();
antiItems.set(antiItemCounter++, isAnti);
hasAntiItems |= isAnti;
}
}
if (hasAntiItems) {
header.write(NID.kAnti);
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final DataOutputStream out = new DataOutputStream(baos);
writeBits(out, antiItems, antiItemCounter);
out.flush();
final byte[] contents = baos.toByteArray();
writeUint64(header, contents.length);
header.write(contents);
}
}
private void writeFileNames(final DataOutput header) throws IOException {
header.write(NID.kName);
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final DataOutputStream out = new DataOutputStream(baos);
out.write(0);
for (final SevenZArchiveEntry entry : files) {
out.write(entry.getName().getBytes(StandardCharsets.UTF_16LE));
out.writeShort(0);
}
out.flush();
final byte[] contents = baos.toByteArray();
writeUint64(header, contents.length);
header.write(contents);
}
private void writeFileCTimes(final DataOutput header) throws IOException {
int numCreationDates = 0;
for (final SevenZArchiveEntry entry : files) {
if (entry.getHasCreationDate()) {
++numCreationDates;
}
}
if (numCreationDates > 0) {
header.write(NID.kCTime);
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final DataOutputStream out = new DataOutputStream(baos);
if (numCreationDates != files.size()) {
out.write(0);
final BitSet cTimes = new BitSet(files.size());
for (int i = 0; i < files.size(); i++) {
cTimes.set(i, files.get(i).getHasCreationDate());
}
writeBits(out, cTimes, files.size());
} else {
out.write(1); // "allAreDefined" == true
}
out.write(0);
for (final SevenZArchiveEntry entry : files) {
if (entry.getHasCreationDate()) {
out.writeLong(Long.reverseBytes(
SevenZArchiveEntry.javaTimeToNtfsTime(entry.getCreationDate())));
}
}
out.flush();
final byte[] contents = baos.toByteArray();
writeUint64(header, contents.length);
header.write(contents);
}
}
private void writeFileATimes(final DataOutput header) throws IOException {
int numAccessDates = 0;
for (final SevenZArchiveEntry entry : files) {
if (entry.getHasAccessDate()) {
++numAccessDates;
}
}
if (numAccessDates > 0) {
header.write(NID.kATime);
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final DataOutputStream out = new DataOutputStream(baos);
if (numAccessDates != files.size()) {
out.write(0);
final BitSet aTimes = new BitSet(files.size());
for (int i = 0; i < files.size(); i++) {
aTimes.set(i, files.get(i).getHasAccessDate());
}
writeBits(out, aTimes, files.size());
} else {
out.write(1); // "allAreDefined" == true
}
out.write(0);
for (final SevenZArchiveEntry entry : files) {
if (entry.getHasAccessDate()) {
out.writeLong(Long.reverseBytes(
SevenZArchiveEntry.javaTimeToNtfsTime(entry.getAccessDate())));
}
}
out.flush();
final byte[] contents = baos.toByteArray();
writeUint64(header, contents.length);
header.write(contents);
}
}
private void writeFileMTimes(final DataOutput header) throws IOException {
int numLastModifiedDates = 0;
for (final SevenZArchiveEntry entry : files) {
if (entry.getHasLastModifiedDate()) {
++numLastModifiedDates;
}
}
if (numLastModifiedDates > 0) {
header.write(NID.kMTime);
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final DataOutputStream out = new DataOutputStream(baos);
if (numLastModifiedDates != files.size()) {
out.write(0);
final BitSet mTimes = new BitSet(files.size());
for (int i = 0; i < files.size(); i++) {
mTimes.set(i, files.get(i).getHasLastModifiedDate());
}
writeBits(out, mTimes, files.size());
} else {
out.write(1); // "allAreDefined" == true
}
out.write(0);
for (final SevenZArchiveEntry entry : files) {
if (entry.getHasLastModifiedDate()) {
out.writeLong(Long.reverseBytes(
SevenZArchiveEntry.javaTimeToNtfsTime(entry.getLastModifiedDate())));
}
}
out.flush();
final byte[] contents = baos.toByteArray();
writeUint64(header, contents.length);
header.write(contents);
}
}
private void writeFileWindowsAttributes(final DataOutput header) throws IOException {
int numWindowsAttributes = 0;
for (final SevenZArchiveEntry entry : files) {
if (entry.getHasWindowsAttributes()) {
++numWindowsAttributes;
}
}
if (numWindowsAttributes > 0) {
header.write(NID.kWinAttributes);
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final DataOutputStream out = new DataOutputStream(baos);
if (numWindowsAttributes != files.size()) {
out.write(0);
final BitSet attributes = new BitSet(files.size());
for (int i = 0; i < files.size(); i++) {
attributes.set(i, files.get(i).getHasWindowsAttributes());
}
writeBits(out, attributes, files.size());
} else {
out.write(1); // "allAreDefined" == true
}
out.write(0);
for (final SevenZArchiveEntry entry : files) {
if (entry.getHasWindowsAttributes()) {
out.writeInt(Integer.reverseBytes(entry.getWindowsAttributes()));
}
}
out.flush();
final byte[] contents = baos.toByteArray();
writeUint64(header, contents.length);
header.write(contents);
}
}
private void writeUint64(final DataOutput header, long value) throws IOException {
int firstByte = 0;
int mask = 0x80;
int i;
for (i = 0; i < 8; i++) {
if (value < ((1L << ( 7 * (i + 1))))) {
firstByte |= (value >>> (8 * i));
break;
}
firstByte |= mask;
mask >>>= 1;
}
header.write(firstByte);
for (; i > 0; i--) {
header.write((int) (0xff & value));
value >>>= 8;
}
}
private void writeBits(final DataOutput header, final BitSet bits, final int length) throws IOException {
int cache = 0;
int shift = 7;
for (int i = 0; i < length; i++) {
cache |= ((bits.get(i) ? 1 : 0) << shift);
if (--shift < 0) {
header.write(cache);
shift = 7;
cache = 0;
}
}
if (shift != 7) {
header.write(cache);
}
}
private static <T> Iterable<T> reverse(final Iterable<T> i) {
final LinkedList<T> l = new LinkedList<>();
for (final T t : i) {
l.addFirst(t);
}
return l;
}
private class OutputStreamWrapper extends OutputStream {
private static final int BUF_SIZE = 8192;
private final ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE);
@Override
public void write(final int b) throws IOException {
buffer.clear();
buffer.put((byte) b).flip();
channel.write(buffer);
compressedCrc32.update(b);
fileBytesWritten++;
}
@Override
public void write(final byte[] b) throws IOException {
OutputStreamWrapper.this.write(b, 0, b.length);
}
@Override
public void write(final byte[] b, final int off, final int len)
throws IOException {
if (len > BUF_SIZE) {
channel.write(ByteBuffer.wrap(b, off, len));
} else {
buffer.clear();
buffer.put(b, off, len).flip();
channel.write(buffer);
}
compressedCrc32.update(b, off, len);
fileBytesWritten += len;
}
@Override
public void flush() throws IOException {
// no reason to flush the channel
}
@Override
public void close() throws IOException {
// the file will be closed by the containing class's close method
}
}
}