1 <?xml version="1.0"?> 2 <!-- 3 4 Licensed to the Apache Software Foundation (ASF) under one or more 5 contributor license agreements. See the NOTICE file distributed with 6 this work for additional information regarding copyright ownership. 7 The ASF licenses this file to You under the Apache License, Version 2.0 8 (the "License"); you may not use this file except in compliance with 9 the License. You may obtain a copy of the License at 10 11 http://www.apache.org/licenses/LICENSE-2.0 12 13 Unless required by applicable law or agreed to in writing, software 14 distributed under the License is distributed on an "AS IS" BASIS, 15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 See the License for the specific language governing permissions and 17 limitations under the License. 18 19 --> 20 <document> 21 <properties> 22 <title>Commons Compress User Guide</title> 23 <author email="dev (a] commons.apache.org">Commons Documentation Team</author> 24 </properties> 25 <body> 26 <section name="General Notes"> 27 28 <subsection name="Archivers and Compressors"> 29 <p>Commons Compress calls all formats that compress a single 30 stream of data compressor formats while all formats that 31 collect multiple entries inside a single (potentially 32 compressed) archive are archiver formats.</p> 33 34 <p>The compressor formats supported are gzip, bzip2, xz, lzma, 35 Pack200, DEFLATE, Brotli, DEFLATE64, ZStandard and Z, the archiver formats are 7z, ar, arj, 36 cpio, dump, tar and zip. Pack200 is a special case as it can 37 only compress JAR files.</p> 38 39 <p>We currently only provide read support for arj, 40 dump, Brotli, DEFLATE64 and Z. arj can only read uncompressed archives, 7z can read 41 archives with many compression and encryption algorithms 42 supported by 7z but doesn't support encryption when writing 43 archives.</p> 44 </subsection> 45 46 <subsection name="Buffering"> 47 <p>The stream classes all wrap around streams provided by the 48 calling code and they work on them directly without any 49 additional buffering. On the other hand most of them will 50 benefit from buffering so it is highly recommended that 51 users wrap their stream 52 in <code>Buffered<em>(In|Out)</em>putStream</code>s before 53 using the Commons Compress API.</p> 54 55 </subsection> 56 57 <subsection name="Factories"> 58 59 <p>Compress provides factory methods to create input/output 60 streams based on the names of the compressor or archiver 61 format as well as factory methods that try to guess the 62 format of an input stream.</p> 63 64 <p>To create a compressor writing to a given output by using 65 the algorithm name:</p> 66 <source><![CDATA[ 67 CompressorOutputStream gzippedOut = new CompressorStreamFactory() 68 .createCompressorOutputStream(CompressorStreamFactory.GZIP, myOutputStream); 69 ]]></source> 70 71 <p>Make the factory guess the input format for a given 72 archiver stream:</p> 73 <source><![CDATA[ 74 ArchiveInputStream input = new ArchiveStreamFactory() 75 .createArchiveInputStream(originalInput); 76 ]]></source> 77 78 <p>Make the factory guess the input format for a given 79 compressor stream:</p> 80 <source><![CDATA[ 81 CompressorInputStream input = new CompressorStreamFactory() 82 .createCompressorInputStream(originalInput); 83 ]]></source> 84 85 <p>Note that there is no way to detect the lzma or Brotli formats so only 86 the two-arg version of 87 <code>createCompressorInputStream</code> can be used. Prior 88 to Compress 1.9 the .Z format hasn't been auto-detected 89 either.</p> 90 91 </subsection> 92 93 <subsection name="Restricting Memory Usage"> 94 <p>Starting with Compress 1.14 95 <code>CompressorStreamFactory</code> has an optional 96 constructor argument that can be used to set an upper limit of 97 memory that may be used while decompressing or compressing a 98 stream. As of 1.14 this setting only affects decompressing Z, 99 XZ and LZMA compressed streams.</p> 100 <p>For the Snappy and LZ4 formats the amount of memory used 101 during compression is directly proportional to the window 102 size.</p> 103 </subsection> 104 105 <subsection name="Statistics"> 106 <p>Starting with Compress 1.17 most of the 107 <code>CompressorInputStream</code> implementations as well as 108 <code>ZipArchiveInputStream</code> and all streams returned by 109 <code>ZipFile.getInputStream</code> implement the 110 <code>InputStreamStatistics</code> 111 interface. <code>SevenZFile</code> provides statistics for the 112 current entry via the 113 <code>getStatisticsForCurrentEntry</code> method. This 114 interface can be used to track progress while extracting a 115 stream or to detect potential <a 116 href="https://en.wikipedia.org/wiki/Zip_bomb">zip bombs</a> 117 when the compression ration becomes suspiciously large.</p> 118 </subsection> 119 120 </section> 121 <section name="Archivers"> 122 123 <subsection name="Unsupported Features"> 124 <p>Many of the supported formats have developed different 125 dialects and extensions and some formats allow for features 126 (not yet) supported by Commons Compress.</p> 127 128 <p>The <code>ArchiveInputStream</code> class provides a method 129 <code>canReadEntryData</code> that will return false if 130 Commons Compress can detect that an archive uses a feature 131 that is not supported by the current implementation. If it 132 returns false you should not try to read the entry but skip 133 over it.</p> 134 135 </subsection> 136 137 <subsection name="Entry Names"> 138 <p>All archive formats provide meta data about the individual 139 archive entries via instances of <code>ArchiveEntry</code> (or 140 rather subclasses of it). When reading from an archive the 141 information provided the <code>getName</code> method is the 142 raw name as stored inside of the archive. There is no 143 guarantee the name represents a relative file name or even a 144 valid file name on your target operating system at all. You 145 should double check the outcome when you try to create file 146 names from entry names.</p> 147 </subsection> 148 149 <subsection name="Common Extraction Logic"> 150 <p>Apart from 7z all formats provide a subclass of 151 <code>ArchiveInputStream</code> that can be used to create an 152 archive. For 7z <code>SevenZFile</code> provides a similar API 153 that does not represent a stream as our implementation 154 requires random access to the input and cannot be used for 155 general streams. The ZIP implementation can benefit a lot from 156 random access as well, see the <a 157 href="zip.html#ZipArchiveInputStream_vs_ZipFile">zip 158 page</a> for details.</p> 159 160 <p>Assuming you want to extract an archive to a target 161 directory you'd call <code>getNextEntry</code>, verify the 162 entry can be read, construct a sane file name from the entry's 163 name, create a <codee>File</codee> and write all contents to 164 it - here <code>IOUtils.copy</code> may come handy. You do so 165 for every entry until <code>getNextEntry</code> returns 166 <code>null</code>.</p> 167 168 <p>A skeleton might look like:</p> 169 170 <source><![CDATA[ 171 File targetDir = ... 172 try (ArchiveInputStream i = ... create the stream for your format, use buffering...) { 173 ArchiveEntry entry = null; 174 while ((entry = i.getNextEntry()) != null) { 175 if (!i.canReadEntryData(entry)) { 176 // log something? 177 continue; 178 } 179 String name = fileName(targetDir, entry); 180 File f = new File(name); 181 if (entry.isDirectory()) { 182 if (!f.isDirectory() && !f.mkdirs()) { 183 throw new IOException("failed to create directory " + f); 184 } 185 } else { 186 File parent = f.getParentFile(); 187 if (!parent.isDirectory() && !parent.mkdirs()) { 188 throw new IOException("failed to create directory " + parent); 189 } 190 try (OutputStream o = Files.newOutputStream(f.toPath())) { 191 IOUtils.copy(i, o); 192 } 193 } 194 } 195 } 196 ]]></source> 197 198 <p>where the hypothetical <code>fileName</code> method is 199 written by you and provides the absolute name for the file 200 that is going to be written on disk. Here you should perform 201 checks that ensure the resulting file name actually is a valid 202 file name on your operating system or belongs to a file inside 203 of <code>targetDir</code> when using the entry's name as 204 input.</p> 205 206 <p>If you want to combine an archive format with a compression 207 format - like when reading a "tar.gz" file - you wrap the 208 <code>ArchiveInputStream</code> around 209 <code>CompressorInputStream</code> for example:</p> 210 211 <source><![CDATA[ 212 try (InputStream fi = Files.newInputStream(Paths.get("my.tar.gz")); 213 InputStream bi = new BufferedInputStream(fi); 214 InputStream gzi = new GzipCompressorInputStream(bi); 215 ArchiveInputStream o = new TarArchiveInputStream(gzi)) { 216 } 217 ]]></source> 218 219 </subsection> 220 221 <subsection name="Common Archival Logic"> 222 <p>Apart from 7z all formats that support writing provide a 223 subclass of <code>ArchiveOutputStream</code> that can be used 224 to create an archive. For 7z <code>SevenZOutputFile</code> 225 provides a similar API that does not represent a stream as our 226 implementation requires random access to the output and cannot 227 be used for general streams. The 228 <code>ZipArchiveOutputStream</code> class will benefit from 229 random access as well but can be used for non-seekable streams 230 - but not all features will be available and the archive size 231 might be slightly bigger, see <a 232 href="zip.html#ZipArchiveOutputStream">the zip page</a> for 233 details.</p> 234 235 <p>Assuming you want to add a collection of files to an 236 archive, you can first use <code>createArchiveEntry</code> for 237 each file. In general this will set a few flags (usually the 238 last modified time, the size and the information whether this 239 is a file or directory) based on the <code>File</code> 240 instance. Alternatively you can create the 241 <code>ArchiveEntry</code> subclass corresponding to your 242 format directly. Often you may want to set additional flags 243 like file permissions or owner information before adding the 244 entry to the archive.</p> 245 246 <p>Next you use <code>putArchiveEntry</code> in order to add 247 the entry and then start using <code>write</code> to add the 248 content of the entry - here <code>IOUtils.copy</code> may 249 come handy. Finally you invoke 250 <code>closeArchiveEntry</code> once you've written all content 251 and before you add the next entry.</p> 252 253 <p>Once all entries have been added you'd invoke 254 <code>finish</code> and finally <code>close</code> the 255 stream.</p> 256 257 <p>A skeleton might look like:</p> 258 259 <source><![CDATA[ 260 Collection<File> filesToArchive = ... 261 try (ArchiveOutputStream o = ... create the stream for your format ...) { 262 for (File f : filesToArchive) { 263 // maybe skip directories for formats like AR that don't store directories 264 ArchiveEntry entry = o.createArchiveEntry(f, entryName(f)); 265 // potentially add more flags to entry 266 o.putArchiveEntry(entry); 267 if (f.isFile()) { 268 try (InputStream i = Files.newInputStream(f.toPath())) { 269 IOUtils.copy(i, o); 270 } 271 } 272 o.closeArchiveEntry(); 273 } 274 out.finish(); 275 } 276 ]]></source> 277 278 <p>where the hypothetical <code>entryName</code> method is 279 written by you and provides the name for the entry as it is 280 going to be written to the archive.</p> 281 282 <p>If you want to combine an archive format with a compression 283 format - like when creating a "tar.gz" file - you wrap the 284 <code>ArchiveOutputStream</code> around a 285 <code>CompressorOutputStream</code> for example:</p> 286 287 <source><![CDATA[ 288 try (OutputStream fo = Files.newOutputStream(Paths.get("my.tar.gz")); 289 OutputStream gzo = new GzipCompressorOutputStream(fo); 290 ArchiveOutputStream o = new TarArchiveOutputStream(gzo)) { 291 } 292 ]]></source> 293 294 </subsection> 295 296 <subsection name="7z"> 297 298 <p>Note that Commons Compress currently only supports a subset 299 of compression and encryption algorithms used for 7z archives. 300 For writing only uncompressed entries, LZMA, LZMA2, BZIP2 and 301 Deflate are supported - in addition to those reading supports 302 AES-256/SHA-256 and DEFLATE64.</p> 303 304 <p>Multipart archives are not supported at all.</p> 305 306 <p>7z archives can use multiple compression and encryption 307 methods as well as filters combined as a pipeline of methods 308 for its entries. Prior to Compress 1.8 you could only specify 309 a single method when creating archives - reading archives 310 using more than one method has been possible before. Starting 311 with Compress 1.8 it is possible to configure the full 312 pipeline using the <code>setContentMethods</code> method of 313 <code>SevenZOutputFile</code>. Methods are specified in the 314 order they appear inside the pipeline when creating the 315 archive, you can also specify certain parameters for some of 316 the methods - see the Javadocs of 317 <code>SevenZMethodConfiguration</code> for details.</p> 318 319 <p>When reading entries from an archive the 320 <code>getContentMethods</code> method of 321 <code>SevenZArchiveEntry</code> will properly represent the 322 compression/encryption/filter methods but may fail to 323 determine the configuration options used. As of Compress 1.8 324 only the dictionary size used for LZMA2 can be read.</p> 325 326 <p>Currently solid compression - compressing multiple files 327 as a single block to benefit from patterns repeating accross 328 files - is only supported when reading archives. This also 329 means compression ratio will likely be worse when using 330 Commons Compress compared to the native 7z executable.</p> 331 332 <p>Reading or writing requires a 333 <code>SeekableByteChannel</code> that will be obtained 334 transparently when reading from or writing to a file. The 335 class 336 <code>org.apache.commons.compress.utils.SeekableInMemoryByteChannel</code> 337 allows you to read from or write to an in-memory archive.</p> 338 339 <p>Adding an entry to a 7z archive:</p> 340 <source><![CDATA[ 341 SevenZOutputFile sevenZOutput = new SevenZOutputFile(file); 342 SevenZArchiveEntry entry = sevenZOutput.createArchiveEntry(fileToArchive, name); 343 sevenZOutput.putArchiveEntry(entry); 344 sevenZOutput.write(contentOfEntry); 345 sevenZOutput.closeArchiveEntry(); 346 ]]></source> 347 348 <p>Uncompressing a given 7z archive (you would 349 certainly add exception handling and make sure all streams 350 get closed properly):</p> 351 <source><![CDATA[ 352 SevenZFile sevenZFile = new SevenZFile(new File("archive.7z")); 353 SevenZArchiveEntry entry = sevenZFile.getNextEntry(); 354 byte[] content = new byte[entry.getSize()]; 355 LOOP UNTIL entry.getSize() HAS BEEN READ { 356 sevenZFile.read(content, offset, content.length - offset); 357 } 358 ]]></source> 359 360 <p>Uncompressing a given in-memory 7z archive:</p> 361 <source><![CDATA[ 362 byte[] inputData; // 7z archive contents 363 SeekableInMemoryByteChannel inMemoryByteChannel = new SeekableInMemoryByteChannel(inputData); 364 SevenZFile sevenZFile = new SevenZFile(inMemoryByteChannel); 365 SevenZArchiveEntry entry = sevenZFile.getNextEntry(); 366 sevenZFile.read(); // read current entry's data 367 ]]></source> 368 369 <h4><a name="Encrypted 7z Archives"></a>Encrypted 7z Archives</h4> 370 371 <p>Currently Compress supports reading but not writing of 372 encrypted archives. When reading an encrypted archive a 373 password has to be provided to one of 374 <code>SevenZFile</code>'s constructors. If you try to read 375 an encrypted archive without specifying a password a 376 <code>PasswordRequiredException</code> (a subclass of 377 <code>IOException</code>) will be thrown.</p> 378 379 <p>When specifying the password as a <code>byte[]</code> one 380 common mistake is to use the wrong encoding when creating 381 the <code>byte[]</code> from a <code>String</code>. The 382 <code>SevenZFile</code> class expects the bytes to 383 correspond to the UTF16-LE encoding of the password. An 384 example of reading an encrypted archive is</p> 385 386 <source><![CDATA[ 387 SevenZFile sevenZFile = new SevenZFile(new File("archive.7z"), "secret".getBytes(StandardCharsets.UTF_16LE)); 388 SevenZArchiveEntry entry = sevenZFile.getNextEntry(); 389 byte[] content = new byte[entry.getSize()]; 390 LOOP UNTIL entry.getSize() HAS BEEN READ { 391 sevenZFile.read(content, offset, content.length - offset); 392 } 393 ]]></source> 394 395 <p>Starting with Compress 1.17 new constructors have been 396 added that accept the password as <code>char[]</code> rather 397 than a <code>byte[]</code>. We recommend you use these in 398 order to avoid the problem above.</p> 399 400 <source><![CDATA[ 401 SevenZFile sevenZFile = new SevenZFile(new File("archive.7z"), "secret".toCharArray()); 402 SevenZArchiveEntry entry = sevenZFile.getNextEntry(); 403 byte[] content = new byte[entry.getSize()]; 404 LOOP UNTIL entry.getSize() HAS BEEN READ { 405 sevenZFile.read(content, offset, content.length - offset); 406 } 407 ]]></source> 408 409 </subsection> 410 411 <subsection name="ar"> 412 413 <p>In addition to the information stored 414 in <code>ArchiveEntry</code> a <code>ArArchiveEntry</code> 415 stores information about the owner user and group as well as 416 Unix permissions.</p> 417 418 <p>Adding an entry to an ar archive:</p> 419 <source><![CDATA[ 420 ArArchiveEntry entry = new ArArchiveEntry(name, size); 421 arOutput.putArchiveEntry(entry); 422 arOutput.write(contentOfEntry); 423 arOutput.closeArchiveEntry(); 424 ]]></source> 425 426 <p>Reading entries from an ar archive:</p> 427 <source><![CDATA[ 428 ArArchiveEntry entry = (ArArchiveEntry) arInput.getNextEntry(); 429 byte[] content = new byte[entry.getSize()]; 430 LOOP UNTIL entry.getSize() HAS BEEN READ { 431 arInput.read(content, offset, content.length - offset); 432 } 433 ]]></source> 434 435 <p>Traditionally the AR format doesn't allow file names longer 436 than 16 characters. There are two variants that circumvent 437 this limitation in different ways, the GNU/SRV4 and the BSD 438 variant. Commons Compress 1.0 to 1.2 can only read archives 439 using the GNU/SRV4 variant, support for the BSD variant has 440 been added in Commons Compress 1.3. Commons Compress 1.3 441 also optionally supports writing archives with file names 442 longer than 16 characters using the BSD dialect, writing 443 the SVR4/GNU dialect is not supported.</p> 444 445 <table> 446 <thead> 447 <tr> 448 <th>Version of Apache Commons Compress</th> 449 <th>Support for Traditional AR Format</th> 450 <th>Support for GNU/SRV4 Dialect</th> 451 <th>Support for BSD Dialect</th> 452 </tr> 453 </thead> 454 <tbody> 455 <tr> 456 <td>1.0 to 1.2</td> 457 <td>read/write</td> 458 <td>read</td> 459 <td>-</td> 460 </tr> 461 <tr> 462 <td>1.3 and later</td> 463 <td>read/write</td> 464 <td>read</td> 465 <td>read/write</td> 466 </tr> 467 </tbody> 468 </table> 469 470 <p>It is not possible to detect the end of an AR archive in a 471 reliable way so <code>ArArchiveInputStream</code> will read 472 until it reaches the end of the stream or fails to parse the 473 stream's content as AR entries.</p> 474 475 </subsection> 476 477 <subsection name="arj"> 478 479 <p>Note that Commons Compress doesn't support compressed, 480 encrypted or multi-volume ARJ archives, yet.</p> 481 482 <p>Uncompressing a given arj archive (you would 483 certainly add exception handling and make sure all streams 484 get closed properly):</p> 485 <source><![CDATA[ 486 ArjArchiveEntry entry = arjInput.getNextEntry(); 487 byte[] content = new byte[entry.getSize()]; 488 LOOP UNTIL entry.getSize() HAS BEEN READ { 489 arjInput.read(content, offset, content.length - offset); 490 } 491 ]]></source> 492 </subsection> 493 494 <subsection name="cpio"> 495 496 <p>In addition to the information stored 497 in <code>ArchiveEntry</code> a <code>CpioArchiveEntry</code> 498 stores various attributes including information about the 499 original owner and permissions.</p> 500 501 <p>The cpio package supports the "new portable" as well as the 502 "old" format of CPIO archives in their binary, ASCII and 503 "with CRC" variants.</p> 504 505 <p>Adding an entry to a cpio archive:</p> 506 <source><![CDATA[ 507 CpioArchiveEntry entry = new CpioArchiveEntry(name, size); 508 cpioOutput.putArchiveEntry(entry); 509 cpioOutput.write(contentOfEntry); 510 cpioOutput.closeArchiveEntry(); 511 ]]></source> 512 513 <p>Reading entries from an cpio archive:</p> 514 <source><![CDATA[ 515 CpioArchiveEntry entry = cpioInput.getNextCPIOEntry(); 516 byte[] content = new byte[entry.getSize()]; 517 LOOP UNTIL entry.getSize() HAS BEEN READ { 518 cpioInput.read(content, offset, content.length - offset); 519 } 520 ]]></source> 521 522 <p>Traditionally CPIO archives are written in blocks of 512 523 bytes - the block size is a configuration parameter of the 524 <code>Cpio*Stream</code>'s constuctors. Starting with version 525 1.5 <code>CpioArchiveInputStream</code> will consume the 526 padding written to fill the current block when the end of the 527 archive is reached. Unfortunately many CPIO implementations 528 use larger block sizes so there may be more zero-byte padding 529 left inside the original input stream after the archive has 530 been consumed completely.</p> 531 532 </subsection> 533 534 <subsection name="jar"> 535 <p>In general, JAR archives are ZIP files, so the JAR package 536 supports all options provided by the <a href="#zip">ZIP</a> package.</p> 537 538 <p>To be interoperable JAR archives should always be created 539 using the UTF-8 encoding for file names (which is the 540 default).</p> 541 542 <p>Archives created using <code>JarArchiveOutputStream</code> 543 will implicitly add a <code>JarMarker</code> extra field to 544 the very first archive entry of the archive which will make 545 Solaris recognize them as Java archives and allows them to 546 be used as executables.</p> 547 548 <p>Note that <code>ArchiveStreamFactory</code> doesn't 549 distinguish ZIP archives from JAR archives, so if you use 550 the one-argument <code>createArchiveInputStream</code> 551 method on a JAR archive, it will still return the more 552 generic <code>ZipArchiveInputStream</code>.</p> 553 554 <p>The <code>JarArchiveEntry</code> class contains fields for 555 certificates and attributes that are planned to be supported 556 in the future but are not supported as of Compress 1.0.</p> 557 558 <p>Adding an entry to a jar archive:</p> 559 <source><![CDATA[ 560 JarArchiveEntry entry = new JarArchiveEntry(name, size); 561 entry.setSize(size); 562 jarOutput.putArchiveEntry(entry); 563 jarOutput.write(contentOfEntry); 564 jarOutput.closeArchiveEntry(); 565 ]]></source> 566 567 <p>Reading entries from an jar archive:</p> 568 <source><![CDATA[ 569 JarArchiveEntry entry = jarInput.getNextJarEntry(); 570 byte[] content = new byte[entry.getSize()]; 571 LOOP UNTIL entry.getSize() HAS BEEN READ { 572 jarInput.read(content, offset, content.length - offset); 573 } 574 ]]></source> 575 </subsection> 576 577 <subsection name="dump"> 578 579 <p>In addition to the information stored 580 in <code>ArchiveEntry</code> a <code>DumpArchiveEntry</code> 581 stores various attributes including information about the 582 original owner and permissions.</p> 583 584 <p>As of Commons Compress 1.3 only dump archives using the 585 new-fs format - this is the most common variant - are 586 supported. Right now this library supports uncompressed and 587 ZLIB compressed archives and can not write archives at 588 all.</p> 589 590 <p>Reading entries from an dump archive:</p> 591 <source><![CDATA[ 592 DumpArchiveEntry entry = dumpInput.getNextDumpEntry(); 593 byte[] content = new byte[entry.getSize()]; 594 LOOP UNTIL entry.getSize() HAS BEEN READ { 595 dumpInput.read(content, offset, content.length - offset); 596 } 597 ]]></source> 598 599 <p>Prior to version 1.5 <code>DumpArchiveInputStream</code> 600 would close the original input once it had read the last 601 record. Starting with version 1.5 it will not close the 602 stream implicitly.</p> 603 604 </subsection> 605 606 <subsection name="tar"> 607 608 <p>The TAR package has a <a href="tar.html">dedicated 609 documentation page</a>.</p> 610 611 <p>Adding an entry to a tar archive:</p> 612 <source><![CDATA[ 613 TarArchiveEntry entry = new TarArchiveEntry(name); 614 entry.setSize(size); 615 tarOutput.putArchiveEntry(entry); 616 tarOutput.write(contentOfEntry); 617 tarOutput.closeArchiveEntry(); 618 ]]></source> 619 620 <p>Reading entries from an tar archive:</p> 621 <source><![CDATA[ 622 TarArchiveEntry entry = tarInput.getNextTarEntry(); 623 byte[] content = new byte[entry.getSize()]; 624 LOOP UNTIL entry.getSize() HAS BEEN READ { 625 tarInput.read(content, offset, content.length - offset); 626 } 627 ]]></source> 628 </subsection> 629 630 <subsection name="zip"> 631 <p>The ZIP package has a <a href="zip.html">dedicated 632 documentation page</a>.</p> 633 634 <p>Adding an entry to a zip archive:</p> 635 <source><![CDATA[ 636 ZipArchiveEntry entry = new ZipArchiveEntry(name); 637 entry.setSize(size); 638 zipOutput.putArchiveEntry(entry); 639 zipOutput.write(contentOfEntry); 640 zipOutput.closeArchiveEntry(); 641 ]]></source> 642 643 <p><code>ZipArchiveOutputStream</code> can use some internal 644 optimizations exploiting <code>SeekableByteChannel</code> if it 645 knows it is writing to a seekable output rather than a non-seekable 646 stream. If you are writing to a file, you should use the 647 constructor that accepts a <code>File</code> or 648 <code>SeekableByteChannel</code> argument rather 649 than the one using an <code>OutputStream</code> or the 650 factory method in <code>ArchiveStreamFactory</code>.</p> 651 652 <p>Reading entries from an zip archive:</p> 653 <source><![CDATA[ 654 ZipArchiveEntry entry = zipInput.getNextZipEntry(); 655 byte[] content = new byte[entry.getSize()]; 656 LOOP UNTIL entry.getSize() HAS BEEN READ { 657 zipInput.read(content, offset, content.length - offset); 658 } 659 ]]></source> 660 661 <p>Reading entries from an zip archive using the 662 recommended <code>ZipFile</code> class:</p> 663 <source><![CDATA[ 664 ZipArchiveEntry entry = zipFile.getEntry(name); 665 InputStream content = zipFile.getInputStream(entry); 666 try { 667 READ UNTIL content IS EXHAUSTED 668 } finally { 669 content.close(); 670 } 671 ]]></source> 672 673 <p>Reading entries from an in-memory zip archive using 674 <code>SeekableInMemoryByteChannel</code> and <code>ZipFile</code> class:</p> 675 <source><![CDATA[ 676 byte[] inputData; // zip archive contents 677 SeekableInMemoryByteChannel inMemoryByteChannel = new SeekableInMemoryByteChannel(inputData); 678 ZipFile zipFile = new ZipFile(inMemoryByteChannel); 679 ZipArchiveEntry archiveEntry = zipFile.getEntry("entryName"); 680 InputStream inputStream = zipFile.getInputStream(archiveEntry); 681 inputStream.read() // read data from the input stream 682 ]]></source> 683 684 <p>Creating a zip file with multiple threads:</p> 685 686 A simple implementation to create a zip file might look like this: 687 688 <source> 689 public class ScatterSample { 690 691 ParallelScatterZipCreator scatterZipCreator = new ParallelScatterZipCreator(); 692 ScatterZipOutputStream dirs = ScatterZipOutputStream.fileBased(File.createTempFile("scatter-dirs", "tmp")); 693 694 public ScatterSample() throws IOException { 695 } 696 697 public void addEntry(ZipArchiveEntry zipArchiveEntry, InputStreamSupplier streamSupplier) throws IOException { 698 if (zipArchiveEntry.isDirectory() && !zipArchiveEntry.isUnixSymlink()) 699 dirs.addArchiveEntry(ZipArchiveEntryRequest.createZipArchiveEntryRequest(zipArchiveEntry, streamSupplier)); 700 else 701 scatterZipCreator.addArchiveEntry( zipArchiveEntry, streamSupplier); 702 } 703 704 public void writeTo(ZipArchiveOutputStream zipArchiveOutputStream) 705 throws IOException, ExecutionException, InterruptedException { 706 dirs.writeTo(zipArchiveOutputStream); 707 dirs.close(); 708 scatterZipCreator.writeTo(zipArchiveOutputStream); 709 } 710 } 711 </source> 712 </subsection> 713 714 </section> 715 <section name="Compressors"> 716 717 <subsection name="Concatenated Streams"> 718 <p>For the bzip2, gzip and xz formats as well as the framed 719 lz4 format a single compressed file 720 may actually consist of several streams that will be 721 concatenated by the command line utilities when decompressing 722 them. Starting with Commons Compress 1.4 the 723 <code>*CompressorInputStream</code>s for these formats support 724 concatenating streams as well, but they won't do so by 725 default. You must use the two-arg constructor and explicitly 726 enable the support.</p> 727 </subsection> 728 729 <subsection name="Brotli"> 730 731 <p>The implementation of this package is provided by the 732 <a href="https://github.com/google/brotli">Google Brotli dec</a> library.</p> 733 734 <p>Uncompressing a given Brotli compressed file (you would 735 certainly add exception handling and make sure all streams 736 get closed properly):</p> 737 <source><![CDATA[ 738 InputStream fin = Files.newInputStream(Paths.get("archive.tar.br")); 739 BufferedInputStream in = new BufferedInputStream(fin); 740 OutputStream out = Files.newOutputStream(Paths.get("archive.tar")); 741 BrotliCompressorInputStream brIn = new BrotliCompressorInputStream(in); 742 final byte[] buffer = new byte[buffersize]; 743 int n = 0; 744 while (-1 != (n = brIn.read(buffer))) { 745 out.write(buffer, 0, n); 746 } 747 out.close(); 748 brIn.close(); 749 ]]></source> 750 </subsection> 751 752 <subsection name="bzip2"> 753 754 <p>Note that <code>BZipCompressorOutputStream</code> keeps 755 hold of some big data structures in memory. While it is 756 recommended for <em>any</em> stream that you close it as soon as 757 you no longer need it, this is even more important 758 for <code>BZipCompressorOutputStream</code>.</p> 759 760 <p>Uncompressing a given bzip2 compressed file (you would 761 certainly add exception handling and make sure all streams 762 get closed properly):</p> 763 <source><![CDATA[ 764 InputStream fin = Files.newInputStream(Paths.get("archive.tar.bz2")); 765 BufferedInputStream in = new BufferedInputStream(fin); 766 OutputStream out = Files.newOutputStream(Paths.get("archive.tar")); 767 BZip2CompressorInputStream bzIn = new BZip2CompressorInputStream(in); 768 final byte[] buffer = new byte[buffersize]; 769 int n = 0; 770 while (-1 != (n = bzIn.read(buffer))) { 771 out.write(buffer, 0, n); 772 } 773 out.close(); 774 bzIn.close(); 775 ]]></source> 776 777 <p>Compressing a given file using bzip2 (you would 778 certainly add exception handling and make sure all streams 779 get closed properly):</p> 780 <source><![CDATA[ 781 InputStream in = Files.newInputStream(Paths.get("archive.tar")); 782 OutputStream fout = Files.newOutputStream(Paths.get("archive.tar.gz")); 783 BufferedOutputStream out = new BufferedOutputStream(fout); 784 BZip2CompressorOutputStream bzOut = new BZip2CompressorOutputStream(out); 785 final byte[] buffer = new byte[buffersize]; 786 int n = 0; 787 while (-1 != (n = in.read(buffer))) { 788 bzOut.write(buffer, 0, n); 789 } 790 bzOut.close(); 791 in.close(); 792 ]]></source> 793 794 </subsection> 795 796 <subsection name="DEFLATE"> 797 798 <p>The implementation of the DEFLATE/INFLATE code used by this 799 package is provided by the <code>java.util.zip</code> package 800 of the Java class library.</p> 801 802 <p>Uncompressing a given DEFLATE compressed file (you would 803 certainly add exception handling and make sure all streams 804 get closed properly):</p> 805 <source><![CDATA[ 806 InputStream fin = Files.newInputStream(Paths.get("some-file")); 807 BufferedInputStream in = new BufferedInputStream(fin); 808 OutputStream out = Files.newOutputStream(Paths.get("archive.tar")); 809 DeflateCompressorInputStream defIn = new DeflateCompressorInputStream(in); 810 final byte[] buffer = new byte[buffersize]; 811 int n = 0; 812 while (-1 != (n = defIn.read(buffer))) { 813 out.write(buffer, 0, n); 814 } 815 out.close(); 816 defIn.close(); 817 ]]></source> 818 819 <p>Compressing a given file using DEFLATE (you would 820 certainly add exception handling and make sure all streams 821 get closed properly):</p> 822 <source><![CDATA[ 823 InputStream in = Files.newInputStream(Paths.get("archive.tar")); 824 OutputStream fout = Files.newOutputStream(Paths.get("some-file")); 825 BufferedOutputStream out = new BufferedOutputStream(fout); 826 DeflateCompressorOutputStream defOut = new DeflateCompressorOutputStream(out); 827 final byte[] buffer = new byte[buffersize]; 828 int n = 0; 829 while (-1 != (n = in.read(buffer))) { 830 defOut.write(buffer, 0, n); 831 } 832 defOut.close(); 833 in.close(); 834 ]]></source> 835 836 </subsection> 837 838 <subsection name="DEFLATE64"> 839 840 <p>Uncompressing a given DEFLATE64 compressed file (you would 841 certainly add exception handling and make sure all streams 842 get closed properly):</p> 843 <source><![CDATA[ 844 InputStream fin = Files.newInputStream(Paths.get("some-file")); 845 BufferedInputStream in = new BufferedInputStream(fin); 846 OutputStream out = Files.newOutputStream(Paths.get("archive.tar")); 847 Deflate64CompressorInputStream defIn = new Deflate64CompressorInputStream(in); 848 final byte[] buffer = new byte[buffersize]; 849 int n = 0; 850 while (-1 != (n = defIn.read(buffer))) { 851 out.write(buffer, 0, n); 852 } 853 out.close(); 854 defIn.close(); 855 ]]></source> 856 857 </subsection> 858 859 <subsection name="gzip"> 860 861 <p>The implementation of the DEFLATE/INFLATE code used by this 862 package is provided by the <code>java.util.zip</code> package 863 of the Java class library.</p> 864 865 <p>Uncompressing a given gzip compressed file (you would 866 certainly add exception handling and make sure all streams 867 get closed properly):</p> 868 <source><![CDATA[ 869 InputStream fin = Files.newInputStream(Paths.get("archive.tar.gz")); 870 BufferedInputStream in = new BufferedInputStream(fin); 871 OutputStream out = Files.newOutputStream(Paths.get("archive.tar")); 872 GzipCompressorInputStream gzIn = new GzipCompressorInputStream(in); 873 final byte[] buffer = new byte[buffersize]; 874 int n = 0; 875 while (-1 != (n = gzIn.read(buffer))) { 876 out.write(buffer, 0, n); 877 } 878 out.close(); 879 gzIn.close(); 880 ]]></source> 881 882 <p>Compressing a given file using gzip (you would 883 certainly add exception handling and make sure all streams 884 get closed properly):</p> 885 <source><![CDATA[ 886 InputStream in = Files.newInputStream(Paths.get("archive.tar")); 887 OutputStream fout = Files.newOutputStream(Paths.get("archive.tar.gz")); 888 BufferedOutputStream out = new BufferedOutputStream(fout); 889 GzipCompressorOutputStream gzOut = new GzipCompressorOutputStream(out); 890 final byte[] buffer = new byte[buffersize]; 891 int n = 0; 892 while (-1 != (n = in.read(buffer))) { 893 gzOut.write(buffer, 0, n); 894 } 895 gzOut.close(); 896 in.close(); 897 ]]></source> 898 899 </subsection> 900 901 <subsection name="LZ4"> 902 903 <p>There are two different "formats" used for <a 904 href="http://lz4.github.io/lz4/">lz4</a>. The format called 905 "block format" only contains the raw compressed data while the 906 other provides a higher level "frame format" - Commons 907 Compress offers two different stream classes for reading or 908 writing either format.</p> 909 910 <p>Uncompressing a given frame LZ4 file (you would 911 certainly add exception handling and make sure all streams 912 get closed properly):</p> 913 <source><![CDATA[ 914 InputStream fin = Files.newInputStream(Paths.get("archive.tar.lz4")); 915 BufferedInputStream in = new BufferedInputStream(fin); 916 OutputStream out = Files.newOutputStream(Paths.get("archive.tar")); 917 FramedLZ4CompressorInputStream zIn = new FramedLZ4CompressorInputStream(in); 918 final byte[] buffer = new byte[buffersize]; 919 int n = 0; 920 while (-1 != (n = zIn.read(buffer))) { 921 out.write(buffer, 0, n); 922 } 923 out.close(); 924 zIn.close(); 925 ]]></source> 926 927 <p>Compressing a given file using the LZ4 frame format (you would 928 certainly add exception handling and make sure all streams 929 get closed properly):</p> 930 <source><![CDATA[ 931 InputStream in = Files.newInputStream(Paths.get("archive.tar")); 932 OutputStream fout = Files.newOutputStream(Paths.get("archive.tar.lz4")); 933 BufferedOutputStream out = new BufferedOutputStream(fout); 934 FramedLZ4CompressorOutputStream lzOut = new FramedLZ4CompressorOutputStream(out); 935 final byte[] buffer = new byte[buffersize]; 936 int n = 0; 937 while (-1 != (n = in.read(buffer))) { 938 lzOut.write(buffer, 0, n); 939 } 940 lzOut.close(); 941 in.close(); 942 ]]></source> 943 944 </subsection> 945 946 <subsection name="lzma"> 947 948 <p>The implementation of this package is provided by the 949 public domain <a href="https://tukaani.org/xz/java.html">XZ 950 for Java</a> library.</p> 951 952 <p>Uncompressing a given lzma compressed file (you would 953 certainly add exception handling and make sure all streams 954 get closed properly):</p> 955 <source><![CDATA[ 956 InputStream fin = Files.newInputStream(Paths.get("archive.tar.lzma")); 957 BufferedInputStream in = new BufferedInputStream(fin); 958 OutputStream out = Files.newOutputStream(Paths.get("archive.tar")); 959 LZMACompressorInputStream lzmaIn = new LZMACompressorInputStream(in); 960 final byte[] buffer = new byte[buffersize]; 961 int n = 0; 962 while (-1 != (n = xzIn.read(buffer))) { 963 out.write(buffer, 0, n); 964 } 965 out.close(); 966 lzmaIn.close(); 967 ]]></source> 968 969 <p>Compressing a given file using lzma (you would 970 certainly add exception handling and make sure all streams 971 get closed properly):</p> 972 <source><![CDATA[ 973 InputStream in = Files.newInputStream(Paths.get("archive.tar")); 974 OutputStream fout = Files.newOutputStream(Paths.get("archive.tar.lzma")); 975 BufferedOutputStream out = new BufferedOutputStream(fout); 976 LZMACompressorOutputStream lzOut = new LZMACompressorOutputStream(out); 977 final byte[] buffer = new byte[buffersize]; 978 int n = 0; 979 while (-1 != (n = in.read(buffer))) { 980 lzOut.write(buffer, 0, n); 981 } 982 lzOut.close(); 983 in.close(); 984 ]]></source> 985 986 </subsection> 987 988 <subsection name="Pack200"> 989 990 <p>The Pack200 package has a <a href="pack200.html">dedicated 991 documentation page</a>.</p> 992 993 <p>The implementation of this package is provided by 994 the <code>java.util.zip</code> package of the Java class 995 library.</p> 996 997 <p>Uncompressing a given pack200 compressed file (you would 998 certainly add exception handling and make sure all streams 999 get closed properly):</p> 1000 <source><![CDATA[ 1001 InputStream fin = Files.newInputStream(Paths.get("archive.pack")); 1002 BufferedInputStream in = new BufferedInputStream(fin); 1003 OutputStream out = Files.newOutputStream(Paths.get("archive.jar")); 1004 Pack200CompressorInputStream pIn = new Pack200CompressorInputStream(in); 1005 final byte[] buffer = new byte[buffersize]; 1006 int n = 0; 1007 while (-1 != (n = pIn.read(buffer))) { 1008 out.write(buffer, 0, n); 1009 } 1010 out.close(); 1011 pIn.close(); 1012 ]]></source> 1013 1014 <p>Compressing a given jar using pack200 (you would 1015 certainly add exception handling and make sure all streams 1016 get closed properly):</p> 1017 <source><![CDATA[ 1018 InputStream in = Files.newInputStream(Paths.get("archive.jar")); 1019 OutputStream fout = Files.newOutputStream(Paths.get("archive.pack")); 1020 BufferedOutputStream out = new BufferedInputStream(fout); 1021 Pack200CompressorOutputStream pOut = new Pack200CompressorOutputStream(out); 1022 final byte[] buffer = new byte[buffersize]; 1023 int n = 0; 1024 while (-1 != (n = in.read(buffer))) { 1025 pOut.write(buffer, 0, n); 1026 } 1027 pOut.close(); 1028 in.close(); 1029 ]]></source> 1030 1031 </subsection> 1032 1033 <subsection name="Snappy"> 1034 1035 <p>There are two different "formats" used for <a 1036 href="https://github.com/google/snappy/">Snappy</a>, one only 1037 contains the raw compressed data while the other provides a 1038 higher level "framing format" - Commons Compress offers two 1039 different stream classes for reading either format.</p> 1040 1041 <p>Starting with 1.12 we've added support for different 1042 dialects of the framing format that can be specified when 1043 constructing the stream. The <code>STANDARD</code> dialect 1044 follows the "framing format" specification while the 1045 <code>IWORK_ARCHIVE</code> dialect can be used to parse IWA 1046 files that are part of Apple's iWork 13 format. If no dialect 1047 has been specified, <code>STANDARD</code> is used. Only the 1048 <code>STANDARD</code> format can be detected by 1049 <code>CompressorStreamFactory</code>.</p> 1050 1051 <p>Uncompressing a given framed Snappy file (you would 1052 certainly add exception handling and make sure all streams 1053 get closed properly):</p> 1054 <source><![CDATA[ 1055 InputStream fin = Files.newInputStream(Paths.get("archive.tar.sz")); 1056 BufferedInputStream in = new BufferedInputStream(fin); 1057 OutputStream out = Files.newOutputStream(Paths.get("archive.tar")); 1058 FramedSnappyCompressorInputStream zIn = new FramedSnappyCompressorInputStream(in); 1059 final byte[] buffer = new byte[buffersize]; 1060 int n = 0; 1061 while (-1 != (n = zIn.read(buffer))) { 1062 out.write(buffer, 0, n); 1063 } 1064 out.close(); 1065 zIn.close(); 1066 ]]></source> 1067 1068 <p>Compressing a given file using framed Snappy (you would 1069 certainly add exception handling and make sure all streams 1070 get closed properly):</p> 1071 <source><![CDATA[ 1072 InputStream in = Files.newInputStream(Paths.get("archive.tar")); 1073 OutputStream fout = Files.newOutputStream(Paths.get("archive.tar.sz")); 1074 BufferedOutputStream out = new BufferedOutputStream(fout); 1075 FramedSnappyCompressorOutputStream snOut = new FramedSnappyCompressorOutputStream(out); 1076 final byte[] buffer = new byte[buffersize]; 1077 int n = 0; 1078 while (-1 != (n = in.read(buffer))) { 1079 snOut.write(buffer, 0, n); 1080 } 1081 snOut.close(); 1082 in.close(); 1083 ]]></source> 1084 1085 </subsection> 1086 1087 <subsection name="XZ"> 1088 1089 <p>The implementation of this package is provided by the 1090 public domain <a href="https://tukaani.org/xz/java.html">XZ 1091 for Java</a> library.</p> 1092 1093 <p>When you try to open an XZ stream for reading using 1094 <code>CompressorStreamFactory</code>, Commons Compress will 1095 check whether the XZ for Java library is available. Starting 1096 with Compress 1.9 the result of this check will be cached 1097 unless Compress finds OSGi classes in its classpath. You can 1098 use <code>XZUtils#setCacheXZAvailability</code> to overrride 1099 this default behavior.</p> 1100 1101 <p>Uncompressing a given XZ compressed file (you would 1102 certainly add exception handling and make sure all streams 1103 get closed properly):</p> 1104 <source><![CDATA[ 1105 InputStream fin = Files.newInputStream(Paths.get("archive.tar.xz")); 1106 BufferedInputStream in = new BufferedInputStream(fin); 1107 OutputStream out = Files.newOutputStream(Paths.get("archive.tar")); 1108 XZCompressorInputStream xzIn = new XZCompressorInputStream(in); 1109 final byte[] buffer = new byte[buffersize]; 1110 int n = 0; 1111 while (-1 != (n = xzIn.read(buffer))) { 1112 out.write(buffer, 0, n); 1113 } 1114 out.close(); 1115 xzIn.close(); 1116 ]]></source> 1117 1118 <p>Compressing a given file using XZ (you would 1119 certainly add exception handling and make sure all streams 1120 get closed properly):</p> 1121 <source><![CDATA[ 1122 InputStream in = Files.newInputStream(Paths.get("archive.tar")); 1123 OutputStream fout = Files.newOutputStream(Paths.get("archive.tar.xz")); 1124 BufferedOutputStream out = new BufferedInputStream(fout); 1125 XZCompressorOutputStream xzOut = new XZCompressorOutputStream(out); 1126 final byte[] buffer = new byte[buffersize]; 1127 int n = 0; 1128 while (-1 != (n = in.read(buffer))) { 1129 xzOut.write(buffer, 0, n); 1130 } 1131 xzOut.close(); 1132 in.close(); 1133 ]]></source> 1134 1135 </subsection> 1136 1137 <subsection name="Z"> 1138 1139 <p>Uncompressing a given Z compressed file (you would 1140 certainly add exception handling and make sure all streams 1141 get closed properly):</p> 1142 <source><![CDATA[ 1143 InputStream fin = Files.newInputStream(Paths.get("archive.tar.Z")); 1144 BufferedInputStream in = new BufferedInputStream(fin); 1145 OutputStream out = Files.newOutputStream(Paths.get("archive.tar")); 1146 ZCompressorInputStream zIn = new ZCompressorInputStream(in); 1147 final byte[] buffer = new byte[buffersize]; 1148 int n = 0; 1149 while (-1 != (n = zIn.read(buffer))) { 1150 out.write(buffer, 0, n); 1151 } 1152 out.close(); 1153 zIn.close(); 1154 ]]></source> 1155 1156 </subsection> 1157 1158 <subsection name="Zstandard"> 1159 1160 <p>The implementation of this package is provided by the 1161 <a href="https://github.com/luben/zstd-jni">Zstandard JNI</a> library.</p> 1162 1163 <p>Uncompressing a given Zstandard compressed file (you would 1164 certainly add exception handling and make sure all streams 1165 get closed properly):</p> 1166 <source><![CDATA[ 1167 InputStream fin = Files.newInputStream(Paths.get("archive.tar.zstd")); 1168 BufferedInputStream in = new BufferedInputStream(fin); 1169 OutputStream out = Files.newOutputStream(Paths.get("archive.tar")); 1170 ZstdCompressorInputStream zsIn = new ZstdCompressorInputStream(in); 1171 final byte[] buffer = new byte[buffersize]; 1172 int n = 0; 1173 while (-1 != (n = zsIn.read(buffer))) { 1174 out.write(buffer, 0, n); 1175 } 1176 out.close(); 1177 zsIn.close(); 1178 ]]></source> 1179 1180 <p>Compressing a given file using the Zstandard format (you 1181 would certainly add exception handling and make sure all 1182 streams get closed properly):</p> 1183 <source><![CDATA[ 1184 InputStream in = Files.newInputStream(Paths.get("archive.tar")); 1185 OutputStream fout = Files.newOutputStream(Paths.get("archive.tar.zstd")); 1186 BufferedOutputStream out = new BufferedOutputStream(fout); 1187 ZstdCompressorOutputStream zOut = new ZstdCompressorOutputStream(out); 1188 final byte[] buffer = new byte[buffersize]; 1189 int n = 0; 1190 while (-1 != (n = in.read(buffer))) { 1191 zOut.write(buffer, 0, n); 1192 } 1193 zOut.close(); 1194 in.close(); 1195 ]]></source> 1196 1197 </subsection> 1198 </section> 1199 1200 <section name="Extending Commons Compress"> 1201 1202 <p> 1203 Starting in release 1.13, it is now possible to add Compressor- and ArchiverStream implementations using the 1204 Java's <a href="https://docs.oracle.com/javase/7/docs/api/java/util/ServiceLoader.html">ServiceLoader</a> 1205 mechanism. 1206 </p> 1207 1208 <subsection name="Extending Commons Compress Compressors"> 1209 1210 <p> 1211 To provide your own compressor, you must make available on the classpath a file called 1212 <code>META-INF/services/org.apache.commons.compress.compressors.CompressorStreamProvider</code>. 1213 </p> 1214 <p> 1215 This file MUST contain one fully-qualified class name per line. 1216 </p> 1217 <p> 1218 For example: 1219 </p> 1220 <pre>org.apache.commons.compress.compressors.TestCompressorStreamProvider</pre> 1221 <p> 1222 This class MUST implement the Commons Compress interface 1223 <a href="apidocs/org/apache/commons/compress/compressors/CompressorStreamProvider.html">org.apache.commons.compress.compressors.CompressorStreamProvider</a>. 1224 </p> 1225 </subsection> 1226 1227 <subsection name="Extending Commons Compress Archivers"> 1228 1229 <p> 1230 To provide your own compressor, you must make available on the classpath a file called 1231 <code>META-INF/services/org.apache.commons.compress.archivers.ArchiveStreamProvider</code>. 1232 </p> 1233 <p> 1234 This file MUST contain one fully-qualified class name per line. 1235 </p> 1236 <p> 1237 For example: 1238 </p> 1239 <pre>org.apache.commons.compress.archivers.TestArchiveStreamProvider</pre> 1240 <p> 1241 This class MUST implement the Commons Compress interface 1242 <a href="apidocs/org/apache/commons/compress/archivers/ArchiveStreamProvider.html">org.apache.commons.compress.archivers.ArchiveStreamProvider</a>. 1243 </p> 1244 </subsection> 1245 1246 </section> 1247 </body> 1248 </document> 1249