KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > de > schlichtherle > util > zip > BasicZipFile


1 /*
2  * Copyright 2005-2006 Schlichtherle IT Services
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package de.schlichtherle.util.zip;
18
19 import de.schlichtherle.io.rof.BufferedReadOnlyFile;
20 import de.schlichtherle.io.rof.ReadOnlyFile;
21 import de.schlichtherle.io.rof.SimpleReadOnlyFile;
22
23 import java.io.*;
24 import java.lang.ref.*;
25 import java.util.*;
26 import java.util.zip.*;
27
28 /**
29  * <em>This class is <skipBuf>not</skipBuf> intended for public use!</em>
30  * The methods in this class are unsynchronized and
31  * {@link #entries}/{@link #getEntry} enumerate/return {@link ZipEntry}
32  * instances which are originally used by this class rather than clones
33  * of them.
34  * The class {@link de.schlichtherle.io.archive.zip.Zip32InputArchive}
35  * extends from this class in order to benefit from the slightly better
36  * performance.
37  *
38  *
39  *
40  *
41  * @author Christian Schlichtherle
42  * @version @version@
43  * @see ZipOutputStream
44  * @since TrueZIP 6.4
45  */

46 public class BasicZipFile implements ZipConstants {
47
48     private static final long LONG_MSB = 0x8000000000000000L;
49
50     private static final int LFH_FILE_NAME_LENGTH_OFFSET =
51         /* local file header signature */ 4 +
52         /* version needed to extract */ 2 +
53         /* general purpose bit flag */ 2 +
54         /* compression method */ 2 +
55         /* last mod file time */ 2 +
56         /* last mod file date */ 2 +
57         /* crc-32 */ 4 +
58         /* compressed size */ 4 +
59         /* uncompressed size */ 4;
60
61     private static final int EOCD_NUM_ENTRIES_OFFSET =
62         /* end of central dir signature */ 4 +
63         /* number of this disk */ 2 +
64         /* number of the disk with the */ +
65         /* start of the central directory */ 2 +
66         /* total number of entries in */ +
67         /* the central dir on this disk */ 2;
68
69     private static final int EOCD_CD_SIZE_OFFSET =
70             EOCD_MIN_LEN - 10;
71
72     private static final int EOCD_CD_LOCATION_OFFSET =
73         /* end of central dir signature */ 4 +
74         /* number of this disk */ 2 +
75         /* number of the disk with the */ +
76         /* start of the central directory */ 2 +
77         /* total number of entries in */ +
78         /* the central dir on this disk */ 2 +
79         /* total number of entries in */ +
80         /* the central dir */ 2 +
81         /* size of the central directory */ 4;
82     
83     private static final int EOCD_COMMENT_OFFSET =
84             EOCD_MIN_LEN - 2;
85
86     private static final Set allocatedInflaters = new HashSet();
87     private static final List releasedInflaters = new LinkedList();
88
89     /**
90      * The encoding to use for entry names and comments.
91      */

92     private final String JavaDoc encoding;
93
94     /**
95      * The comment of this ZIP compatible file.
96      */

97     private String JavaDoc comment;
98
99     /**
100      * Maps entry names to zip entries [String -> ZipEntry].
101      */

102     private final Map entries = new HashMap();
103
104     /** The actual data source. */
105     private ReadOnlyFile archive;
106
107     /**
108      * The number of open streams reading from this ZIP compatible file.
109      */

110 private int openStreams;
111
112     /**
113      * The number of bytes in the preamble of this ZIP compatible file.
114      */

115     private long preamble;
116
117     /**
118      * The number of bytes in the postamble of this ZIP compatible file.
119      */

120     private long postamble;
121
122     private OffsetMapper mapper;
123
124     /**
125      * Opens the given file for reading its ZIP contents,
126      * assuming UTF-8 encoding for file names.
127      *
128      * @param name name of the file.
129      *
130      * @throws NullPointerException If <code>name</code> is <code>null</code>.
131      * @throws FileNotFoundException If the file cannot get opened for reading.
132      * @throws ZipException If the file is not ZIP compatible.
133      * @throws IOException On any other I/O related issue.
134      */

135     public BasicZipFile(String JavaDoc name)
136     throws NullPointerException JavaDoc,
137             FileNotFoundException,
138             ZipException,
139             IOException {
140         this.encoding = DEFAULT_ENCODING;
141         try {
142             init(null, new File(name), true, false);
143         } catch (UnsupportedEncodingException cannotHappen) {
144             throw new AssertionError JavaDoc(cannotHappen);
145         }
146     }
147
148     /**
149      * Opens the given file for reading its ZIP contents,
150      * assuming the specified encoding for file names.
151      *
152      * @param name name of the file.
153      * @param encoding the encoding to use for file names
154      *
155      * @throws NullPointerException If <code>name</code> or <code>encoding</code> is
156      * <code>null</code>.
157      * @throws UnsupportedEncodingException If encoding is not supported by
158      * this JVM.
159      * @throws FileNotFoundException If the file cannot get opened for reading.
160      * @throws ZipException If the file is not ZIP compatible.
161      * @throws IOException On any other I/O related issue.
162      */

163     public BasicZipFile(String JavaDoc name, String JavaDoc encoding)
164     throws NullPointerException JavaDoc,
165             UnsupportedEncodingException,
166             FileNotFoundException,
167             ZipException,
168             IOException {
169         this.encoding = encoding;
170         init(null, new File(name), true, false);
171     }
172
173     /**
174      * Opens the given file for reading its ZIP contents,
175      * assuming the specified encoding for file names.
176      *
177      * @param name name of the file.
178      * @param encoding the encoding to use for file names
179      * @param preambled If this is <code>true</code>, then the ZIP compatible
180      * file may have a preamble.
181      * Otherwise, the ZIP compatible file must start with either a
182      * Local File Header (LFH) signature or an End Of Central Directory
183      * (EOCD) Header, causing this constructor to fail fast if the file
184      * is actually a false positive ZIP compatible file, i.e. not
185      * compatible to the ZIP File Format Specification.
186      * This may be used to read Self Extracting ZIP files (SFX), which
187      * usually contain the application code required for extraction in
188      * a preamble.
189      * This parameter is <code>true</code> by default.
190      * @param postambled If this is <code>true</code>, then the ZIP compatible
191      * file may have a postamble of arbitrary length.
192      * Otherwise, the ZIP compatible file must not have a postamble
193      * which exceeds 64KB size, including the End Of Central Directory
194      * record (i.e. including the ZIP file comment), causing this
195      * constructor to fail fast if the file is actually a false positive
196      * ZIP compatible file, i.e. not compatible to the ZIP File Format
197      * Specification.
198      * This may be used to read Self Extracting ZIP files (SFX) with
199      * large postambles.
200      * This parameter is <code>false</code> by default.
201      *
202      * @throws NullPointerException If <code>name</code> or <code>encoding</code> is
203      * <code>null</code>.
204      * @throws UnsupportedEncodingException If encoding is not supported by
205      * this JVM.
206      * @throws FileNotFoundException If the file cannot get opened for reading.
207      * @throws ZipException If the file is not ZIP compatible.
208      * @throws IOException On any other I/O related issue.
209      */

210     public BasicZipFile(
211             String JavaDoc name,
212             String JavaDoc encoding,
213             boolean preambled,
214             boolean postambled)
215     throws NullPointerException JavaDoc,
216             UnsupportedEncodingException,
217             FileNotFoundException,
218             ZipException,
219             IOException {
220         this.encoding = encoding;
221         init(null, new File(name), preambled, postambled);
222     }
223
224     /**
225      * Opens the given file for reading its ZIP contents,
226      * assuming UTF-8 encoding for file names.
227      *
228      * @param file The file.
229      *
230      * @throws NullPointerException If <code>file</code> is <code>null</code>.
231      * @throws FileNotFoundException If the file cannot get opened for reading.
232      * @throws ZipException If the file is not ZIP compatible.
233      * @throws IOException On any other I/O related issue.
234      */

235     public BasicZipFile(File file)
236     throws NullPointerException JavaDoc,
237             FileNotFoundException,
238             ZipException,
239             IOException {
240         this.encoding = DEFAULT_ENCODING;
241         try {
242             init(null, file, true, false);
243         } catch (UnsupportedEncodingException cannotHappen) {
244             throw new AssertionError JavaDoc(cannotHappen);
245         }
246     }
247
248     /**
249      * Opens the given file for reading its ZIP contents,
250      * assuming the specified encoding for file names.
251      *
252      * @param file The file.
253      * @param encoding The encoding to use for entry names and comments
254      * - must <em>not</em> be <code>null</code>!
255      *
256      * @throws NullPointerException If <code>file</code> or <code>encoding</code> is
257      * <code>null</code>.
258      * @throws UnsupportedEncodingException If encoding is not supported by
259      * this JVM.
260      * @throws FileNotFoundException If the file cannot get opened for reading.
261      * @throws ZipException If the file is not ZIP compatible.
262      * @throws IOException On any other I/O related issue.
263      */

264     public BasicZipFile(File file, String JavaDoc encoding)
265     throws NullPointerException JavaDoc,
266             UnsupportedEncodingException,
267             FileNotFoundException,
268             ZipException,
269             IOException {
270         this.encoding = encoding;
271         init(null, file, true, false);
272     }
273
274     /**
275      * Opens the given file for reading its ZIP contents,
276      * assuming the specified encoding for file names.
277      *
278      * @param file The file.
279      * @param encoding The encoding to use for entry names and comments
280      * - must <em>not</em> be <code>null</code>!
281      * @param preambled If this is <code>true</code>, then the ZIP compatible
282      * file may have a preamble.
283      * Otherwise, the ZIP compatible file must start with either a
284      * Local File Header (LFH) signature or an End Of Central Directory
285      * (EOCD) Header, causing this constructor to fail fast if the file
286      * is actually a false positive ZIP compatible file, i.e. not
287      * compatible to the ZIP File Format Specification.
288      * This may be used to read Self Extracting ZIP files (SFX), which
289      * usually contain the application code required for extraction in
290      * a preamble.
291      * This parameter is <code>true</code> by default.
292      * @param postambled If this is <code>true</code>, then the ZIP compatible
293      * file may have a postamble of arbitrary length.
294      * Otherwise, the ZIP compatible file must not have a postamble
295      * which exceeds 64KB size, including the End Of Central Directory
296      * record (i.e. including the ZIP file comment), causing this
297      * constructor to fail fast if the file is actually a false positive
298      * ZIP compatible file, i.e. not compatible to the ZIP File Format
299      * Specification.
300      * This may be used to read Self Extracting ZIP files (SFX) with
301      * large postambles.
302      * This parameter is <code>false</code> by default.
303      *
304      * @throws NullPointerException If <code>file</code> or <code>encoding</code> is
305      * <code>null</code>.
306      * @throws UnsupportedEncodingException If encoding is not supported by
307      * this JVM.
308      * @throws FileNotFoundException If the file cannot get opened for reading.
309      * @throws ZipException If the file is not ZIP compatible.
310      * @throws IOException On any other I/O related issue.
311      */

312     public BasicZipFile(
313             File file,
314             String JavaDoc encoding,
315             boolean preambled,
316             boolean postambled)
317     throws NullPointerException JavaDoc,
318             UnsupportedEncodingException,
319             FileNotFoundException,
320             ZipException,
321             IOException {
322         this.encoding = encoding;
323         init(null, file, preambled, postambled);
324     }
325
326     /**
327      * Opens the given read only file for reading its ZIP contents,
328      * assuming UTF-8 encoding for file names.
329      *
330      * @param rof The read only file.
331      * Note that this constructor <em>never</em> closes this file.
332      *
333      * @throws NullPointerException If <code>file</code> is <code>null</code>.
334      * @throws FileNotFoundException If the file cannot get opened for reading.
335      * @throws ZipException If the file is not ZIP compatible.
336      * @throws IOException On any other I/O related issue.
337      */

338     public BasicZipFile(ReadOnlyFile rof)
339     throws NullPointerException JavaDoc,
340             FileNotFoundException,
341             ZipException,
342             IOException {
343         this.encoding = DEFAULT_ENCODING;
344         try {
345             init(rof, null, true, false);
346         } catch (UnsupportedEncodingException cannotHappen) {
347             throw new AssertionError JavaDoc(cannotHappen);
348         }
349     }
350
351     /**
352      * Opens the given read only file for reading its ZIP contents,
353      * assuming the specified encoding for file names.
354      *
355      * @param rof The read only file.
356      * Note that this constructor <em>never</em> closes this file.
357      * @param encoding The encoding to use for entry names and comments
358      * - must <em>not</em> be <code>null</code>!
359      *
360      * @throws NullPointerException If <code>file</code> or <code>encoding</code> is
361      * <code>null</code>.
362      * @throws UnsupportedEncodingException If encoding is not supported by
363      * this JVM.
364      * @throws FileNotFoundException If the file cannot get opened for reading.
365      * @throws ZipException If the file is not ZIP compatible.
366      * @throws IOException On any other I/O related issue.
367      */

368     public BasicZipFile(ReadOnlyFile rof, String JavaDoc encoding)
369     throws NullPointerException JavaDoc,
370             UnsupportedEncodingException,
371             FileNotFoundException,
372             ZipException,
373             IOException {
374         this.encoding = encoding;
375         init(rof, null, true, false);
376     }
377
378     /**
379      * Opens the given read only file for reading its ZIP contents,
380      * assuming the specified encoding for file names.
381      *
382      * @param rof The read only file.
383      * Note that this constructor <em>never</em> closes this file.
384      * @param encoding The encoding to use for entry names and comments
385      * - must <em>not</em> be <code>null</code>!
386      * @param preambled If this is <code>true</code>, then the ZIP compatible
387      * file may have a preamble.
388      * Otherwise, the ZIP compatible file must start with either a
389      * Local File Header (LFH) signature or an End Of Central Directory
390      * (EOCD) Header, causing this constructor to fail fast if the file
391      * is actually a false positive ZIP compatible file, i.e. not
392      * compatible to the ZIP File Format Specification.
393      * This may be used to read Self Extracting ZIP files (SFX), which
394      * usually contain the application code required for extraction in
395      * a preamble.
396      * This parameter is <code>true</code> by default.
397      * @param postambled If this is <code>true</code>, then the ZIP compatible
398      * file may have a postamble of arbitrary length.
399      * Otherwise, the ZIP compatible file must not have a postamble
400      * which exceeds 64KB size, including the End Of Central Directory
401      * record (i.e. including the ZIP file comment), causing this
402      * constructor to fail fast if the file is actually a false positive
403      * ZIP compatible file, i.e. not compatible to the ZIP File Format
404      * Specification.
405      * This may be used to read Self Extracting ZIP files (SFX) with
406      * large postambles.
407      * This parameter is <code>false</code> by default.
408      *
409      * @throws NullPointerException If <code>file</code> or <code>encoding</code> is
410      * <code>null</code>.
411      * @throws UnsupportedEncodingException If encoding is not supported by
412      * this JVM.
413      * @throws FileNotFoundException If the file cannot get opened for reading.
414      * @throws ZipException If the file is not ZIP compatible.
415      * @throws IOException On any other I/O related issue.
416      */

417     public BasicZipFile(
418             ReadOnlyFile rof,
419             String JavaDoc encoding,
420             boolean preambled,
421             boolean postambled)
422     throws NullPointerException JavaDoc,
423             UnsupportedEncodingException,
424             FileNotFoundException,
425             ZipException,
426             IOException {
427         this.encoding = encoding;
428         init(rof, null, preambled, postambled);
429     }
430
431     private void init(
432             ReadOnlyFile rof,
433             final File file,
434             final boolean preambled,
435             final boolean postambled)
436     throws NullPointerException JavaDoc,
437             UnsupportedEncodingException,
438             FileNotFoundException,
439             ZipException,
440             IOException {
441         // Check parameters (fail fast).
442
if (encoding == null)
443             throw new NullPointerException JavaDoc("encoding");
444         new String JavaDoc(new byte[0], encoding); // may throw UnsupportedEncodingException!
445
if (rof == null) {
446             if (file == null)
447                 throw new NullPointerException JavaDoc();
448             rof = createReadOnlyFile(file);
449         } else { // rof != null
450
assert file == null;
451         }
452         archive = rof;
453
454         try {
455             final BufferedReadOnlyFile brof;
456             if (archive instanceof BufferedReadOnlyFile)
457                 brof = (BufferedReadOnlyFile) archive;
458             else
459                 brof = new BufferedReadOnlyFile(archive);
460             mountCentralDirectory(brof, preambled, postambled);
461             // Do NOT close brof - would close rof as well!
462
} catch (IOException failure) {
463             if (file != null)
464                 rof.close();
465             throw failure;
466         }
467         
468         assert mapper != null;
469     }
470
471     /**
472      * A factory method called by the constructor to get a read only file
473      * to access the contents of the ZIP file.
474      * This method is only used if the constructor isn't called with a read
475      * only file as its parameter.
476      *
477      * @throws FileNotFoundException If the file cannot get opened for reading.
478      * @throws IOException On any other I/O related issue.
479      */

480     protected ReadOnlyFile createReadOnlyFile(File file)
481     throws FileNotFoundException, IOException {
482         return new SimpleReadOnlyFile(file);
483     }
484
485     /**
486      * Reads the central directory of the given file and populates
487      * the internal tables with ZipEntry instances.
488      * <p>
489      * The ZipEntrys will know all data that can be obtained from
490      * the central directory alone, but not the data that requires the
491      * local file header or additional data to be read.
492      *
493      * @throws ZipException If the file is not ZIP compatible.
494      * @throws IOException On any other I/O related issue.
495      */

496     private void mountCentralDirectory(
497             final ReadOnlyFile rof,
498             final boolean preambled,
499             final boolean postambled)
500     throws ZipException, IOException {
501         int numEntries = findCentralDirectory(rof, preambled, postambled);
502         assert mapper != null;
503
504         preamble = Long.MAX_VALUE;
505
506         final byte[] sig = new byte[4];
507         final byte[] cfh = new byte[CFH_MIN_LEN - sig.length];
508         for (; ; numEntries--) {
509             rof.readFully(sig);
510             if (readUInt(sig) != CFH_SIG)
511                 break;
512
513             rof.readFully(cfh);
514             final int entryNameLen = readUShort(cfh, 24);
515             final byte[] entryName = new byte[entryNameLen];
516             rof.readFully(entryName);
517
518             final ZipEntry ze = createZipEntry(new String JavaDoc(entryName, encoding));
519             try {
520                 int off = 0;
521
522                 final int versionMadeBy = readUShort(cfh, off);
523                 off += 2;
524                 ze.setPlatform((short) ((versionMadeBy >> 8) & 0xFF));
525
526                 off += 4; // skip version info and general purpose byte
527

528                 ze.setMethod((short) readUShort(cfh, off));
529                 off += 2;
530
531                 ze.setDosTime(readUInt(cfh, off));
532                 off += 4;
533
534                 ze.setCrc(readUInt(cfh, off));
535                 off += 4;
536
537                 ze.setCompressedSize(readUInt(cfh, off));
538                 off += 4;
539
540                 ze.setSize(readUInt(cfh, off));
541                 off += 4;
542
543                 off += 2; // file name length
544

545                 final int extraLen = readUShort(cfh, off);
546                 off += 2;
547
548                 final int commentLen = readUShort(cfh, off);
549                 off += 2;
550
551                 off += 2; // disk number
552

553                 //ze.setInternalAttributes(readUShort(cfh, off));
554
off += 2;
555
556                 //ze.setExternalAttributes(readUInt(cfh, off));
557
off += 4;
558
559                 // Local file header offset.
560
final long lfhOff = mapper.location(readUInt(cfh, off));
561
562                 // Set MSB in entry offset in order to indicate that
563
// getInputStream(*) should resolve this.
564
// Note that the result can never be -1 as a long value.
565
ze.offset = lfhOff | LONG_MSB;
566                 
567                 // Update preamble size conditionally.
568
if (lfhOff < preamble)
569                     preamble = lfhOff;
570
571                 entries.put(ze.getName(), ze);
572
573                 if (extraLen > 0) {
574                     final byte[] extra = new byte[extraLen];
575                     rof.readFully(extra);
576                     ze.setExtra(extra);
577                 }
578
579                 if (commentLen > 0) {
580                     final byte[] comment = new byte[commentLen];
581                     rof.readFully(comment);
582                     ze.setComment(new String JavaDoc(comment, encoding));
583                 }
584             } catch (IllegalArgumentException JavaDoc incompatibleZipFile) {
585                 final ZipException exc = new ZipException(ze.getName());
586                 exc.initCause(incompatibleZipFile);
587                 throw exc;
588             }
589         }
590
591         // Check if the number of entries found matches the number of entries
592
// declared in the End Of Central Directory header.
593
// If this is a (possibly negative) multiple of 65536, then the
594
// number of entries stored in the ZIP file exceeds the maximum
595
// number of 65535 entries supported by the ZIP File Format
596
// Specification (a two byte unsigned integer).
597
// Although beyond the spec, we silently tolerate this.
598
// Thanks to Jean-Francois Thamie for submitting this issue!
599
if (numEntries % 65536 != 0)
600             throw new ZipException(
601                     "Not a ZIP compatible file: Expected " +
602                     Math.abs(numEntries) +
603                     (numEntries > 0 ? " more" : " less") +
604                     " entries in the Central Directory!");
605
606         if (preamble == Long.MAX_VALUE)
607             preamble = 0;
608     }
609
610     /**
611      * Searches for the &quot;End of central dir record&quot;, parses
612      * it and positions the file pointer at the first central directory
613      * record.
614      * Performs some means to check that this is really a ZIP compatible
615      * file.
616      * <p>
617      * As a side effect, both <code>mapper</code> and </code>postamble</code>
618      * will be set.
619      *
620      * @throws ZipException If the file is not ZIP compatible.
621      * @throws IOException On any other I/O related issue.
622      */

623     private int findCentralDirectory(
624             final ReadOnlyFile rof,
625             boolean preambled,
626             final boolean postambled)
627     throws ZipException, IOException {
628         final byte[] sig = new byte[4];
629         if (!preambled) {
630             rof.seek(0);
631             rof.readFully(sig);
632             final long signature = readUInt(sig);
633             // Constraint: A ZIP file must start with a Local File Header (LFH)
634
// or an End Of Central Directory (EOCD) record in case it's emtpy.
635
preambled = signature == LFH_SIG || signature == EOCD_SIG;
636         }
637         if (preambled) {
638             final long length = rof.length();
639             final long max = length - EOCD_MIN_LEN;
640             final long min = !postambled && max >= 0xFFFF ? max - 0xFFFF : 0;
641             for (long eocdOff = max; eocdOff >= min; eocdOff--) {
642                 rof.seek(eocdOff);
643                 rof.readFully(sig);
644                 if (readUInt(sig) != EOCD_SIG)
645                     continue;
646                 
647                 // Process EOCD.
648
final byte[] eocd = new byte[EOCD_MIN_LEN - sig.length];
649                 rof.readFully(eocd);
650                 final int numEntries = readUShort(eocd, EOCD_NUM_ENTRIES_OFFSET - sig.length);
651                 final long cdSize = readUInt(eocd, EOCD_CD_SIZE_OFFSET - sig.length);
652                 final long cdLoc = readUInt(eocd, EOCD_CD_LOCATION_OFFSET - sig.length);
653                 final int commentLen = readUShort(eocd, EOCD_COMMENT_OFFSET - sig.length);
654                 if (commentLen > 0) {
655                     final byte[] comment = new byte[commentLen];
656                     rof.readFully(comment);
657                     setComment(new String JavaDoc(comment, encoding));
658                 }
659                 postamble = length - rof.getFilePointer();
660                 
661                 // Seek and check first CFH, probably using an offset mapper.
662
long start = eocdOff - cdSize;
663                 rof.seek(start);
664                 start -= cdLoc;
665                 if (start != 0) {
666                     mapper = new IrregularOffsetMapper(start);
667                 } else {
668                     mapper = new OffsetMapper();
669                 }
670                 
671                 return numEntries;
672             }
673         }
674         throw new ZipException(
675                 "Not a ZIP compatible file: End Of Central Directory signature is missing!");
676     }
677
678     /**
679      * A factory method returning a newly created ZipEntry for the given name.
680      */

681     protected ZipEntry createZipEntry(String JavaDoc name) {
682         return new ZipEntry(name);
683     }
684
685     /**
686      * Returns the comment of this ZIP compatible file or <code>null</code>
687      * if no comment exists.
688      */

689     public String JavaDoc getComment() {
690         return comment;
691     }
692     
693     private void setComment(String JavaDoc comment) {
694         this.comment = comment;
695     }
696
697     /**
698      * Returns <code>true</code> if and only if some input streams are open to
699      * read from this ZIP compatible file.
700      */

701     public boolean busy() {
702         return openStreams > 0;
703     }
704
705     /**
706      * Returns the encoding to use for filenames and the file comment.
707      */

708     public String JavaDoc getEncoding() {
709         return encoding;
710     }
711
712     /**
713      * Returns an enumeration of the ZIP entries in this ZIP file.
714      * Note that the enumerated entries are shared with this class.
715      * It is illegal to change their state!
716      */

717     public Enumeration entries() {
718         return Collections.enumeration(entries.values());
719     }
720
721     /**
722      * Returns the {@link ZipEntry} for the given name or
723      * <code>null</code> if no entry with that name exists.
724      * Note that the returned entry is shared with this class.
725      * It is illegal to change its state!
726      *
727      * @param name Name of the ZIP entry.
728      */

729     public ZipEntry getEntry(String JavaDoc name) {
730         return (ZipEntry) entries.get(name);
731     }
732
733     /**
734      * Returns the number of entries in this ZIP compatible file.
735      */

736     public int size() {
737     return entries.size();
738     }
739
740     /**
741      * Returns the file length of this ZIP compatible file in bytes.
742      */

743     public long length() throws IOException {
744         return archive.length();
745     }
746
747     /**
748      * Returns the length of the preamble of this ZIP compatible file in bytes.
749      *
750      * @return A positive value or zero to indicate that this ZIP compatible
751      * file does not have a preamble.
752      *
753      * @since TrueZIP 5.1
754      */

755     public long getPreambleLength() {
756         return preamble;
757     }
758     
759     /**
760      * Returns an {@link InputStream} to read the preamble of this ZIP
761      * compatible file.
762      * <p>
763      * Note that the returned stream is a <i>lightweight</i> stream,
764      * i.e. there is no external resource such as a {@link ReadOnlyFile}
765      * allocated for it. Instead, all streams returned by this method share
766      * the underlying <code>ReadOnlyFile</code> of this <code>ZipFile</code>.
767      * This allows to close this object (and hence the underlying
768      * <code>ReadOnlyFile</code>) without cooperation of the returned
769      * streams, which is important if the application wants to work on the
770      * underlying file again (e.g. update or delete it).
771      *
772      * @since TrueZIP 5.1
773      * @throws ZipException If this ZIP file has been closed.
774      */

775     public InputStream getPreambleInputStream() throws IOException {
776         ensureOpen();
777         return new IntervalInputStream(0, preamble);
778     }
779
780     /**
781      * Returns the length of the postamble of this ZIP compatible file in bytes.
782      *
783      * @return A positive value or zero to indicate that this ZIP compatible
784      * file does not have an postamble.
785      *
786      * @since TrueZIP 5.1
787      */

788     public long getPostambleLength() {
789         return postamble;
790     }
791     
792     /**
793      * Returns an {@link InputStream} to read the postamble of this ZIP
794      * compatible file.
795      * <p>
796      * Note that the returned stream is a <i>lightweight</i> stream,
797      * i.e. there is no external resource such as a {@link ReadOnlyFile}
798      * allocated for it. Instead, all streams returned by this method share
799      * the underlying <code>ReadOnlyFile</code> of this <code>ZipFile</code>.
800      * This allows to close this object (and hence the underlying
801      * <code>ReadOnlyFile</code>) without cooperation of the returned
802      * streams, which is important if the application wants to work on the
803      * underlying file again (e.g. update or delete it).
804      *
805      * @since TrueZIP 5.1
806      * @throws ZipException If this ZIP file has been closed.
807      */

808     public InputStream getPostambleInputStream() throws IOException {
809         ensureOpen();
810         return new IntervalInputStream(archive.length() - postamble, postamble);
811     }
812
813     /**
814      * Returns <code>true</code> if and only if the offsets in this ZIP file
815      * are relative to the start of the file, rather than the first Local
816      * File Header.
817      * <p>
818      * This method is intended for very special purposes only.
819      */

820     public boolean offsetsConsiderPreamble() {
821         assert mapper != null;
822         return mapper.location(0) == 0;
823     }
824
825     /**
826      * Equivalent to {@link #getCheckedInputStream(String)
827      * getCheckedInputStream(entry.getName())}.
828      *
829      * @since TrueZIP 6.0
830      */

831     public final InputStream getCheckedInputStream(final ZipEntry entry)
832     throws IOException {
833         return getInputStream(entry.getName(), true, true);
834     }
835
836     /**
837      * Equivalent to {@link #getInputStream(String, boolean)
838      * getInputStream(name, true)}, but also checks the CRC-32 checksum.
839      * <p>
840      * If there is a mismatch of the CRC-32 values for the ZIP entry,
841      * the {@link InputStream#close} method of the returned input stream will
842      * throw a {@link CRC32Exception}.
843      * Other than this, the archive entry will be processed normally.
844      * So if just the CRC-32 value for the entry in the archive file has been
845      * modified, you can still read its contents.
846      *
847      * @param name The name of the entry to get the stream for
848      * - may <em>not</em> be <code>null</code>!
849      * @return A stream to read the entry data from or <code>null</code> if the
850      * entry does not exist.
851      * @throws NullPointerException If <code>name</code> is <code>null</code>.
852      * @throws IOException If the entry cannot get read from this ZipFile.
853      * @since TrueZIP 6.1
854      */

855     public final InputStream getCheckedInputStream(final String JavaDoc name)
856     throws IOException {
857         return getInputStream(name, true, true);
858     }
859
860     /**
861      * Equivalent to {@link #getInputStream(String, boolean)
862      * getInputStream(entry.getName(), true)}.
863      */

864     public final InputStream getInputStream(ZipEntry entry)
865     throws IOException {
866         return getInputStream(entry.getName(), false, true);
867     }
868
869     /**
870      * Equivalent to {@link #getInputStream(String, boolean)
871      * getInputStream(entry.getName(), inflate)}.
872      *
873      * @deprecated Client classes should have no need to use this method.
874      */

875     public final InputStream getInputStream(ZipEntry entry, boolean inflate)
876     throws IOException {
877         return getInputStream(entry.getName(), false, inflate);
878     }
879     
880     /**
881      * Equivalent to {@link #getInputStream(String, boolean)
882      * getInputStream(name, true)}.
883      */

884     public final InputStream getInputStream(String JavaDoc name)
885     throws IOException {
886         return getInputStream(name, false, true);
887     }
888     
889     /**
890      * Returns an <code>InputStream</code> for reading the contents of the
891      * given entry.
892      * <p>
893      * The returned stream is a <i>lightweight</i> stream,
894      * i.e. there is no external resource such as a {@link ReadOnlyFile}
895      * allocated for it. Instead, all streams returned by this method share
896      * the underlying <code>ReadOnlyFile</code> of this <code>ZipFile</code>.
897      * This allows to close this object (and hence the underlying
898      * <code>ReadOnlyFile</code>) without cooperation of the returned
899      * streams, which is important if the application wants to work on the
900      * underlying file again (e.g. update or delete it).
901      * <p>
902      * For performance reasons, the CRC-32 checksum of the entry is
903      * <em>not</em> validated.
904      * If you need to validate the CRC-32 checksum, you could either use the
905      * <code>crc32</code> property of the provided entry directly or call
906      * {@link #getCheckedInputStream} instead.
907      *
908      * @deprecated Client classes should have no need to use this method.
909      * @param name The name of the entry to get the stream for
910      * - may <em>not</em> be <code>null</code>!
911      * @param inflate Whether or not the entry data should be inflated.
912      * If <code>false</code>, the entry data is not inflated,
913      * even if the entry data is deflated.
914      * Should be <code>true</code> for most applications.
915      * @return A stream to read the entry data from or <code>null</code> if the
916      * entry does not exist.
917      * @throws NullPointerException If <code>name</code> is <code>null</code>.
918      * @throws IOException If the entry cannot get read from this ZipFile.
919      */

920     public InputStream getInputStream(
921             final String JavaDoc name,
922             final boolean inflate)
923     throws IOException {
924         return getInputStream(name, false, inflate);
925     }
926
927     /**
928      * Returns an <code>InputStream</code> for reading the contents of the
929      * given entry.
930      * This method is called by all <code>get(Checked)?InputStream</code>
931      * methods.
932      * <p>
933      * The returned stream is an unsynchronized, <i>lightweight</i> stream,
934      * i.e. there is no external resource such as a {@link ReadOnlyFile}
935      * allocated for it. Instead, all streams returned by this method share
936      * the underlying <code>ReadOnlyFile</code> of this <code>ZipFile</code>.
937      * This allows to close this object (and hence the underlying
938      * <code>ReadOnlyFile</code>) without cooperation of the returned
939      * streams, which is important if the application wants to work on the
940      * underlying file again (e.g. update or delete it).
941      * <p>
942      * Note that this class is not thread safe, so you can't use the returned
943      * threads in multiple streams.
944      * Please refer to {@link ZipFile} for a thread safe version of this class.
945      *
946      * If there is a mismatch of the CRC-32 values for the ZIP entry,
947      * the {@link InputStream#close} method of the returned input stream will
948      * throw a {@link CRC32Exception}.
949      * Other than this, the archive entry will be processed normally.
950      * So if just the CRC-32 value for the entry in the archive file has been
951      * modified, you can still read its contents.
952      *
953      * @param name The name of the entry to get the stream for
954      * - may <em>not</em> be <code>null</code>!
955      * @param check Whether or not the entry's CRC-32 value is checked.
956      * If and only if this parameter is true, the returned input
957      * stream computes the CRC-32 value over the inflated data
958      * (regardless of the <code>inflate</code> parameter).
959      * Then if the computed value does not match the value as declared
960      * in the ZIP file, then the {@link InputStream#close} method
961      * will throw a {@link CRC32Exception}.
962      * Other than this, the archive entry will be processed normally and
963      * you can always read the entire entry data, inflated or deflated.
964      * Should be <code>false</code> for most applications (this is the
965      * default for the sibling of this class in {@link java.util.zip.ZipFile
966      * java.util.zip.ZipFile}).
967      * @param inflate Whether or not the entry data should be inflated.
968      * If <code>false</code>, the entry data is not inflated,
969      * even if the entry data is deflated.
970      * Should be <code>true</code> for most applications.
971      * @return A stream to read the entry data from or <code>null</code> if the
972      * entry does not exist.
973      * @throws NullPointerException If <code>name</code> is <code>null</code>.
974      * @throws IOException If the entry cannot get read from this ZipFile.
975      * @since TrueZIP 6.4
976      */

977     protected InputStream getInputStream(
978             final String JavaDoc name,
979             final boolean check,
980             final boolean inflate)
981     throws ZipException,
982             IOException {
983         if (name == null)
984             throw new NullPointerException JavaDoc();
985
986         ensureOpen();
987         final ZipEntry entry = (ZipEntry) entries.get(name);
988         if (entry == null)
989             return null;
990
991         long offset = entry.offset;
992         assert offset != -1;
993         if (offset < 0) {
994             // This offset has been set by mountCentralDirectory()
995
// and needs to be resolved first.
996
offset &= ~LONG_MSB; // Switch off MSB.
997
archive.seek(offset);
998             final byte[] lfh = new byte[LFH_MIN_LEN];
999             archive.readFully(lfh);
1000            final long lfhSig = readUInt(lfh);
1001            if (lfhSig != LFH_SIG)
1002                throw new ZipException(name + ": Local File Header signature expected!");
1003            offset += LFH_MIN_LEN
1004                    + readUShort(lfh, LFH_FILE_NAME_LENGTH_OFFSET) // file name length
1005
+ readUShort(lfh, LFH_FILE_NAME_LENGTH_OFFSET + 2); // extra field length
1006
entry.offset = offset;
1007        }
1008
1009        final IntervalInputStream iis
1010                = new IntervalInputStream(offset, entry.getCompressedSize());
1011        final int bufSize = getBufferSize(entry);
1012        InputStream in;
1013        switch (entry.getMethod()) {
1014            case ZipEntry.DEFLATED:
1015                if (inflate) {
1016                    iis.addDummy();
1017                    in = new PooledInflaterInputStream(iis, bufSize);
1018                    if (check)
1019                        in = new CheckedInputStream(in, entry, bufSize);
1020                    break;
1021                } else {
1022                    in = check
1023                            ? new RawCheckedInputStream(iis, entry, bufSize)
1024                            : (InputStream) iis;
1025                }
1026                break;
1027
1028            case ZipEntry.STORED:
1029                in = check
1030                        ? new CheckedInputStream(iis, entry, bufSize)
1031                        : (InputStream) iis;
1032                break;
1033
1034            default:
1035                throw new ZipException(name + ": " + entry.getMethod()
1036                        + ": Unsupported compression method!");
1037        }
1038
1039        return in;
1040    }
1041
1042    private static final int getBufferSize(final ZipEntry entry) {
1043        long size = entry.getSize();
1044        if (size > FLATER_BUF_LENGTH)
1045            size = FLATER_BUF_LENGTH;
1046        else if (size < FLATER_BUF_LENGTH / 8)
1047            size = FLATER_BUF_LENGTH / 8;
1048        return (int) size;
1049    }
1050
1051    /**
1052     * Ensures that this archive is still open.
1053     */

1054    private final void ensureOpen() throws ZipException {
1055        if (archive == null)
1056            throw new ZipException("ZipFile has been closed!");
1057    }
1058
1059    private static final class PooledInflaterInputStream
1060            extends InflaterInputStream {
1061        private boolean closed;
1062
1063        public PooledInflaterInputStream(InputStream in, int size) {
1064            super(in, allocateInflater(), size);
1065        }
1066
1067        public void close() throws IOException {
1068            if (!closed) {
1069                closed = true;
1070                try {
1071                    super.close();
1072                } finally {
1073                    releaseInflater(inf);
1074                }
1075            }
1076        }
1077    } // class PooledInflaterInputStream
1078

1079    private static Inflater allocateInflater() {
1080        Inflater inflater = null;
1081
1082        synchronized (releasedInflaters) {
1083            for (Iterator i = releasedInflaters.iterator(); i.hasNext(); ) {
1084                inflater = (Inflater) ((Reference) i.next()).get();
1085                i.remove();
1086                if (inflater != null) {
1087                    inflater.reset();
1088                    break;
1089                }
1090            }
1091            if (inflater == null)
1092                inflater = new Inflater(true);
1093
1094            // We MUST make sure that we keep a strong reference to the
1095
// inflater in order to retain it from being released again and
1096
// then finalized when the close() method of the InputStream
1097
// returned by getInputStream(...) is called from within another
1098
// finalizer.
1099
// The finalizer of the inflater calls end() and leaves the object
1100
// in a state so that the subsequent call to reset() throws an NPE.
1101
// The ZipFile class in Sun's J2SE 1.4.2 shows this bug.
1102
allocatedInflaters.add(inflater);
1103        }
1104        
1105        return inflater;
1106    }
1107
1108    private static void releaseInflater(Inflater inflater) {
1109        //inflater.end(); // TODO: Review: Do we need to call this?
1110
synchronized (releasedInflaters) {
1111            releasedInflaters.add(new SoftReference(inflater));
1112            allocatedInflaters.remove(inflater);
1113        }
1114    }
1115
1116    private static final class CheckedInputStream
1117            extends java.util.zip.CheckedInputStream JavaDoc {
1118        private final ZipEntry entry;
1119        private final int size;
1120
1121        public CheckedInputStream(
1122                final InputStream in,
1123                final ZipEntry entry,
1124                final int size) {
1125            super(in, new CRC32());
1126            this.entry = entry;
1127            this.size = size;
1128        }
1129
1130        public long skip(long toSkip) throws IOException {
1131            return skipWithBuffer(this, toSkip, new byte[size]);
1132        }
1133
1134        public void close() throws IOException {
1135            try {
1136                skip(Long.MAX_VALUE); // process CRC-32 until EOF
1137
} finally {
1138                super.close();
1139            }
1140            long expectedCrc = entry.getCrc();
1141            long actualCrc = getChecksum().getValue();
1142            if (expectedCrc != actualCrc)
1143                throw new CRC32Exception(
1144                        entry.getName(), expectedCrc, actualCrc);
1145        }
1146    } // class CheckedInputStream
1147

1148    /**
1149     * This method skips <code>toSkip</code> bytes in the given input stream
1150     * using the given buffer unless EOF or IOException.
1151     */

1152    private static long skipWithBuffer(
1153            final InputStream in,
1154            long toSkip,
1155            final byte[] buf)
1156    throws IOException {
1157        long total = 0;
1158        while (toSkip > 0) {
1159            // Read while computing the CRC-32.
1160
final int skipped = in.read(buf, 0,
1161                    toSkip < buf.length ? (int) toSkip : buf.length);
1162            if (skipped < 0)
1163                return total;
1164            total += skipped;
1165            toSkip -= skipped;
1166        }
1167        return total;
1168    }
1169
1170    /**
1171     * An stream which reads and returns deflated data from its input
1172     * while a CRC-32 checksum is computed over the inflated data and
1173     * checked in the close() method.
1174     */

1175    private static final class RawCheckedInputStream extends FilterInputStream {
1176
1177        private final Checksum checksum = new CRC32();
1178        private final byte[] singleByteBuf = new byte[1];
1179        private final Inflater inf;
1180        private final byte[] infBuf; // contains inflated data!
1181
private final ZipEntry entry;
1182        private boolean closed;
1183
1184        public RawCheckedInputStream(
1185                final InputStream in,
1186                final ZipEntry entry,
1187                final int size) {
1188            super(in);
1189            this.inf = allocateInflater();
1190            this.infBuf = new byte[size];
1191            this.entry = entry;
1192        }
1193
1194        private void ensureOpen() throws IOException {
1195            if (closed)
1196                throw new IOException("Input stream has been closed!");
1197        }
1198
1199        public int read() throws IOException {
1200            int read;
1201            while ((read = read(singleByteBuf, 0, 1)) == 0) // reading nothing is not acceptible!
1202
;
1203            return read < 0 ? -1 : singleByteBuf[0] & 0xFF;
1204        }
1205
1206        public int read(byte[] buf, int off, int len) throws IOException {
1207            // Check parameters.
1208
if (buf == null)
1209                throw new NullPointerException JavaDoc();
1210            final int offPlusLen = off + len;
1211            if ((off | len | offPlusLen | buf.length - offPlusLen) < 0)
1212                throw new IndexOutOfBoundsException JavaDoc();
1213            if (len == 0)
1214                return 0;
1215
1216            // Check state.
1217
ensureOpen();
1218
1219            // Check invariants.
1220
assert inf.finished() || inf.needsInput();
1221            assert !inf.needsDictionary();
1222
1223            // Read data.
1224
final int read = in.read(buf, off, len);
1225            if (read < 0) {
1226                // Check (more restrictive) invariants.
1227
assert inf.finished();
1228                assert inf.needsInput();
1229                assert !inf.needsDictionary();
1230                return read;
1231            }
1232
1233            // Inflate and update checksum.
1234
inf.setInput(buf, off, read);
1235            try {
1236                int inflated;
1237                while ((inflated = inf.inflate(infBuf, 0, infBuf.length)) > 0)
1238                    checksum.update(infBuf, 0, inflated);
1239            } catch (DataFormatException failure) {
1240                IOException ioe = new IOException(failure.getLocalizedMessage());
1241                ioe.initCause(failure);
1242                throw ioe;
1243            }
1244
1245            // Check invariants.
1246
assert inf.finished() || inf.needsInput();
1247            assert !inf.needsDictionary();
1248
1249            return read;
1250        }
1251
1252        public long skip(long toSkip) throws IOException {
1253            return skipWithBuffer(this, toSkip, new byte[infBuf.length]);
1254        }
1255
1256        public void close() throws IOException {
1257            if (closed)
1258                return;
1259
1260            // Order is important!
1261
try {
1262                skip(Long.MAX_VALUE); // process CRC-32 until EOF
1263
} finally {
1264                closed = true;
1265                releaseInflater(inf);
1266                super.close();
1267            }
1268
1269            long expectedCrc = entry.getCrc();
1270            long actualCrc = checksum.getValue();
1271            if (expectedCrc != actualCrc)
1272                throw new CRC32Exception(
1273                        entry.getName(), expectedCrc, actualCrc);
1274        }
1275
1276        public synchronized void mark(int readlimit) {
1277        }
1278
1279        public synchronized void reset() throws IOException {
1280            throw new IOException("mark()/reset() not supported!");
1281        }
1282
1283        public boolean markSupported() {
1284            return false;
1285        }
1286    } // class CheckedDeflatedInputStream
1287

1288    /**
1289     * Closes the file.
1290     * This closes any open input streams reading from this ZIP file.
1291     *
1292     * @throws IOException if an error occurs closing the file.
1293     */

1294    public void close() throws IOException {
1295        // Order is important here!
1296
if (archive != null) {
1297            final ReadOnlyFile oldArchive = archive;
1298            archive = null;
1299            oldArchive.close();
1300        }
1301    }
1302
1303    private static final int readUShort(final byte[] bytes) {
1304        return readUShort(bytes, 0);
1305    }
1306
1307    private static final int readUShort(final byte[] bytes, final int off) {
1308        return ((bytes[off + 1] & 0xFF) << 8) | (bytes[off] & 0xFF);
1309    }
1310
1311    private static final long readUInt(final byte[] bytes) {
1312        return readUInt(bytes, 0);
1313    }
1314    
1315    private static final long readUInt(final byte[] bytes, int off) {
1316        off += 3;
1317        long v = bytes[off--] & 0xFFl;
1318        v <<= 8;
1319        v |= bytes[off--] & 0xFFl;
1320        v <<= 8;
1321        v |= bytes[off--] & 0xFFl;
1322        v <<= 8;
1323        v |= bytes[off] & 0xFFl;
1324        return v;
1325    }
1326
1327    /**
1328     * InputStream that delegates requests to the underlying
1329     * RandomAccessFile, making sure that only bytes from a certain
1330     * range can be read.
1331     * This design of this class makes the ZipFile class thread safe,
1332     * i.e. multiple threads may safely retrieve individual InputStreams.
1333     * It also allows to call close() on the ZipFile, thereby closing all
1334     * input streams reading from it, which is important in the context of
1335     * TrueZIP's high level API.
1336     */

1337    private class IntervalInputStream extends AccountedInputStream {
1338        private long remaining;
1339        private long fp;
1340        private boolean addDummyByte;
1341
1342        /**
1343         * @param start The start address (not offset) in <code>archive</code>.
1344         * @param remaining The remaining bytes allowed to be read in
1345         * <code>archive</code>.
1346         */

1347        IntervalInputStream(long start, long remaining) {
1348            assert start >= 0;
1349            assert remaining >= 0;
1350            this.remaining = remaining;
1351            fp = start;
1352        }
1353
1354        public int read() throws IOException {
1355            if (remaining <= 0) {
1356                if (addDummyByte) {
1357                    addDummyByte = false;
1358                    return 0;
1359                }
1360
1361                return -1;
1362            }
1363
1364            final int ret;
1365            ensureOpen();
1366            archive.seek(fp);
1367            ret = archive.read();
1368            if (ret >= 0) {
1369                fp++;
1370                remaining--;
1371            }
1372
1373            return ret;
1374        }
1375
1376        public int read(final byte[] b, final int off, int len)
1377        throws IOException {
1378            if (len <= 0)
1379                return 0;
1380            
1381            if (remaining <= 0) {
1382                if (addDummyByte) {
1383                    addDummyByte = false;
1384                    b[off] = 0;
1385                    return 1;
1386                }
1387
1388                return -1;
1389            }
1390
1391            if (len > remaining)
1392                len = (int) remaining;
1393            
1394
1395            final int ret;
1396            ensureOpen();
1397            archive.seek(fp);
1398            ret = archive.read(b, off, len);
1399            if (ret > 0) {
1400                fp += ret;
1401                remaining -= ret;
1402            }
1403
1404            return ret;
1405        }
1406
1407        /**
1408         * Inflater needs an extra dummy byte for nowrap - see
1409         * Inflater's javadocs.
1410         */

1411        void addDummy() {
1412            addDummyByte = true;
1413        }
1414
1415        /**
1416         * @return The number of bytes remaining in this entry, yet maximum
1417         * <code>Integer.MAX_VALUE</code>.
1418         * Note that this is only relevant for entries which have been
1419         * stored with the <code>STORED</code> method.
1420         * For entries stored according to the <code>DEFLATED</code>
1421         * method, the value returned by this method on the
1422         * <code>InputStream</code> returned by {@link #getInputStream}
1423         * is actually determined by an {@link InflaterInputStream}.
1424         */

1425        public int available() {
1426            long available = remaining;
1427            if (addDummyByte)
1428                available++;
1429            return available > Integer.MAX_VALUE
1430                    ? Integer.MAX_VALUE
1431                    : (int) available;
1432        }
1433    } // class BoundedInputStream
1434

1435    private abstract class AccountedInputStream extends InputStream {
1436        private boolean closed;
1437
1438        public AccountedInputStream() {
1439            openStreams++;
1440        }
1441
1442        public void close() throws IOException {
1443            // Order is important here!
1444
if (!closed) {
1445                closed = true;
1446                openStreams--;
1447                super.close();
1448            }
1449        }
1450
1451        protected void finalize() throws IOException {
1452            close();
1453        }
1454    };
1455
1456    private static class OffsetMapper {
1457        public long location(long offset) {
1458            return offset;
1459        }
1460    }
1461    
1462    private static class IrregularOffsetMapper extends OffsetMapper {
1463        final long start;
1464
1465        public IrregularOffsetMapper(long start) {
1466            this.start = start;
1467        }
1468
1469        public long location(long offset) {
1470            return start + offset;
1471        }
1472    }
1473}
1474
Popular Tags