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