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