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 // doesn't need to be closed, just wraps the instance field channel 313 OutputStream out = new OutputStreamWrapper(); // NOSONAR 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.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.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}