blob: 8ad177fdc92999514a8e013e7b37fb350991ac1f [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1995-2006 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package java.util.zip;
27
28import java.io.InputStream;
29import java.io.IOException;
30import java.io.EOFException;
31import java.io.File;
32import java.util.Vector;
33import java.util.Enumeration;
34import java.util.NoSuchElementException;
35
36/**
37 * This class is used to read entries from a zip file.
38 *
39 * <p> Unless otherwise noted, passing a <tt>null</tt> argument to a constructor
40 * or method in this class will cause a {@link NullPointerException} to be
41 * thrown.
42 *
43 * @author David Connelly
44 */
45public
46class ZipFile implements ZipConstants {
47 private long jzfile; // address of jzfile data
48 private String name; // zip file name
49 private int total; // total number of entries
50 private boolean closeRequested;
51
52 private static final int STORED = ZipEntry.STORED;
53 private static final int DEFLATED = ZipEntry.DEFLATED;
54
55 /**
56 * Mode flag to open a zip file for reading.
57 */
58 public static final int OPEN_READ = 0x1;
59
60 /**
61 * Mode flag to open a zip file and mark it for deletion. The file will be
62 * deleted some time between the moment that it is opened and the moment
63 * that it is closed, but its contents will remain accessible via the
64 * <tt>ZipFile</tt> object until either the close method is invoked or the
65 * virtual machine exits.
66 */
67 public static final int OPEN_DELETE = 0x4;
68
69 static {
70 /* Zip library is loaded from System.initializeSystemClass */
71 initIDs();
72 }
73
74 private static native void initIDs();
75
76 /**
77 * Opens a zip file for reading.
78 *
79 * <p>First, if there is a security
80 * manager, its <code>checkRead</code> method
81 * is called with the <code>name</code> argument
82 * as its argument to ensure the read is allowed.
83 *
84 * @param name the name of the zip file
85 * @throws ZipException if a ZIP format error has occurred
86 * @throws IOException if an I/O error has occurred
87 * @throws SecurityException if a security manager exists and its
88 * <code>checkRead</code> method doesn't allow read access to the file.
89 * @see SecurityManager#checkRead(java.lang.String)
90 */
91 public ZipFile(String name) throws IOException {
92 this(new File(name), OPEN_READ);
93 }
94
95 /**
96 * Opens a new <code>ZipFile</code> to read from the specified
97 * <code>File</code> object in the specified mode. The mode argument
98 * must be either <tt>OPEN_READ</tt> or <tt>OPEN_READ | OPEN_DELETE</tt>.
99 *
100 * <p>First, if there is a security manager, its <code>checkRead</code>
101 * method is called with the <code>name</code> argument as its argument to
102 * ensure the read is allowed.
103 *
104 * @param file the ZIP file to be opened for reading
105 * @param mode the mode in which the file is to be opened
106 * @throws ZipException if a ZIP format error has occurred
107 * @throws IOException if an I/O error has occurred
108 * @throws SecurityException if a security manager exists and
109 * its <code>checkRead</code> method
110 * doesn't allow read access to the file,
111 * or its <code>checkDelete</code> method doesn't allow deleting
112 * the file when the <tt>OPEN_DELETE</tt> flag is set.
113 * @throws IllegalArgumentException if the <tt>mode</tt> argument is invalid
114 * @see SecurityManager#checkRead(java.lang.String)
115 * @since 1.3
116 */
117 public ZipFile(File file, int mode) throws IOException {
118 if (((mode & OPEN_READ) == 0) ||
119 ((mode & ~(OPEN_READ | OPEN_DELETE)) != 0)) {
120 throw new IllegalArgumentException("Illegal mode: 0x"+
121 Integer.toHexString(mode));
122 }
123 String name = file.getPath();
124 SecurityManager sm = System.getSecurityManager();
125 if (sm != null) {
126 sm.checkRead(name);
127 if ((mode & OPEN_DELETE) != 0) {
128 sm.checkDelete(name);
129 }
130 }
131 jzfile = open(name, mode, file.lastModified());
132
133 this.name = name;
134 this.total = getTotal(jzfile);
135 }
136
137 private static native long open(String name, int mode, long lastModified);
138 private static native int getTotal(long jzfile);
139
140
141 /**
142 * Opens a ZIP file for reading given the specified File object.
143 * @param file the ZIP file to be opened for reading
144 * @throws ZipException if a ZIP error has occurred
145 * @throws IOException if an I/O error has occurred
146 */
147 public ZipFile(File file) throws ZipException, IOException {
148 this(file, OPEN_READ);
149 }
150
151 /**
152 * Returns the zip file entry for the specified name, or null
153 * if not found.
154 *
155 * @param name the name of the entry
156 * @return the zip file entry, or null if not found
157 * @throws IllegalStateException if the zip file has been closed
158 */
159 public ZipEntry getEntry(String name) {
160 if (name == null) {
161 throw new NullPointerException("name");
162 }
163 long jzentry = 0;
164 synchronized (this) {
165 ensureOpen();
166 jzentry = getEntry(jzfile, name, true);
167 if (jzentry != 0) {
168 ZipEntry ze = new ZipEntry(name, jzentry);
169 freeEntry(jzfile, jzentry);
170 return ze;
171 }
172 }
173 return null;
174 }
175
176 private static native long getEntry(long jzfile, String name,
177 boolean addSlash);
178
179 // freeEntry releases the C jzentry struct.
180 private static native void freeEntry(long jzfile, long jzentry);
181
182 /**
183 * Returns an input stream for reading the contents of the specified
184 * zip file entry.
185 *
186 * <p> Closing this ZIP file will, in turn, close all input
187 * streams that have been returned by invocations of this method.
188 *
189 * @param entry the zip file entry
190 * @return the input stream for reading the contents of the specified
191 * zip file entry.
192 * @throws ZipException if a ZIP format error has occurred
193 * @throws IOException if an I/O error has occurred
194 * @throws IllegalStateException if the zip file has been closed
195 */
196 public InputStream getInputStream(ZipEntry entry) throws IOException {
197 return getInputStream(entry.name);
198 }
199
200 /**
201 * Returns an input stream for reading the contents of the specified
202 * entry, or null if the entry was not found.
203 */
204 private InputStream getInputStream(String name) throws IOException {
205 if (name == null) {
206 throw new NullPointerException("name");
207 }
208 long jzentry = 0;
209 ZipFileInputStream in = null;
210 synchronized (this) {
211 ensureOpen();
212 jzentry = getEntry(jzfile, name, false);
213 if (jzentry == 0) {
214 return null;
215 }
216
217 in = new ZipFileInputStream(jzentry);
218
219 }
220 final ZipFileInputStream zfin = in;
221 switch (getMethod(jzentry)) {
222 case STORED:
223 return zfin;
224 case DEFLATED:
225 // MORE: Compute good size for inflater stream:
226 long size = getSize(jzentry) + 2; // Inflater likes a bit of slack
227 if (size > 65536) size = 8192;
228 if (size <= 0) size = 4096;
229 return new InflaterInputStream(zfin, getInflater(), (int)size) {
230 private boolean isClosed = false;
231
232 public void close() throws IOException {
233 if (!isClosed) {
234 releaseInflater(inf);
235 this.in.close();
236 isClosed = true;
237 }
238 }
239 // Override fill() method to provide an extra "dummy" byte
240 // at the end of the input stream. This is required when
241 // using the "nowrap" Inflater option.
242 protected void fill() throws IOException {
243 if (eof) {
244 throw new EOFException(
245 "Unexpected end of ZLIB input stream");
246 }
247 len = this.in.read(buf, 0, buf.length);
248 if (len == -1) {
249 buf[0] = 0;
250 len = 1;
251 eof = true;
252 }
253 inf.setInput(buf, 0, len);
254 }
255 private boolean eof;
256
257 public int available() throws IOException {
258 if (isClosed)
259 return 0;
260 long avail = zfin.size() - inf.getBytesWritten();
261 return avail > (long) Integer.MAX_VALUE ?
262 Integer.MAX_VALUE : (int) avail;
263 }
264 };
265 default:
266 throw new ZipException("invalid compression method");
267 }
268 }
269
270 private static native int getMethod(long jzentry);
271
272 /*
273 * Gets an inflater from the list of available inflaters or allocates
274 * a new one.
275 */
276 private Inflater getInflater() {
277 synchronized (inflaters) {
278 int size = inflaters.size();
279 if (size > 0) {
280 Inflater inf = (Inflater)inflaters.remove(size - 1);
281 inf.reset();
282 return inf;
283 } else {
284 return new Inflater(true);
285 }
286 }
287 }
288
289 /*
290 * Releases the specified inflater to the list of available inflaters.
291 */
292 private void releaseInflater(Inflater inf) {
293 synchronized (inflaters) {
294 inflaters.add(inf);
295 }
296 }
297
298 // List of available Inflater objects for decompression
299 private Vector inflaters = new Vector();
300
301 /**
302 * Returns the path name of the ZIP file.
303 * @return the path name of the ZIP file
304 */
305 public String getName() {
306 return name;
307 }
308
309 /**
310 * Returns an enumeration of the ZIP file entries.
311 * @return an enumeration of the ZIP file entries
312 * @throws IllegalStateException if the zip file has been closed
313 */
314 public Enumeration<? extends ZipEntry> entries() {
315 ensureOpen();
316 return new Enumeration<ZipEntry>() {
317 private int i = 0;
318 public boolean hasMoreElements() {
319 synchronized (ZipFile.this) {
320 ensureOpen();
321 return i < total;
322 }
323 }
324 public ZipEntry nextElement() throws NoSuchElementException {
325 synchronized (ZipFile.this) {
326 ensureOpen();
327 if (i >= total) {
328 throw new NoSuchElementException();
329 }
330 long jzentry = getNextEntry(jzfile, i++);
331 if (jzentry == 0) {
332 String message;
333 if (closeRequested) {
334 message = "ZipFile concurrently closed";
335 } else {
336 message = getZipMessage(ZipFile.this.jzfile);
337 }
338 throw new ZipError("jzentry == 0" +
339 ",\n jzfile = " + ZipFile.this.jzfile +
340 ",\n total = " + ZipFile.this.total +
341 ",\n name = " + ZipFile.this.name +
342 ",\n i = " + i +
343 ",\n message = " + message
344 );
345 }
346 ZipEntry ze = new ZipEntry(jzentry);
347 freeEntry(jzfile, jzentry);
348 return ze;
349 }
350 }
351 };
352 }
353
354 private static native long getNextEntry(long jzfile, int i);
355
356 /**
357 * Returns the number of entries in the ZIP file.
358 * @return the number of entries in the ZIP file
359 * @throws IllegalStateException if the zip file has been closed
360 */
361 public int size() {
362 ensureOpen();
363 return total;
364 }
365
366 /**
367 * Closes the ZIP file.
368 * <p> Closing this ZIP file will close all of the input streams
369 * previously returned by invocations of the {@link #getInputStream
370 * getInputStream} method.
371 *
372 * @throws IOException if an I/O error has occurred
373 */
374 public void close() throws IOException {
375 synchronized (this) {
376 closeRequested = true;
377
378 if (jzfile != 0) {
379 // Close the zip file
380 long zf = this.jzfile;
381 jzfile = 0;
382
383 close(zf);
384
385 // Release inflaters
386 synchronized (inflaters) {
387 int size = inflaters.size();
388 for (int i = 0; i < size; i++) {
389 Inflater inf = (Inflater)inflaters.get(i);
390 inf.end();
391 }
392 }
393 }
394 }
395 }
396
397
398 /**
399 * Ensures that the <code>close</code> method of this ZIP file is
400 * called when there are no more references to it.
401 *
402 * <p>
403 * Since the time when GC would invoke this method is undetermined,
404 * it is strongly recommended that applications invoke the <code>close</code>
405 * method as soon they have finished accessing this <code>ZipFile</code>.
406 * This will prevent holding up system resources for an undetermined
407 * length of time.
408 *
409 * @throws IOException if an I/O error has occurred
410 * @see java.util.zip.ZipFile#close()
411 */
412 protected void finalize() throws IOException {
413 close();
414 }
415
416 private static native void close(long jzfile);
417
418 private void ensureOpen() {
419 if (closeRequested) {
420 throw new IllegalStateException("zip file closed");
421 }
422
423 if (jzfile == 0) {
424 throw new IllegalStateException("The object is not initialized.");
425 }
426 }
427
428 private void ensureOpenOrZipException() throws IOException {
429 if (closeRequested) {
430 throw new ZipException("ZipFile closed");
431 }
432 }
433
434 /*
435 * Inner class implementing the input stream used to read a
436 * (possibly compressed) zip file entry.
437 */
438 private class ZipFileInputStream extends InputStream {
439 protected long jzentry; // address of jzentry data
440 private long pos; // current position within entry data
441 protected long rem; // number of remaining bytes within entry
442 protected long size; // uncompressed size of this entry
443
444 ZipFileInputStream(long jzentry) {
445 pos = 0;
446 rem = getCSize(jzentry);
447 size = getSize(jzentry);
448 this.jzentry = jzentry;
449 }
450
451 public int read(byte b[], int off, int len) throws IOException {
452 if (rem == 0) {
453 return -1;
454 }
455 if (len <= 0) {
456 return 0;
457 }
458 if (len > rem) {
459 len = (int) rem;
460 }
461 synchronized (ZipFile.this) {
462 ensureOpenOrZipException();
463
464 len = ZipFile.read(ZipFile.this.jzfile, jzentry, pos, b,
465 off, len);
466 }
467 if (len > 0) {
468 pos += len;
469 rem -= len;
470 }
471 if (rem == 0) {
472 close();
473 }
474 return len;
475 }
476
477 public int read() throws IOException {
478 byte[] b = new byte[1];
479 if (read(b, 0, 1) == 1) {
480 return b[0] & 0xff;
481 } else {
482 return -1;
483 }
484 }
485
486 public long skip(long n) {
487 if (n > rem)
488 n = rem;
489 pos += n;
490 rem -= n;
491 if (rem == 0) {
492 close();
493 }
494 return n;
495 }
496
497 public int available() {
498 return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem;
499 }
500
501 public long size() {
502 return size;
503 }
504
505 public void close() {
506 rem = 0;
507 synchronized (ZipFile.this) {
508 if (jzentry != 0 && ZipFile.this.jzfile != 0) {
509 freeEntry(ZipFile.this.jzfile, jzentry);
510 jzentry = 0;
511 }
512 }
513 }
514
515 }
516
517 private static native int read(long jzfile, long jzentry,
518 long pos, byte[] b, int off, int len);
519
520 private static native long getCSize(long jzentry);
521
522 private static native long getSize(long jzentry);
523
524 // Temporary add on for bug troubleshooting
525 private static native String getZipMessage(long jzfile);
526}