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