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.sevenz; 019 020import java.io.ByteArrayOutputStream; 021import java.io.Closeable; 022import java.io.DataOutput; 023import java.io.DataOutputStream; 024import java.io.File; 025import java.io.IOException; 026import java.io.OutputStream; 027import java.nio.ByteBuffer; 028import java.nio.ByteOrder; 029import java.nio.channels.SeekableByteChannel; 030import java.nio.file.Files; 031import java.nio.file.StandardOpenOption; 032import java.util.ArrayList; 033import java.util.BitSet; 034import java.util.Collections; 035import java.util.Date; 036import java.util.EnumSet; 037import java.util.HashMap; 038import java.util.List; 039import java.util.LinkedList; 040import java.util.Map; 041import java.util.zip.CRC32; 042 043import org.apache.commons.compress.archivers.ArchiveEntry; 044import org.apache.commons.compress.utils.CountingOutputStream; 045 046/** 047 * Writes a 7z file. 048 * @since 1.6 049 */ 050public class SevenZOutputFile implements Closeable { 051 private final SeekableByteChannel channel; 052 private final List<SevenZArchiveEntry> files = new ArrayList<>(); 053 private int numNonEmptyStreams = 0; 054 private final CRC32 crc32 = new CRC32(); 055 private final CRC32 compressedCrc32 = new CRC32(); 056 private long fileBytesWritten = 0; 057 private boolean finished = false; 058 private CountingOutputStream currentOutputStream; 059 private CountingOutputStream[] additionalCountingStreams; 060 private Iterable<? extends SevenZMethodConfiguration> contentMethods = 061 Collections.singletonList(new SevenZMethodConfiguration(SevenZMethod.LZMA2)); 062 private final Map<SevenZArchiveEntry, long[]> additionalSizes = new HashMap<>(); 063 064 /** 065 * Opens file to write a 7z archive to. 066 * 067 * @param filename the file to write to 068 * @throws IOException if opening the file fails 069 */ 070 public SevenZOutputFile(final File filename) throws IOException { 071 this(Files.newByteChannel(filename.toPath(), 072 EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE, 073 StandardOpenOption.TRUNCATE_EXISTING))); 074 } 075 076 /** 077 * Prepares channel to write a 7z archive to. 078 * 079 * <p>{@link 080 * org.apache.commons.compress.utils.SeekableInMemoryByteChannel} 081 * allows you to write to an in-memory archive.</p> 082 * 083 * @param channel the channel to write to 084 * @throws IOException if the channel cannot be positioned properly 085 * @since 1.13 086 */ 087 public SevenZOutputFile(final SeekableByteChannel channel) throws IOException { 088 this.channel = channel; 089 channel.position(SevenZFile.SIGNATURE_HEADER_SIZE); 090 } 091 092 /** 093 * Sets the default compression method to use for entry contents - the 094 * default is LZMA2. 095 * 096 * <p>Currently only {@link SevenZMethod#COPY}, {@link 097 * SevenZMethod#LZMA2}, {@link SevenZMethod#BZIP2} and {@link 098 * SevenZMethod#DEFLATE} are supported.</p> 099 * 100 * <p>This is a short form for passing a single-element iterable 101 * to {@link #setContentMethods}.</p> 102 * @param method the default compression method 103 */ 104 public void setContentCompression(final SevenZMethod method) { 105 setContentMethods(Collections.singletonList(new SevenZMethodConfiguration(method))); 106 } 107 108 /** 109 * Sets the default (compression) methods to use for entry contents - the 110 * default is LZMA2. 111 * 112 * <p>Currently only {@link SevenZMethod#COPY}, {@link 113 * SevenZMethod#LZMA2}, {@link SevenZMethod#BZIP2} and {@link 114 * SevenZMethod#DEFLATE} are supported.</p> 115 * 116 * <p>The methods will be consulted in iteration order to create 117 * the final output.</p> 118 * 119 * @since 1.8 120 * @param methods the default (compression) methods 121 */ 122 public void setContentMethods(final Iterable<? extends SevenZMethodConfiguration> methods) { 123 this.contentMethods = reverse(methods); 124 } 125 126 /** 127 * Closes the archive, calling {@link #finish} if necessary. 128 * 129 * @throws IOException on error 130 */ 131 @Override 132 public void close() throws IOException { 133 if (!finished) { 134 finish(); 135 } 136 channel.close(); 137 } 138 139 /** 140 * Create an archive entry using the inputFile and entryName provided. 141 * 142 * @param inputFile file to create an entry from 143 * @param entryName the name to use 144 * @return the ArchiveEntry set up with details from the file 145 * 146 * @throws IOException on error 147 */ 148 public SevenZArchiveEntry createArchiveEntry(final File inputFile, 149 final String entryName) throws IOException { 150 final SevenZArchiveEntry entry = new SevenZArchiveEntry(); 151 entry.setDirectory(inputFile.isDirectory()); 152 entry.setName(entryName); 153 entry.setLastModifiedDate(new Date(inputFile.lastModified())); 154 return entry; 155 } 156 157 /** 158 * Records an archive entry to add. 159 * 160 * The caller must then write the content to the archive and call 161 * {@link #closeArchiveEntry()} to complete the process. 162 * 163 * @param archiveEntry describes the entry 164 * @throws IOException on error 165 */ 166 public void putArchiveEntry(final ArchiveEntry archiveEntry) throws IOException { 167 final SevenZArchiveEntry entry = (SevenZArchiveEntry) archiveEntry; 168 files.add(entry); 169 } 170 171 /** 172 * Closes the archive entry. 173 * @throws IOException on error 174 */ 175 public void closeArchiveEntry() throws IOException { 176 if (currentOutputStream != null) { 177 currentOutputStream.flush(); 178 currentOutputStream.close(); 179 } 180 181 final SevenZArchiveEntry entry = files.get(files.size() - 1); 182 if (fileBytesWritten > 0) { // this implies currentOutputStream != null 183 entry.setHasStream(true); 184 ++numNonEmptyStreams; 185 entry.setSize(currentOutputStream.getBytesWritten()); //NOSONAR 186 entry.setCompressedSize(fileBytesWritten); 187 entry.setCrcValue(crc32.getValue()); 188 entry.setCompressedCrcValue(compressedCrc32.getValue()); 189 entry.setHasCrc(true); 190 if (additionalCountingStreams != null) { 191 final long[] sizes = new long[additionalCountingStreams.length]; 192 for (int i = 0; i < additionalCountingStreams.length; i++) { 193 sizes[i] = additionalCountingStreams[i].getBytesWritten(); 194 } 195 additionalSizes.put(entry, sizes); 196 } 197 } else { 198 entry.setHasStream(false); 199 entry.setSize(0); 200 entry.setCompressedSize(0); 201 entry.setHasCrc(false); 202 } 203 currentOutputStream = null; 204 additionalCountingStreams = null; 205 crc32.reset(); 206 compressedCrc32.reset(); 207 fileBytesWritten = 0; 208 } 209 210 /** 211 * Writes a byte to the current archive entry. 212 * @param b The byte to be written. 213 * @throws IOException on error 214 */ 215 public void write(final int b) throws IOException { 216 getCurrentOutputStream().write(b); 217 } 218 219 /** 220 * Writes a byte array to the current archive entry. 221 * @param b The byte array to be written. 222 * @throws IOException on error 223 */ 224 public void write(final byte[] b) throws IOException { 225 write(b, 0, b.length); 226 } 227 228 /** 229 * Writes part of a byte array to the current archive entry. 230 * @param b The byte array to be written. 231 * @param off offset into the array to start writing from 232 * @param len number of bytes to write 233 * @throws IOException on error 234 */ 235 public void write(final byte[] b, final int off, final int len) throws IOException { 236 if (len > 0) { 237 getCurrentOutputStream().write(b, off, len); 238 } 239 } 240 241 /** 242 * Finishes the addition of entries to this archive, without closing it. 243 * 244 * @throws IOException if archive is already closed. 245 */ 246 public void finish() throws IOException { 247 if (finished) { 248 throw new IOException("This archive has already been finished"); 249 } 250 finished = true; 251 252 final long headerPosition = channel.position(); 253 254 final ByteArrayOutputStream headerBaos = new ByteArrayOutputStream(); 255 final DataOutputStream header = new DataOutputStream(headerBaos); 256 257 writeHeader(header); 258 header.flush(); 259 final byte[] headerBytes = headerBaos.toByteArray(); 260 channel.write(ByteBuffer.wrap(headerBytes)); 261 262 final CRC32 crc32 = new CRC32(); 263 crc32.update(headerBytes); 264 265 ByteBuffer bb = ByteBuffer.allocate(SevenZFile.sevenZSignature.length 266 + 2 /* version */ 267 + 4 /* start header CRC */ 268 + 8 /* next header position */ 269 + 8 /* next header length */ 270 + 4 /* next header CRC */) 271 .order(ByteOrder.LITTLE_ENDIAN); 272 // signature header 273 channel.position(0); 274 bb.put(SevenZFile.sevenZSignature); 275 // version 276 bb.put((byte) 0).put((byte) 2); 277 278 // placeholder for start header CRC 279 bb.putInt(0); 280 281 // start header 282 bb.putLong(headerPosition - SevenZFile.SIGNATURE_HEADER_SIZE) 283 .putLong(0xffffFFFFL & headerBytes.length) 284 .putInt((int) crc32.getValue()); 285 crc32.reset(); 286 crc32.update(bb.array(), SevenZFile.sevenZSignature.length + 6, 20); 287 bb.putInt(SevenZFile.sevenZSignature.length + 2, (int) crc32.getValue()); 288 bb.flip(); 289 channel.write(bb); 290 } 291 292 /* 293 * Creation of output stream is deferred until data is actually 294 * written as some codecs might write header information even for 295 * empty streams and directories otherwise. 296 */ 297 private OutputStream getCurrentOutputStream() throws IOException { 298 if (currentOutputStream == null) { 299 currentOutputStream = setupFileOutputStream(); 300 } 301 return currentOutputStream; 302 } 303 304 private CountingOutputStream setupFileOutputStream() throws IOException { 305 if (files.isEmpty()) { 306 throw new IllegalStateException("No current 7z entry"); 307 } 308 309 OutputStream out = new OutputStreamWrapper(); 310 final ArrayList<CountingOutputStream> moreStreams = new ArrayList<>(); 311 boolean first = true; 312 for (final SevenZMethodConfiguration m : getContentMethods(files.get(files.size() - 1))) { 313 if (!first) { 314 final CountingOutputStream cos = new CountingOutputStream(out); 315 moreStreams.add(cos); 316 out = cos; 317 } 318 out = Coders.addEncoder(out, m.getMethod(), m.getOptions()); 319 first = false; 320 } 321 if (!moreStreams.isEmpty()) { 322 additionalCountingStreams = moreStreams.toArray(new CountingOutputStream[moreStreams.size()]); 323 } 324 return new CountingOutputStream(out) { 325 @Override 326 public void write(final int b) throws IOException { 327 super.write(b); 328 crc32.update(b); 329 } 330 331 @Override 332 public void write(final byte[] b) throws IOException { 333 super.write(b); 334 crc32.update(b); 335 } 336 337 @Override 338 public void write(final byte[] b, final int off, final int len) 339 throws IOException { 340 super.write(b, off, len); 341 crc32.update(b, off, len); 342 } 343 }; 344 } 345 346 private Iterable<? extends SevenZMethodConfiguration> getContentMethods(final SevenZArchiveEntry entry) { 347 final Iterable<? extends SevenZMethodConfiguration> ms = entry.getContentMethods(); 348 return ms == null ? contentMethods : ms; 349 } 350 351 private void writeHeader(final DataOutput header) throws IOException { 352 header.write(NID.kHeader); 353 354 header.write(NID.kMainStreamsInfo); 355 writeStreamsInfo(header); 356 writeFilesInfo(header); 357 header.write(NID.kEnd); 358 } 359 360 private void writeStreamsInfo(final DataOutput header) throws IOException { 361 if (numNonEmptyStreams > 0) { 362 writePackInfo(header); 363 writeUnpackInfo(header); 364 } 365 366 writeSubStreamsInfo(header); 367 368 header.write(NID.kEnd); 369 } 370 371 private void writePackInfo(final DataOutput header) throws IOException { 372 header.write(NID.kPackInfo); 373 374 writeUint64(header, 0); 375 writeUint64(header, 0xffffFFFFL & numNonEmptyStreams); 376 377 header.write(NID.kSize); 378 for (final SevenZArchiveEntry entry : files) { 379 if (entry.hasStream()) { 380 writeUint64(header, entry.getCompressedSize()); 381 } 382 } 383 384 header.write(NID.kCRC); 385 header.write(1); // "allAreDefined" == true 386 for (final SevenZArchiveEntry entry : files) { 387 if (entry.hasStream()) { 388 header.writeInt(Integer.reverseBytes((int) entry.getCompressedCrcValue())); 389 } 390 } 391 392 header.write(NID.kEnd); 393 } 394 395 private void writeUnpackInfo(final DataOutput header) throws IOException { 396 header.write(NID.kUnpackInfo); 397 398 header.write(NID.kFolder); 399 writeUint64(header, numNonEmptyStreams); 400 header.write(0); 401 for (final SevenZArchiveEntry entry : files) { 402 if (entry.hasStream()) { 403 writeFolder(header, entry); 404 } 405 } 406 407 header.write(NID.kCodersUnpackSize); 408 for (final SevenZArchiveEntry entry : files) { 409 if (entry.hasStream()) { 410 final long[] moreSizes = additionalSizes.get(entry); 411 if (moreSizes != null) { 412 for (final long s : moreSizes) { 413 writeUint64(header, s); 414 } 415 } 416 writeUint64(header, entry.getSize()); 417 } 418 } 419 420 header.write(NID.kCRC); 421 header.write(1); // "allAreDefined" == true 422 for (final SevenZArchiveEntry entry : files) { 423 if (entry.hasStream()) { 424 header.writeInt(Integer.reverseBytes((int) entry.getCrcValue())); 425 } 426 } 427 428 header.write(NID.kEnd); 429 } 430 431 private void writeFolder(final DataOutput header, final SevenZArchiveEntry entry) throws IOException { 432 final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 433 int numCoders = 0; 434 for (final SevenZMethodConfiguration m : getContentMethods(entry)) { 435 numCoders++; 436 writeSingleCodec(m, bos); 437 } 438 439 writeUint64(header, numCoders); 440 header.write(bos.toByteArray()); 441 for (long i = 0; i < numCoders - 1; i++) { 442 writeUint64(header, i + 1); 443 writeUint64(header, i); 444 } 445 } 446 447 private void writeSingleCodec(final SevenZMethodConfiguration m, final OutputStream bos) throws IOException { 448 final byte[] id = m.getMethod().getId(); 449 final byte[] properties = Coders.findByMethod(m.getMethod()) 450 .getOptionsAsProperties(m.getOptions()); 451 452 int codecFlags = id.length; 453 if (properties.length > 0) { 454 codecFlags |= 0x20; 455 } 456 bos.write(codecFlags); 457 bos.write(id); 458 459 if (properties.length > 0) { 460 bos.write(properties.length); 461 bos.write(properties); 462 } 463 } 464 465 private void writeSubStreamsInfo(final DataOutput header) throws IOException { 466 header.write(NID.kSubStreamsInfo); 467// 468// header.write(NID.kCRC); 469// header.write(1); 470// for (final SevenZArchiveEntry entry : files) { 471// if (entry.getHasCrc()) { 472// header.writeInt(Integer.reverseBytes(entry.getCrc())); 473// } 474// } 475// 476 header.write(NID.kEnd); 477 } 478 479 private void writeFilesInfo(final DataOutput header) throws IOException { 480 header.write(NID.kFilesInfo); 481 482 writeUint64(header, files.size()); 483 484 writeFileEmptyStreams(header); 485 writeFileEmptyFiles(header); 486 writeFileAntiItems(header); 487 writeFileNames(header); 488 writeFileCTimes(header); 489 writeFileATimes(header); 490 writeFileMTimes(header); 491 writeFileWindowsAttributes(header); 492 header.write(NID.kEnd); 493 } 494 495 private void writeFileEmptyStreams(final DataOutput header) throws IOException { 496 boolean hasEmptyStreams = false; 497 for (final SevenZArchiveEntry entry : files) { 498 if (!entry.hasStream()) { 499 hasEmptyStreams = true; 500 break; 501 } 502 } 503 if (hasEmptyStreams) { 504 header.write(NID.kEmptyStream); 505 final BitSet emptyStreams = new BitSet(files.size()); 506 for (int i = 0; i < files.size(); i++) { 507 emptyStreams.set(i, !files.get(i).hasStream()); 508 } 509 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 510 final DataOutputStream out = new DataOutputStream(baos); 511 writeBits(out, emptyStreams, files.size()); 512 out.flush(); 513 final byte[] contents = baos.toByteArray(); 514 writeUint64(header, contents.length); 515 header.write(contents); 516 } 517 } 518 519 private void writeFileEmptyFiles(final DataOutput header) throws IOException { 520 boolean hasEmptyFiles = false; 521 int emptyStreamCounter = 0; 522 final BitSet emptyFiles = new BitSet(0); 523 for (final SevenZArchiveEntry file1 : files) { 524 if (!file1.hasStream()) { 525 final boolean isDir = file1.isDirectory(); 526 emptyFiles.set(emptyStreamCounter++, !isDir); 527 hasEmptyFiles |= !isDir; 528 } 529 } 530 if (hasEmptyFiles) { 531 header.write(NID.kEmptyFile); 532 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 533 final DataOutputStream out = new DataOutputStream(baos); 534 writeBits(out, emptyFiles, emptyStreamCounter); 535 out.flush(); 536 final byte[] contents = baos.toByteArray(); 537 writeUint64(header, contents.length); 538 header.write(contents); 539 } 540 } 541 542 private void writeFileAntiItems(final DataOutput header) throws IOException { 543 boolean hasAntiItems = false; 544 final BitSet antiItems = new BitSet(0); 545 int antiItemCounter = 0; 546 for (final SevenZArchiveEntry file1 : files) { 547 if (!file1.hasStream()) { 548 final boolean isAnti = file1.isAntiItem(); 549 antiItems.set(antiItemCounter++, isAnti); 550 hasAntiItems |= isAnti; 551 } 552 } 553 if (hasAntiItems) { 554 header.write(NID.kAnti); 555 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 556 final DataOutputStream out = new DataOutputStream(baos); 557 writeBits(out, antiItems, antiItemCounter); 558 out.flush(); 559 final byte[] contents = baos.toByteArray(); 560 writeUint64(header, contents.length); 561 header.write(contents); 562 } 563 } 564 565 private void writeFileNames(final DataOutput header) throws IOException { 566 header.write(NID.kName); 567 568 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 569 final DataOutputStream out = new DataOutputStream(baos); 570 out.write(0); 571 for (final SevenZArchiveEntry entry : files) { 572 out.write(entry.getName().getBytes("UTF-16LE")); 573 out.writeShort(0); 574 } 575 out.flush(); 576 final byte[] contents = baos.toByteArray(); 577 writeUint64(header, contents.length); 578 header.write(contents); 579 } 580 581 private void writeFileCTimes(final DataOutput header) throws IOException { 582 int numCreationDates = 0; 583 for (final SevenZArchiveEntry entry : files) { 584 if (entry.getHasCreationDate()) { 585 ++numCreationDates; 586 } 587 } 588 if (numCreationDates > 0) { 589 header.write(NID.kCTime); 590 591 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 592 final DataOutputStream out = new DataOutputStream(baos); 593 if (numCreationDates != files.size()) { 594 out.write(0); 595 final BitSet cTimes = new BitSet(files.size()); 596 for (int i = 0; i < files.size(); i++) { 597 cTimes.set(i, files.get(i).getHasCreationDate()); 598 } 599 writeBits(out, cTimes, files.size()); 600 } else { 601 out.write(1); // "allAreDefined" == true 602 } 603 out.write(0); 604 for (final SevenZArchiveEntry entry : files) { 605 if (entry.getHasCreationDate()) { 606 out.writeLong(Long.reverseBytes( 607 SevenZArchiveEntry.javaTimeToNtfsTime(entry.getCreationDate()))); 608 } 609 } 610 out.flush(); 611 final byte[] contents = baos.toByteArray(); 612 writeUint64(header, contents.length); 613 header.write(contents); 614 } 615 } 616 617 private void writeFileATimes(final DataOutput header) throws IOException { 618 int numAccessDates = 0; 619 for (final SevenZArchiveEntry entry : files) { 620 if (entry.getHasAccessDate()) { 621 ++numAccessDates; 622 } 623 } 624 if (numAccessDates > 0) { 625 header.write(NID.kATime); 626 627 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 628 final DataOutputStream out = new DataOutputStream(baos); 629 if (numAccessDates != files.size()) { 630 out.write(0); 631 final BitSet aTimes = new BitSet(files.size()); 632 for (int i = 0; i < files.size(); i++) { 633 aTimes.set(i, files.get(i).getHasAccessDate()); 634 } 635 writeBits(out, aTimes, files.size()); 636 } else { 637 out.write(1); // "allAreDefined" == true 638 } 639 out.write(0); 640 for (final SevenZArchiveEntry entry : files) { 641 if (entry.getHasAccessDate()) { 642 out.writeLong(Long.reverseBytes( 643 SevenZArchiveEntry.javaTimeToNtfsTime(entry.getAccessDate()))); 644 } 645 } 646 out.flush(); 647 final byte[] contents = baos.toByteArray(); 648 writeUint64(header, contents.length); 649 header.write(contents); 650 } 651 } 652 653 private void writeFileMTimes(final DataOutput header) throws IOException { 654 int numLastModifiedDates = 0; 655 for (final SevenZArchiveEntry entry : files) { 656 if (entry.getHasLastModifiedDate()) { 657 ++numLastModifiedDates; 658 } 659 } 660 if (numLastModifiedDates > 0) { 661 header.write(NID.kMTime); 662 663 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 664 final DataOutputStream out = new DataOutputStream(baos); 665 if (numLastModifiedDates != files.size()) { 666 out.write(0); 667 final BitSet mTimes = new BitSet(files.size()); 668 for (int i = 0; i < files.size(); i++) { 669 mTimes.set(i, files.get(i).getHasLastModifiedDate()); 670 } 671 writeBits(out, mTimes, files.size()); 672 } else { 673 out.write(1); // "allAreDefined" == true 674 } 675 out.write(0); 676 for (final SevenZArchiveEntry entry : files) { 677 if (entry.getHasLastModifiedDate()) { 678 out.writeLong(Long.reverseBytes( 679 SevenZArchiveEntry.javaTimeToNtfsTime(entry.getLastModifiedDate()))); 680 } 681 } 682 out.flush(); 683 final byte[] contents = baos.toByteArray(); 684 writeUint64(header, contents.length); 685 header.write(contents); 686 } 687 } 688 689 private void writeFileWindowsAttributes(final DataOutput header) throws IOException { 690 int numWindowsAttributes = 0; 691 for (final SevenZArchiveEntry entry : files) { 692 if (entry.getHasWindowsAttributes()) { 693 ++numWindowsAttributes; 694 } 695 } 696 if (numWindowsAttributes > 0) { 697 header.write(NID.kWinAttributes); 698 699 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 700 final DataOutputStream out = new DataOutputStream(baos); 701 if (numWindowsAttributes != files.size()) { 702 out.write(0); 703 final BitSet attributes = new BitSet(files.size()); 704 for (int i = 0; i < files.size(); i++) { 705 attributes.set(i, files.get(i).getHasWindowsAttributes()); 706 } 707 writeBits(out, attributes, files.size()); 708 } else { 709 out.write(1); // "allAreDefined" == true 710 } 711 out.write(0); 712 for (final SevenZArchiveEntry entry : files) { 713 if (entry.getHasWindowsAttributes()) { 714 out.writeInt(Integer.reverseBytes(entry.getWindowsAttributes())); 715 } 716 } 717 out.flush(); 718 final byte[] contents = baos.toByteArray(); 719 writeUint64(header, contents.length); 720 header.write(contents); 721 } 722 } 723 724 private void writeUint64(final DataOutput header, long value) throws IOException { 725 int firstByte = 0; 726 int mask = 0x80; 727 int i; 728 for (i = 0; i < 8; i++) { 729 if (value < ((1L << ( 7 * (i + 1))))) { 730 firstByte |= (value >>> (8 * i)); 731 break; 732 } 733 firstByte |= mask; 734 mask >>>= 1; 735 } 736 header.write(firstByte); 737 for (; i > 0; i--) { 738 header.write((int) (0xff & value)); 739 value >>>= 8; 740 } 741 } 742 743 private void writeBits(final DataOutput header, final BitSet bits, final int length) throws IOException { 744 int cache = 0; 745 int shift = 7; 746 for (int i = 0; i < length; i++) { 747 cache |= ((bits.get(i) ? 1 : 0) << shift); 748 if (--shift < 0) { 749 header.write(cache); 750 shift = 7; 751 cache = 0; 752 } 753 } 754 if (shift != 7) { 755 header.write(cache); 756 } 757 } 758 759 private static <T> Iterable<T> reverse(final Iterable<T> i) { 760 final LinkedList<T> l = new LinkedList<>(); 761 for (final T t : i) { 762 l.addFirst(t); 763 } 764 return l; 765 } 766 767 private class OutputStreamWrapper extends OutputStream { 768 private static final int BUF_SIZE = 8192; 769 private final ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE); 770 @Override 771 public void write(final int b) throws IOException { 772 buffer.clear(); 773 buffer.put((byte) b).flip(); 774 channel.write(buffer); 775 compressedCrc32.update(b); 776 fileBytesWritten++; 777 } 778 779 @Override 780 public void write(final byte[] b) throws IOException { 781 OutputStreamWrapper.this.write(b, 0, b.length); 782 } 783 784 @Override 785 public void write(final byte[] b, final int off, final int len) 786 throws IOException { 787 if (len > BUF_SIZE) { 788 channel.write(ByteBuffer.wrap(b, off, len)); 789 } else { 790 buffer.clear(); 791 buffer.put(b, off, len).flip(); 792 channel.write(buffer); 793 } 794 compressedCrc32.update(b, off, len); 795 fileBytesWritten += len; 796 } 797 798 @Override 799 public void flush() throws IOException { 800 // no reason to flush the channel 801 } 802 803 @Override 804 public void close() throws IOException { 805 // the file will be closed by the containing class's close method 806 } 807 } 808}