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