aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFile.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFile.java')
-rw-r--r--src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFile.java811
1 files changed, 811 insertions, 0 deletions
diff --git a/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFile.java b/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFile.java
new file mode 100644
index 000000000..db47f44b4
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFile.java
@@ -0,0 +1,811 @@
+/*
+ * 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.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.DataOutput;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.Files;
+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.List;
+import java.util.LinkedList;
+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 = 0;
+ private final CRC32 crc32 = new CRC32();
+ private final CRC32 compressedCrc32 = new CRC32();
+ private long fileBytesWritten = 0;
+ private boolean finished = false;
+ 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;
+ }
+
+ /**
+ * 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);
+ }
+ }
+
+ /**
+ * 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);
+
+ 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");
+ }
+
+ OutputStream out = new OutputStreamWrapper();
+ 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[moreStreams.size()]);
+ }
+ 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("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
+ }
+ }
+}