001: /*
002: * @(#)ZipFile.java 1.60 06/10/10
003: *
004: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: *
026: */
027:
028: package java.util.zip;
029:
030: import java.io.InputStream;
031: import java.io.IOException;
032: import java.io.EOFException;
033: import java.io.File;
034: import java.util.Vector;
035: import java.util.Enumeration;
036: import java.util.NoSuchElementException;
037: import java.security.AccessController;
038: import java.security.PrivilegedAction;
039:
040: /**
041: * This class is used to read entries from a zip file.
042: *
043: * @version 1.60, 10/10/06
044: * @author David Connelly
045: */
046: public class ZipFile implements ZipConstants {
047: private long jzfile; // address of jzfile data
048: private String name; // zip file name
049: private int total; // total number of entries
050: private boolean deletemode = false; // zip file mode
051: private File zipfile; // zip file pointer for OPEN_DELETE mode
052:
053: private static final int STORED = ZipEntry.STORED;
054: private static final int DEFLATED = ZipEntry.DEFLATED;
055:
056: /**
057: * Mode flag to open a zip file for reading.
058: */
059: public static final int OPEN_READ = 0x1;
060:
061: /**
062: * Mode flag to open a zip file and mark it for deletion. The file will be
063: * deleted some time between the moment that it is opened and the moment
064: * that it is closed, but its contents will remain accessible via the
065: * <tt>ZipFile</tt> object until either the close method is invoked or the
066: * virtual machine exits.
067: */
068: public static final int OPEN_DELETE = 0x4;
069:
070: static {
071: AccessController
072: .doPrivileged(new sun.security.action.LoadLibraryAction(
073: "zip"));
074: initIDs();
075: }
076:
077: private static native void initIDs();
078:
079: /**
080: * Opens a zip file for reading.
081: *
082: * <p>First, if there is a security
083: * manager, its <code>checkRead</code> method
084: * is called with the <code>name</code> argument
085: * as its argument to ensure the read is allowed.
086: *
087: * @param name the name of the zip file
088: * @exception ZipException if a ZIP format error has occurred
089: * @exception IOException if an I/O error has occurred
090: * @exception SecurityException if a security manager exists and its
091: * <code>checkRead</code> method doesn't allow read access to the file.
092: * @see SecurityManager#checkRead(java.lang.String)
093: */
094: public ZipFile(String name) throws IOException {
095: this (new File(name), OPEN_READ);
096: }
097:
098: /**
099: * Opens a new <code>ZipFile</code> to read from the specified
100: * <code>File</code> object in the specified mode. The mode argument
101: * must be either <tt>OPEN_READ</tt> or <tt>OPEN_READ | OPEN_DELETE</tt>.
102: *
103: * <p>First, if there is a security manager, its <code>checkRead</code>
104: * method is called with the <code>name</code> argument as its argument to
105: * ensure the read is allowed.
106: *
107: * @param file the ZIP file to be opened for reading
108: * @param mode the mode in which the file is to be opened
109: * @exception ZipException if a ZIP format error has occurred
110: * @exception IOException if an I/O error has occurred
111: * @exception SecurityException if a security manager exists and its
112: * <code>checkRead</code> method doesn't allow read access to the file,
113: * or <code>checkDelete</code> method doesn't allow deleting the file
114: * when <tt>OPEN_DELETE</tt> flag is set.
115: * @exception IllegalArgumentException
116: * If the <tt>mode</tt> argument is invalid
117: * @see SecurityManager#checkRead(java.lang.String)
118: */
119: public ZipFile(File file, int mode) throws IOException {
120: if (((mode & OPEN_READ) == 0)
121: || ((mode & ~(OPEN_READ | OPEN_DELETE)) != 0)) {
122: throw new IllegalArgumentException("Illegal mode: 0x"
123: + Integer.toHexString(mode));
124: }
125:
126: String name = file.getPath();
127: SecurityManager sm = System.getSecurityManager();
128: if (sm != null) {
129: sm.checkRead(name);
130: if ((mode & OPEN_DELETE) != 0) {
131: sm.checkDelete(name);
132: }
133: }
134:
135: /* Mask out OPEN_DELETE */
136: if ((mode & OPEN_DELETE) != 0) {
137: mode &= ~OPEN_DELETE;
138: deletemode = true;
139: zipfile = file;
140: }
141:
142: long jzfileCopy = open(name, mode, file.lastModified());
143: this .name = name;
144: this .total = getTotal(jzfileCopy);
145: jzfile = jzfileCopy;
146: }
147:
148: private static native long open(String name, int mode,
149: long lastModified);
150:
151: private static native int getTotal(long jzfile);
152:
153: /**
154: * Opens a ZIP file for reading given the specified File object.
155: * @param file the ZIP file to be opened for reading
156: * @exception ZipException if a ZIP error has occurred
157: * @exception IOException if an I/O error has occurred
158: */
159: public ZipFile(File file) throws ZipException, IOException {
160: this (file, OPEN_READ);
161: }
162:
163: /**
164: * Returns the zip file entry for the specified name, or null
165: * if not found.
166: *
167: * @param name the name of the entry
168: * @return the zip file entry, or null if not found
169: * @exception IllegalStateException if the zip file has been closed
170: */
171: public ZipEntry getEntry(String name) {
172: if (name == null) {
173: throw new NullPointerException("name");
174: }
175: long jzentry = 0;
176: synchronized (this ) {
177: ensureOpen(jzfile);
178: jzentry = getEntry(jzfile, name);
179: if (jzentry == 0 && !name.endsWith("/")) {
180: // try a directory name
181: jzentry = getEntry(jzfile, name + "/");
182: }
183:
184: if (jzentry != 0) {
185: ZipEntry ze = new ZipEntry(name, jzentry);
186: freeEntry(jzfile, jzentry);
187: return ze;
188: }
189: }
190: return null;
191: }
192:
193: private static native long getEntry(long jzfile, String name);
194:
195: // freeEntry releases the C jzentry struct.
196: private static native void freeEntry(long jzfile, long jzentry);
197:
198: /**
199: * Returns an input stream for reading the contents of the specified
200: * zip file entry.
201: *
202: * Returns an input stream for reading the contents of the specified
203: * zip file entry.
204: *
205: * <p> Closing this ZIP file will, in turn, close all input
206: * streams that have been returned by invocations of this method.
207: *
208: * @param entry the zip file entry
209: * @return the input stream for reading the contents of the specified
210: * zip file entry.
211: * @exception ZipException if a ZIP format error has occurred
212: * @exception IOException if an I/O error has occurred
213: * @exception IllegalStateException if the zip file has been closed
214: */
215: public InputStream getInputStream(ZipEntry entry)
216: throws IOException {
217: return getInputStream(entry.name);
218: }
219:
220: /**
221: * Returns an input stream for reading the contents of the specified
222: * entry, or null if the entry was not found.
223: */
224: private InputStream getInputStream(String name) throws IOException {
225: if (name == null) {
226: throw new NullPointerException("name");
227: }
228: long jzentry = 0;
229: InputStream in = null;
230: synchronized (this ) {
231: ensureOpen(jzfile);
232: jzentry = getEntry(jzfile, name);
233: if (jzentry == 0) {
234: return null;
235: }
236: in = new ZipFileInputStream(jzentry, this );
237: }
238: switch (getMethod(jzentry)) {
239: case STORED:
240: return in;
241: case DEFLATED:
242: return new InflaterInputStream(in, getInflater()) {
243: private boolean isClosed = false;
244:
245: public void close() throws IOException {
246: if (!isClosed) {
247: releaseInflater(inf);
248: this .in.close();
249: isClosed = true;
250: }
251: }
252:
253: // Override fill() method to provide an extra "dummy" byte
254: // at the end of the input stream. This is required when
255: // using the "nowrap" Inflater option.
256: protected void fill() throws IOException {
257: if (eof) {
258: throw new EOFException(
259: "Unexpected end of ZLIB input stream");
260: }
261: len = this .in.read(buf, 0, buf.length);
262: if (len == -1) {
263: buf[0] = 0;
264: len = 1;
265: eof = true;
266: }
267: inf.setInput(buf, 0, len);
268: }
269:
270: private boolean eof;
271:
272: public int available() throws IOException {
273: if (super .available() != 0) {
274: return this .in.available();
275: } else {
276: return 0;
277: }
278: }
279: };
280: default:
281: throw new ZipException("invalid compression method");
282: }
283: }
284:
285: private static native int getMethod(long jzentry);
286:
287: /*
288: * Gets an inflater from the list of available inflaters or allocates
289: * a new one.
290: */
291: private Inflater getInflater() {
292: synchronized (inflaters) {
293: int size = inflaters.size();
294: if (size > 0) {
295: Inflater inf = (Inflater) inflaters.remove(size - 1);
296: inf.reset();
297: return inf;
298: } else {
299: return new Inflater(true);
300: }
301: }
302: }
303:
304: /*
305: * Releases the specified inflater to the list of available inflaters.
306: */
307: private void releaseInflater(Inflater inf) {
308: synchronized (inflaters) {
309: inflaters.add(inf);
310: }
311: }
312:
313: // List of available Inflater objects for decompression
314: private Vector inflaters = new Vector();
315:
316: /**
317: * Returns the path name of the ZIP file.
318: * @return the path name of the ZIP file
319: */
320: public String getName() {
321: return name;
322: }
323:
324: /**
325: * Returns an enumeration of the ZIP file entries.
326: * @return an enumeration of the ZIP file entries
327: * @exception IllegalStateException if the zip file has been closed
328: */
329: public Enumeration entries() {
330: ensureOpen(jzfile);
331: return new Enumeration() {
332: private int i = 0;
333:
334: public boolean hasMoreElements() {
335: synchronized (ZipFile.this ) {
336: if (ZipFile.this .jzfile == 0) {
337: throw new IllegalStateException(
338: "zip file closed");
339: }
340: ensureOpen(ZipFile.this .jzfile);
341: }
342: return i < total;
343: }
344:
345: public Object nextElement() throws NoSuchElementException {
346: synchronized (ZipFile.this ) {
347: ensureOpen(ZipFile.this .jzfile);
348: if (i >= total) {
349: throw new NoSuchElementException();
350: }
351: long jzentry = getNextEntry(jzfile, i++);
352: if (jzentry == 0) {
353: String message;
354: if (ZipFile.this .jzfile == 0) {
355: message = "ZipFile concurrently closed";
356: } else {
357: message = getZipMessage(ZipFile.this .jzfile);
358: }
359: throw new InternalError("jzentry == 0"
360: + ",\n jzfile = " + ZipFile.this .jzfile
361: + ",\n total = " + ZipFile.this .total
362: + ",\n name = " + ZipFile.this .name
363: + ",\n i = " + i + ",\n message = "
364: + message);
365: }
366: ZipEntry ze = new ZipEntry(jzentry);
367: freeEntry(jzfile, jzentry);
368: return ze;
369: }
370: }
371: };
372: }
373:
374: private static native long getNextEntry(long jzfile, int i);
375:
376: /**
377: * Returns the number of entries in the ZIP file.
378: * @return the number of entries in the ZIP file
379: * @exception IllegalStateException if the zip file has been closed
380: */
381: public int size() {
382: ensureOpen(jzfile);
383: return total;
384: }
385:
386: /**
387: * Closes the ZIP file.
388: * <p> Closing this ZIP file will close all of the input streams
389: * previously returned by invocations of the {@link #getInputStream
390: * getInputStream} method.
391: *
392: * @throws IOException if an I/O error has occured
393: */
394: public void close() throws IOException {
395: synchronized (this ) {
396: if (jzfile != 0) {
397: // Close the zip file
398: long zf = this .jzfile;
399: jzfile = 0;
400: close(zf);
401: if (deletemode) {
402: AccessController
403: .doPrivileged(new PrivilegedAction() {
404: public Object run() {
405: zipfile.delete();
406: return null;
407: }
408: });
409: }
410:
411: // Release inflaters
412: synchronized (inflaters) {
413: int size = inflaters.size();
414: for (int i = 0; i < size; i++) {
415: Inflater inf = (Inflater) inflaters.get(i);
416: inf.end();
417: }
418: }
419: }
420: }
421: }
422:
423: /**
424: * Ensures that the <code>close</code> method of this ZIP file is
425: * called when there are no more references to it.
426: *
427: * <p>
428: * Since the time when GC would invoke this method is undetermined,
429: * it is strongly recommanded that applications invoke the <code>close</code>
430: * method as soon they have finished accessing this <code>ZipFile</code>.
431: * This will prevent holding up system resources for an undetermined
432: * length of time.
433: *
434: * @exception IOException if an I/O error occurs.
435: * @see java.util.zip.ZipFile#close()
436: */
437: protected void finalize() throws IOException {
438: close();
439: }
440:
441: private static native void close(long jzfile);
442:
443: private void ensureOpen(long fd) {
444: if (fd == 0) {
445: throw new IllegalStateException("zip file closed");
446: }
447: }
448:
449: /*
450: * Inner class implementing the input stream used to read a zip file entry.
451: */
452: private class ZipFileInputStream extends InputStream {
453: private long jzentry; // address of jzentry data
454: private int pos; // current position within entry data
455: private int rem; // number of remaining bytes within entry
456: private int size; // uncompressed size of this entry
457: private ZipFile handle; // this would prevent the zip file from being GCed
458:
459: ZipFileInputStream(long jzentry, ZipFile zf) {
460: pos = 0;
461: rem = getCSize(jzentry);
462: size = getSize(jzentry);
463: this .handle = zf;
464: this .jzentry = jzentry;
465: }
466:
467: public int read(byte b[], int off, int len) throws IOException {
468: if (rem == 0) {
469: return -1;
470: }
471: if (len <= 0) {
472: return 0;
473: }
474: if (len > rem) {
475: len = rem;
476: }
477: synchronized (ZipFile.this ) {
478: if (ZipFile.this .jzfile == 0)
479: throw new ZipException("ZipFile closed.");
480: len = ZipFile.read(ZipFile.this .jzfile, jzentry, pos,
481: b, off, len);
482: }
483: if (len > 0) {
484: pos += len;
485: rem -= len;
486: }
487: if (rem == 0) {
488: close();
489: }
490: return len;
491: }
492:
493: public int read() throws IOException {
494: byte[] b = new byte[1];
495: if (read(b, 0, 1) == 1) {
496: return b[0] & 0xff;
497: } else {
498: return -1;
499: }
500: }
501:
502: public long skip(long n) {
503: int len = n > rem ? rem : (int) n;
504: pos += len;
505: rem -= len;
506: if (rem == 0) {
507: close();
508: }
509: return len;
510: }
511:
512: public int available() {
513: return size;
514: }
515:
516: public void close() {
517: rem = 0;
518: synchronized (ZipFile.this ) {
519: if (jzentry != 0 && ZipFile.this .jzfile != 0) {
520: freeEntry(ZipFile.this .jzfile, jzentry);
521: jzentry = 0;
522: }
523: }
524: }
525:
526: }
527:
528: private static native int read(long jzfile, long jzentry, int pos,
529: byte[] b, int off, int len);
530:
531: private static native int getCSize(long jzentry);
532:
533: private static native int getSize(long jzentry);
534:
535: // Temporary add on for bug troubleshooting
536: private static native String getZipMessage(long jzfile);
537: }
|