KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > java > util > zip > ZipFile


1 /*
2  * @(#)ZipFile.java 1.71 05/11/21
3  *
4  * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package java.util.zip;
9
10 import java.io.InputStream JavaDoc;
11 import java.io.IOException JavaDoc;
12 import java.io.EOFException JavaDoc;
13 import java.io.File JavaDoc;
14 import java.util.Vector JavaDoc;
15 import java.util.Enumeration JavaDoc;
16 import java.util.NoSuchElementException JavaDoc;
17 import java.security.AccessController JavaDoc;
18 import java.security.PrivilegedAction JavaDoc;
19 import java.nio.ByteBuffer JavaDoc;
20 import java.nio.MappedByteBuffer JavaDoc;
21 import sun.nio.ByteBuffered;
22 import java.lang.reflect.*;
23
24 /**
25  * This class is used to read entries from a zip file.
26  *
27  * <p> Unless otherwise noted, passing a <tt>null</tt> argument to a constructor
28  * or method in this class will cause a {@link NullPointerException} to be
29  * thrown.
30  *
31  * @version 1.71, 11/21/05
32  * @author David Connelly
33  */

34 public
35 class ZipFile implements ZipConstants JavaDoc {
36     private long jzfile; // address of jzfile data
37
private String JavaDoc name; // zip file name
38
private int total; // total number of entries
39
private MappedByteBuffer JavaDoc mappedBuffer; // if ZipFile.c uses file mapping.
40
private ZipCloser closer; // cleans up after mappedBuffer.
41
private boolean mbUsed; // if caller used mappedBuffer
42
private boolean closeRequested;
43
44     private static final int STORED = ZipEntry.STORED;
45     private static final int DEFLATED = ZipEntry.DEFLATED;
46
47     /**
48      * Mode flag to open a zip file for reading.
49      */

50     public static final int OPEN_READ = 0x1;
51         
52     /**
53      * Mode flag to open a zip file and mark it for deletion. The file will be
54      * deleted some time between the moment that it is opened and the moment
55      * that it is closed, but its contents will remain accessible via the
56      * <tt>ZipFile</tt> object until either the close method is invoked or the
57      * virtual machine exits.
58      */

59     public static final int OPEN_DELETE = 0x4;
60     
61     static {
62     /* Zip library is loaded from System.initializeSystemClass */
63     initIDs();
64     }
65
66     private static native void initIDs();
67
68     /**
69      * Opens a zip file for reading.
70      *
71      * <p>First, if there is a security
72      * manager, its <code>checkRead</code> method
73      * is called with the <code>name</code> argument
74      * as its argument to ensure the read is allowed.
75      *
76      * @param name the name of the zip file
77      * @throws ZipException if a ZIP format error has occurred
78      * @throws IOException if an I/O error has occurred
79      * @throws SecurityException if a security manager exists and its
80      * <code>checkRead</code> method doesn't allow read access to the file.
81      * @see SecurityManager#checkRead(java.lang.String)
82      */

83     public ZipFile(String JavaDoc name) throws IOException JavaDoc {
84     this(new File JavaDoc(name), OPEN_READ);
85     }
86
87     /**
88      * Handles cleanup after mappedBuffer is no longer referenced.
89      *
90      * The DirectByteBuffer code creates a phantom reference to mappedBuffer
91      * that will call ZipCloser.run() when mappedBuffer is no longer
92      * (strongly) referenced.
93      * If it was safe to do so, ZipFile.close() (and finalize()) will have
94      * already cleaned up.
95      *
96      * Note: since ZipFile references MappedByteBuffer, we can be sure that
97      * the ZipFile has already been finalized by the time ZipCloser.run()
98      * is called.
99      */

100     private static class ZipCloser
101     implements Runnable JavaDoc
102     {
103     private long mappedFileID;
104     
105     private ZipCloser(long jzFile) {
106         mappedFileID = jzFile;
107     }
108
109     public synchronized void setClosed() {
110         mappedFileID = 0;
111     }
112     
113     public synchronized void run() {
114         if (mappedFileID != 0) {
115         ZipFile.close(mappedFileID);
116         mappedFileID = 0;
117         }
118     }
119     } /* ZipCloser */
120
121     private static Constructor directByteBufferConstructor = null;
122
123     private static void initDBBConstructor() {
124     AccessController.doPrivileged(new PrivilegedAction JavaDoc() {
125         public Object JavaDoc run() {
126             try {
127             Class JavaDoc th = Class.forName("java.nio.DirectByteBuffer");
128             directByteBufferConstructor
129                 = th.getDeclaredConstructor(
130                     new Class JavaDoc[] { int.class,
131                                                       long.class,
132                               Runnable JavaDoc.class });
133             directByteBufferConstructor.setAccessible(true);
134             } catch (ClassNotFoundException JavaDoc x) {
135             throw new InternalError JavaDoc();
136             } catch (NoSuchMethodException JavaDoc x) {
137             throw new InternalError JavaDoc();
138             } catch (IllegalArgumentException JavaDoc x) {
139             throw new InternalError JavaDoc();
140             } catch (ClassCastException JavaDoc x) {
141             throw new InternalError JavaDoc();
142             }
143                     return null;
144         }});
145     }
146
147     private static MappedByteBuffer JavaDoc newMappedByteBuffer(int size, long addr,
148                             Runnable JavaDoc unmapper)
149     {
150         MappedByteBuffer JavaDoc dbb;
151         if (directByteBufferConstructor == null)
152             initDBBConstructor();
153         try {
154             dbb = (MappedByteBuffer JavaDoc)directByteBufferConstructor.newInstance(
155               new Object JavaDoc[] { new Integer JavaDoc(size),
156                              new Long JavaDoc(addr),
157                  unmapper });
158         } catch (InstantiationException JavaDoc e) {
159             throw new InternalError JavaDoc();
160         } catch (IllegalAccessException JavaDoc e) {
161             throw new InternalError JavaDoc();
162         } catch (InvocationTargetException e) {
163             throw new InternalError JavaDoc();
164         }
165         return dbb;
166     }
167
168     /**
169      * Opens a new <code>ZipFile</code> to read from the specified
170      * <code>File</code> object in the specified mode. The mode argument
171      * must be either <tt>OPEN_READ</tt> or <tt>OPEN_READ | OPEN_DELETE</tt>.
172      *
173      * <p>First, if there is a security manager, its <code>checkRead</code>
174      * method is called with the <code>name</code> argument as its argument to
175      * ensure the read is allowed.
176      *
177      * @param file the ZIP file to be opened for reading
178      * @param mode the mode in which the file is to be opened
179      * @throws ZipException if a ZIP format error has occurred
180      * @throws IOException if an I/O error has occurred
181      * @throws SecurityException if a security manager exists and
182      * its <code>checkRead</code> method
183      * doesn't allow read access to the file,
184      * or its <code>checkDelete</code> method doesn't allow deleting
185      * the file when the <tt>OPEN_DELETE</tt> flag is set.
186      * @throws IllegalArgumentException if the <tt>mode</tt> argument is invalid
187      * @see SecurityManager#checkRead(java.lang.String)
188      */

189     public ZipFile(File JavaDoc file, int mode) throws IOException JavaDoc {
190         if (((mode & OPEN_READ) == 0) ||
191             ((mode & ~(OPEN_READ | OPEN_DELETE)) != 0)) {
192             throw new IllegalArgumentException JavaDoc("Illegal mode: 0x"+
193                                                Integer.toHexString(mode));
194         }
195         String JavaDoc name = file.getPath();
196     SecurityManager JavaDoc sm = System.getSecurityManager();
197     if (sm != null) {
198         sm.checkRead(name);
199         if ((mode & OPEN_DELETE) != 0) {
200         sm.checkDelete(name);
201         }
202     }
203     long jzfileCopy = open(name, mode, file.lastModified());
204     this.name = name;
205     this.total = getTotal(jzfileCopy);
206     this.mbUsed = false;
207     long mappedAddr = getMappedAddr(jzfileCopy);
208     long len = getMappedLen(jzfileCopy);
209
210     if (mappedAddr != 0 && len < Integer.MAX_VALUE) {
211         // Zip's native code may be able to handle files up to 4GB, but
212
// ByteBuffers can only handle 2GB. So fallback on Zip files >= 2GB.
213
this.closer = new ZipCloser(jzfileCopy);
214         this.mappedBuffer = newMappedByteBuffer((int)len, mappedAddr,
215                             this.closer);
216     }
217
218         jzfile = jzfileCopy;
219     }
220
221     private static native long open(String JavaDoc name, int mode, long lastModified);
222     private static native int getTotal(long jzfile);
223     private static native long getMappedAddr(long jzfile);
224     private static native long getMappedLen(long jzfile);
225
226
227     /**
228      * Opens a ZIP file for reading given the specified File object.
229      * @param file the ZIP file to be opened for reading
230      * @throws ZipException if a ZIP error has occurred
231      * @throws IOException if an I/O error has occurred
232      */

233     public ZipFile(File JavaDoc file) throws ZipException JavaDoc, IOException JavaDoc {
234     this(file, OPEN_READ);
235     }
236
237     /**
238      * Returns the zip file entry for the specified name, or null
239      * if not found.
240      *
241      * @param name the name of the entry
242      * @return the zip file entry, or null if not found
243      * @throws IllegalStateException if the zip file has been closed
244      */

245     public ZipEntry JavaDoc getEntry(String JavaDoc name) {
246         if (name == null) {
247             throw new NullPointerException JavaDoc("name");
248         }
249         long jzentry = 0;
250         synchronized (this) {
251             ensureOpen();
252             jzentry = getEntry(jzfile, name, true);
253             if (jzentry != 0) {
254                 ZipEntry JavaDoc ze = new ZipEntry JavaDoc(name, jzentry);
255                 freeEntry(jzfile, jzentry);
256                 return ze;
257             }
258         }
259         return null;
260     }
261
262     private static native long getEntry(long jzfile, String JavaDoc name,
263                                         boolean addSlash);
264
265     // freeEntry releases the C jzentry struct.
266
private static native void freeEntry(long jzfile, long jzentry);
267
268     /**
269      * Returns an input stream for reading the contents of the specified
270      * zip file entry.
271      *
272      * Returns an input stream for reading the contents of the specified
273      * zip file entry.
274      *
275      * <p> Closing this ZIP file will, in turn, close all input
276      * streams that have been returned by invocations of this method.
277      *
278      * @param entry the zip file entry
279      * @return the input stream for reading the contents of the specified
280      * zip file entry.
281      * @throws ZipException if a ZIP format error has occurred
282      * @throws IOException if an I/O error has occurred
283      * @throws IllegalStateException if the zip file has been closed
284      */

285     public InputStream JavaDoc getInputStream(ZipEntry JavaDoc entry) throws IOException JavaDoc {
286     return getInputStream(entry.name);
287     }
288
289     /**
290      * Returns an input stream for reading the contents of the specified
291      * entry, or null if the entry was not found.
292      */

293     private InputStream JavaDoc getInputStream(String JavaDoc name) throws IOException JavaDoc {
294     if (name == null) {
295         throw new NullPointerException JavaDoc("name");
296     }
297         long jzentry = 0;
298         ZipFileInputStream in = null;
299         synchronized (this) {
300             ensureOpen();
301             jzentry = getEntry(jzfile, name, false);
302             if (jzentry == 0) {
303                 return null;
304             }
305         if (mappedBuffer != null) {
306         in = new MappedZipFileInputStream(jzentry, name);
307         } else {
308         in = new ZipFileInputStream(jzentry);
309         }
310         }
311         final ZipFileInputStream zfin = in;
312     switch (getMethod(jzentry)) {
313     case STORED:
314         return zfin;
315     case DEFLATED:
316         // MORE: Compute good size for inflater stream:
317
long size = getSize(jzentry) + 2; // Inflater likes a bit of slack
318
if (size > 65536) size = 8192;
319             if (size <= 0) size = 4096;
320             return new InflaterInputStream JavaDoc(zfin, getInflater(), (int)size) {
321                 private boolean isClosed = false;
322                 
323         public void close() throws IOException JavaDoc {
324                     if (!isClosed) {
325                          releaseInflater(inf);
326                         this.in.close();
327                         isClosed = true;
328                     }
329         }
330         // Override fill() method to provide an extra "dummy" byte
331
// at the end of the input stream. This is required when
332
// using the "nowrap" Inflater option.
333
protected void fill() throws IOException JavaDoc {
334             if (eof) {
335             throw new EOFException JavaDoc(
336                 "Unexpected end of ZLIB input stream");
337             }
338             len = this.in.read(buf, 0, buf.length);
339             if (len == -1) {
340             buf[0] = 0;
341             len = 1;
342             eof = true;
343             }
344             inf.setInput(buf, 0, len);
345         }
346         private boolean eof;
347
348                 public int available() throws IOException JavaDoc {
349                     if (isClosed)
350                         return 0;
351             long avail = zfin.size() - inf.getBytesWritten();
352             return avail > (long) Integer.MAX_VALUE ?
353             Integer.MAX_VALUE : (int) avail;
354                 }
355         };
356     default:
357         throw new ZipException JavaDoc("invalid compression method");
358     }
359     }
360
361     private static native int getMethod(long jzentry);
362
363     /*
364      * Gets an inflater from the list of available inflaters or allocates
365      * a new one.
366      */

367     private Inflater JavaDoc getInflater() {
368     synchronized (inflaters) {
369         int size = inflaters.size();
370         if (size > 0) {
371         Inflater JavaDoc inf = (Inflater JavaDoc)inflaters.remove(size - 1);
372         inf.reset();
373         return inf;
374         } else {
375         return new Inflater JavaDoc(true);
376         }
377     }
378     }
379
380     /*
381      * Releases the specified inflater to the list of available inflaters.
382      */

383     private void releaseInflater(Inflater JavaDoc inf) {
384     synchronized (inflaters) {
385         inflaters.add(inf);
386     }
387     }
388
389     // List of available Inflater objects for decompression
390
private Vector JavaDoc inflaters = new Vector JavaDoc();
391
392     /**
393      * Returns the path name of the ZIP file.
394      * @return the path name of the ZIP file
395      */

396     public String JavaDoc getName() {
397         return name;
398     }
399
400     /**
401      * Returns an enumeration of the ZIP file entries.
402      * @return an enumeration of the ZIP file entries
403      * @throws IllegalStateException if the zip file has been closed
404      */

405     public Enumeration JavaDoc<? extends ZipEntry JavaDoc> entries() {
406         ensureOpen();
407         return new Enumeration JavaDoc<ZipEntry JavaDoc>() {
408                 private int i = 0;
409                 public boolean hasMoreElements() {
410                     synchronized (ZipFile.this) {
411                         ensureOpen();
412                         return i < total;
413                     }
414                 }
415                 public ZipEntry JavaDoc nextElement() throws NoSuchElementException JavaDoc {
416                     synchronized (ZipFile.this) {
417                         ensureOpen();
418                         if (i >= total) {
419                             throw new NoSuchElementException JavaDoc();
420                         }
421                         long jzentry = getNextEntry(jzfile, i++);
422                         if (jzentry == 0) {
423                             String JavaDoc message;
424                             if (closeRequested) {
425                                 message = "ZipFile concurrently closed";
426                             } else {
427                                 message = getZipMessage(ZipFile.this.jzfile);
428                             }
429                             throw new InternalError JavaDoc("jzentry == 0" +
430                                                     ",\n jzfile = " + ZipFile.this.jzfile +
431                                                     ",\n total = " + ZipFile.this.total +
432                                                     ",\n name = " + ZipFile.this.name +
433                                                     ",\n i = " + i +
434                                                     ",\n message = " + message
435                                 );
436                         }
437                         ZipEntry JavaDoc ze = new ZipEntry JavaDoc(jzentry);
438                         freeEntry(jzfile, jzentry);
439                         return ze;
440                     }
441                 }
442             };
443     }
444
445     private static native long getNextEntry(long jzfile, int i);
446
447     /**
448      * Returns the number of entries in the ZIP file.
449      * @return the number of entries in the ZIP file
450      * @throws IllegalStateException if the zip file has been closed
451      */

452     public int size() {
453         ensureOpen();
454     return total;
455     }
456
457     /**
458      * Closes the ZIP file.
459      * <p> Closing this ZIP file will close all of the input streams
460      * previously returned by invocations of the {@link #getInputStream
461      * getInputStream} method.
462      *
463      * @throws IOException if an I/O error has occurred
464      */

465     public void close() throws IOException JavaDoc {
466         synchronized (this) {
467         closeRequested = true;
468         
469         if (jzfile != 0) {
470         // Close the zip file
471
long zf = this.jzfile;
472         jzfile = 0;
473         if (closer != null) {
474             if (!mbUsed) { // no one is looking; we can close early
475
closer.setClosed(); // tell closer not to bother
476
close(zf);
477             }
478             // Some caller may have ref to MappedByteBuffer,
479
// so let phantom processing (ZipCloser) close the ZipFile.
480
} else {
481             close(zf);
482         }
483         // Release inflaters
484
synchronized (inflaters) {
485             int size = inflaters.size();
486             for (int i = 0; i < size; i++) {
487             Inflater JavaDoc inf = (Inflater JavaDoc)inflaters.get(i);
488             inf.end();
489             }
490         }
491         }
492         }
493     }
494
495
496     /**
497      * Ensures that the <code>close</code> method of this ZIP file is
498      * called when there are no more references to it.
499      *
500      * <p>
501      * Since the time when GC would invoke this method is undetermined,
502      * it is strongly recommended that applications invoke the <code>close</code>
503      * method as soon they have finished accessing this <code>ZipFile</code>.
504      * This will prevent holding up system resources for an undetermined
505      * length of time.
506      *
507      * @throws IOException if an I/O error has occurred
508      * @see java.util.zip.ZipFile#close()
509      */

510     protected void finalize() throws IOException JavaDoc {
511         close();
512     }
513
514     private static native void close(long jzfile);
515
516     private void ensureOpen() {
517     if (closeRequested) {
518         throw new IllegalStateException JavaDoc("zip file closed");
519     }
520     }
521
522     private void ensureOpenOrZipException() throws IOException JavaDoc {
523     if (closeRequested) {
524         throw new ZipException JavaDoc("ZipFile closed");
525     }
526     }
527     
528     /*
529      * Inner class implementing the input stream used to read a
530      * (possibly compressed) zip file entry.
531      */

532    private class ZipFileInputStream extends InputStream JavaDoc {
533     protected long jzentry; // address of jzentry data
534
private long pos; // current position within entry data
535
protected long rem; // number of remaining bytes within entry
536
protected long size; // uncompressed size of this entry
537

538     ZipFileInputStream(long jzentry) {
539         pos = 0;
540         rem = getCSize(jzentry);
541             size = getSize(jzentry);
542         this.jzentry = jzentry;
543     }
544
545     public int read(byte b[], int off, int len) throws IOException JavaDoc {
546         if (rem == 0) {
547         return -1;
548         }
549         if (len <= 0) {
550         return 0;
551         }
552         if (len > rem) {
553         len = (int) rem;
554         }
555             synchronized (ZipFile.this) {
556         ensureOpenOrZipException();
557
558         len = ZipFile.read(ZipFile.this.jzfile, jzentry, pos, b,
559                    off, len);
560             }
561         if (len > 0) {
562         pos += len;
563         rem -= len;
564         }
565         if (rem == 0) {
566         close();
567         }
568         return len;
569     }
570
571     public int read() throws IOException JavaDoc {
572         byte[] b = new byte[1];
573         if (read(b, 0, 1) == 1) {
574         return b[0] & 0xff;
575         } else {
576         return -1;
577         }
578     }
579
580     public long skip(long n) {
581         if (n > rem)
582         n = rem;
583         pos += n;
584         rem -= n;
585         if (rem == 0) {
586         close();
587         }
588         return n;
589     }
590
591         public int available() {
592         return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem;
593         }
594
595         public long size() {
596             return size;
597         }
598
599         public void close() {
600             rem = 0;
601             synchronized (ZipFile.this) {
602                 if (jzentry != 0 && ZipFile.this.jzfile != 0) {
603                     freeEntry(ZipFile.this.jzfile, jzentry);
604                     jzentry = 0;
605                 }
606             }
607         }
608
609     }
610
611     /*
612      * Inner class implementing the input stream used to read a
613      * mapped (possibly compressed) zip file entry. Overrides
614      * all methods of ZipFileInputStream.
615      */

616    private class MappedZipFileInputStream extends ZipFileInputStream
617        implements ByteBuffered {
618
619        private ByteBuffer JavaDoc directBuffer = null;
620        private String JavaDoc name;
621        
622     MappedZipFileInputStream(long jzentry, String JavaDoc name) {
623         super(jzentry);
624         this.name = name;
625         int offset = (int)getEntryOffset(jzentry);
626         MappedByteBuffer JavaDoc bb = ZipFile.this.mappedBuffer;
627         synchronized (bb) {
628         bb.position(offset);
629         bb.limit((int)(offset + rem)); // won't use this code if file > 2GB
630

631         this.directBuffer = bb.slice();
632         
633         bb.position(0); // reset, but doesn't matter
634
bb.limit(bb.capacity()); // reset limit
635
}
636     }
637
638        /* getByteBuffer returns a ByteBuffer if the jar file has been mapped in.
639       If this method is called, the zip code won't close the ZipFile until
640       the last reference to file's mapped buffer is collected. */

641        public ByteBuffer JavaDoc getByteBuffer() throws IOException JavaDoc {
642        synchronized (ZipFile.this) {
643            ensureOpenOrZipException();
644            // have to defer ZipFile.close() until all the buffers are garbage
645
ZipFile.this.mbUsed = true;
646            return directBuffer;
647        }
648        }
649
650     public int read(byte b[], int off, int len) throws IOException JavaDoc {
651         int rem = directBuffer.remaining();
652         if (rem == 0) {
653         return -1;
654         }
655         if (len <= 0) {
656         return 0;
657         }
658         if (len > rem) {
659         len = rem;
660         }
661             synchronized (ZipFile.this) {
662                 ensureOpenOrZipException();
663
664         directBuffer.get(b, off, len);
665             }
666        
667         if (len == rem) {
668         close();
669         }
670         return len;
671     }
672
673     public int read() throws IOException JavaDoc {
674         synchronized (ZipFile.this) {
675                ensureOpenOrZipException();
676
677         if (directBuffer.remaining() == 0) {
678             return -1;
679         } else {
680             return directBuffer.get() & 0xff;
681         }
682         }
683     }
684
685     public long skip(long n) {
686         int rem = directBuffer.remaining();
687         int len = n > rem ? rem : (int)n;
688         directBuffer.position(directBuffer.position() + len);
689         if (len == rem) {
690         close();
691         }
692         return len;
693     }
694
695         public int available() {
696             return directBuffer.remaining();
697         }
698
699         public long size() {
700             return size;
701         }
702
703         public void close() {
704         directBuffer.position(directBuffer.limit());
705             synchronized (ZipFile.this) {
706                 if (jzentry != 0 && ZipFile.this.jzfile != 0) {
707                     freeEntry(ZipFile.this.jzfile, jzentry);
708                     jzentry = 0;
709                 }
710             }
711         }
712
713     } /* MappedZipFileInputStream */
714
715     private static native int read(long jzfile, long jzentry,
716                    long pos, byte[] b, int off, int len);
717
718     private static native long getCSize(long jzentry);
719
720     private static native long getSize(long jzentry);
721
722     /* If the zip file is mapped, return the offset from the beginning of the zip
723        file to this entry. Return 0 otherwise. */

724     private static native long getEntryOffset(long jzentry);
725
726     // Temporary add on for bug troubleshooting
727
private static native String JavaDoc getZipMessage(long jzfile);
728 }
729
Popular Tags