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