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