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