source: trunk/gcc/libjava/java/util/zip/ZipFile.java

Last change on this file was 1392, checked in by bird, 21 years ago

This commit was generated by cvs2svn to compensate for changes in r1391,
which included commits to RCS files with non-trunk default branches.

  • Property cvs2svn:cvs-rev set to 1.1.1.2
  • Property svn:eol-style set to native
  • Property svn:executable set to *
File size: 14.9 KB
Line 
1/* java.util.zip.ZipFile
2 Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc.
3
4This file is part of GNU Classpath.
5
6GNU Classpath is free software; you can redistribute it and/or modify
7it under the terms of the GNU General Public License as published by
8the Free Software Foundation; either version 2, or (at your option)
9any later version.
10
11GNU Classpath is distributed in the hope that it will be useful, but
12WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14General Public License for more details.
15
16You should have received a copy of the GNU General Public License
17along with GNU Classpath; see the file COPYING. If not, write to the
18Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
1902111-1307 USA.
20
21Linking this library statically or dynamically with other modules is
22making a combined work based on this library. Thus, the terms and
23conditions of the GNU General Public License cover the whole
24combination.
25
26As a special exception, the copyright holders of this library give you
27permission to link this library with independent modules to produce an
28executable, regardless of the license terms of these independent
29modules, and to copy and distribute the resulting executable under
30terms of your choice, provided that you also meet, for each linked
31independent module, the terms and conditions of the license of that
32module. An independent module is a module which is not derived from
33or based on this library. If you modify this library, you may extend
34this exception to your version of the library, but you are not
35obligated to do so. If you do not wish to do so, delete this
36exception statement from your version. */
37
38package java.util.zip;
39
40import java.io.BufferedInputStream;
41import java.io.ByteArrayInputStream;
42import java.io.DataInput;
43import java.io.DataInputStream;
44import java.io.File;
45import java.io.InputStream;
46import java.io.IOException;
47import java.io.EOFException;
48import java.io.RandomAccessFile;
49import java.util.Enumeration;
50import java.util.HashMap;
51import java.util.Iterator;
52import java.util.NoSuchElementException;
53
54/**
55 * This class represents a Zip archive. You can ask for the contained
56 * entries, or get an input stream for a file entry. The entry is
57 * automatically decompressed.
58 *
59 * This class is thread safe: You can open input streams for arbitrary
60 * entries in different threads.
61 *
62 * @author Jochen Hoenicke
63 * @author Artur Biesiadowski
64 */
65public class ZipFile implements ZipConstants
66{
67
68 /**
69 * Mode flag to open a zip file for reading.
70 */
71 public static final int OPEN_READ = 0x1;
72
73 /**
74 * Mode flag to delete a zip file after reading.
75 */
76 public static final int OPEN_DELETE = 0x4;
77
78 // Name of this zip file.
79 private final String name;
80
81 // File from which zip entries are read.
82 private final RandomAccessFile raf;
83
84 // The entries of this zip file when initialized and not yet closed.
85 private HashMap entries;
86
87 private boolean closed = false;
88
89 /**
90 * Opens a Zip file with the given name for reading.
91 * @exception IOException if a i/o error occured.
92 * @exception ZipException if the file doesn't contain a valid zip
93 * archive.
94 */
95 public ZipFile(String name) throws ZipException, IOException
96 {
97 this.raf = new RandomAccessFile(name, "r");
98 this.name = name;
99 }
100
101 /**
102 * Opens a Zip file reading the given File.
103 * @exception IOException if a i/o error occured.
104 * @exception ZipException if the file doesn't contain a valid zip
105 * archive.
106 */
107 public ZipFile(File file) throws ZipException, IOException
108 {
109 this.raf = new RandomAccessFile(file, "r");
110 this.name = file.getName();
111 }
112
113 /**
114 * Opens a Zip file reading the given File in the given mode.
115 *
116 * If the OPEN_DELETE mode is specified, the zip file will be deleted at
117 * some time moment after it is opened. It will be deleted before the zip
118 * file is closed or the Virtual Machine exits.
119 *
120 * The contents of the zip file will be accessible until it is closed.
121 *
122 * The OPEN_DELETE mode is currently unimplemented in this library
123 *
124 * @since JDK1.3
125 * @param mode Must be one of OPEN_READ or OPEN_READ | OPEN_DELETE
126 *
127 * @exception IOException if a i/o error occured.
128 * @exception ZipException if the file doesn't contain a valid zip
129 * archive.
130 */
131 public ZipFile(File file, int mode) throws ZipException, IOException
132 {
133 if ((mode & OPEN_DELETE) != 0)
134 {
135 throw new IllegalArgumentException
136 ("OPEN_DELETE mode not supported yet in java.util.zip.ZipFile");
137 }
138 this.raf = new RandomAccessFile(file, "r");
139 this.name = file.getName();
140 }
141
142 /**
143 * Read an unsigned short in little endian byte order from the given
144 * DataInput stream using the given byte buffer.
145 *
146 * @param di DataInput stream to read from.
147 * @param b the byte buffer to read in (must be at least 2 bytes long).
148 * @return The value read.
149 *
150 * @exception IOException if a i/o error occured.
151 * @exception EOFException if the file ends prematurely
152 */
153 private final int readLeShort(DataInput di, byte[] b) throws IOException
154 {
155 di.readFully(b, 0, 2);
156 return (b[0] & 0xff) | (b[1] & 0xff) << 8;
157 }
158
159 /**
160 * Read an int in little endian byte order from the given
161 * DataInput stream using the given byte buffer.
162 *
163 * @param di DataInput stream to read from.
164 * @param b the byte buffer to read in (must be at least 4 bytes long).
165 * @return The value read.
166 *
167 * @exception IOException if a i/o error occured.
168 * @exception EOFException if the file ends prematurely
169 */
170 private final int readLeInt(DataInput di, byte[] b) throws IOException
171 {
172 di.readFully(b, 0, 4);
173 return ((b[0] & 0xff) | (b[1] & 0xff) << 8)
174 | ((b[2] & 0xff) | (b[3] & 0xff) << 8) << 16;
175 }
176
177
178 /**
179 * Read an unsigned short in little endian byte order from the given
180 * byte buffer at the given offset.
181 *
182 * @param b the byte array to read from.
183 * @param off the offset to read from.
184 * @return The value read.
185 */
186 private final int readLeShort(byte[] b, int off)
187 {
188 return (b[off] & 0xff) | (b[off+1] & 0xff) << 8;
189 }
190
191 /**
192 * Read an int in little endian byte order from the given
193 * byte buffer at the given offset.
194 *
195 * @param b the byte array to read from.
196 * @param off the offset to read from.
197 * @return The value read.
198 */
199 private final int readLeInt(byte[] b, int off)
200 {
201 return ((b[off] & 0xff) | (b[off+1] & 0xff) << 8)
202 | ((b[off+2] & 0xff) | (b[off+3] & 0xff) << 8) << 16;
203 }
204
205
206 /**
207 * Read the central directory of a zip file and fill the entries
208 * array. This is called exactly once when first needed. It is called
209 * while holding the lock on <code>raf</code>.
210 *
211 * @exception IOException if a i/o error occured.
212 * @exception ZipException if the central directory is malformed
213 */
214 private void readEntries() throws ZipException, IOException
215 {
216 /* Search for the End Of Central Directory. When a zip comment is
217 * present the directory may start earlier.
218 * FIXME: This searches the whole file in a very slow manner if the
219 * file isn't a zip file.
220 */
221 long pos = raf.length() - ENDHDR;
222 byte[] ebs = new byte[CENHDR];
223
224 do
225 {
226 if (pos < 0)
227 throw new ZipException
228 ("central directory not found, probably not a zip file: " + name);
229 raf.seek(pos--);
230 }
231 while (readLeInt(raf, ebs) != ENDSIG);
232
233 if (raf.skipBytes(ENDTOT - ENDNRD) != ENDTOT - ENDNRD)
234 throw new EOFException(name);
235 int count = readLeShort(raf, ebs);
236 if (raf.skipBytes(ENDOFF - ENDSIZ) != ENDOFF - ENDSIZ)
237 throw new EOFException(name);
238 int centralOffset = readLeInt(raf, ebs);
239
240 entries = new HashMap(count+count/2);
241 raf.seek(centralOffset);
242
243 byte[] buffer = new byte[16];
244 for (int i = 0; i < count; i++)
245 {
246 raf.readFully(ebs);
247 if (readLeInt(ebs, 0) != CENSIG)
248 throw new ZipException("Wrong Central Directory signature: " + name);
249
250 int method = readLeShort(ebs, CENHOW);
251 int dostime = readLeInt(ebs, CENTIM);
252 int crc = readLeInt(ebs, CENCRC);
253 int csize = readLeInt(ebs, CENSIZ);
254 int size = readLeInt(ebs, CENLEN);
255 int nameLen = readLeShort(ebs, CENNAM);
256 int extraLen = readLeShort(ebs, CENEXT);
257 int commentLen = readLeShort(ebs, CENCOM);
258
259 int offset = readLeInt(ebs, CENOFF);
260
261 int needBuffer = Math.max(nameLen, commentLen);
262 if (buffer.length < needBuffer)
263 buffer = new byte[needBuffer];
264
265 raf.readFully(buffer, 0, nameLen);
266 String name = new String(buffer, 0, 0, nameLen);
267
268 ZipEntry entry = new ZipEntry(name);
269 entry.setMethod(method);
270 entry.setCrc(crc & 0xffffffffL);
271 entry.setSize(size & 0xffffffffL);
272 entry.setCompressedSize(csize & 0xffffffffL);
273 entry.setDOSTime(dostime);
274 if (extraLen > 0)
275 {
276 byte[] extra = new byte[extraLen];
277 raf.readFully(extra);
278 entry.setExtra(extra);
279 }
280 if (commentLen > 0)
281 {
282 raf.readFully(buffer, 0, commentLen);
283 entry.setComment(new String(buffer, 0, commentLen));
284 }
285 entry.offset = offset;
286 entries.put(name, entry);
287 }
288 }
289
290 /**
291 * Closes the ZipFile. This also closes all input streams given by
292 * this class. After this is called, no further method should be
293 * called.
294 *
295 * @exception IOException if a i/o error occured.
296 */
297 public void close() throws IOException
298 {
299 synchronized (raf)
300 {
301 closed = true;
302 entries = null;
303 raf.close();
304 }
305 }
306
307 /**
308 * Calls the <code>close()</code> method when this ZipFile has not yet
309 * been explicitly closed.
310 */
311 protected void finalize() throws IOException
312 {
313 if (!closed) close();
314 }
315
316 /**
317 * Returns an enumeration of all Zip entries in this Zip file.
318 */
319 public Enumeration entries()
320 {
321 try
322 {
323 return new ZipEntryEnumeration(getEntries().values().iterator());
324 }
325 catch (IOException ioe)
326 {
327 return null;
328 }
329 }
330
331 /**
332 * Checks that the ZipFile is still open and reads entries when necessary.
333 *
334 * @exception IllegalStateException when the ZipFile has already been closed.
335 * @exception IOEexception when the entries could not be read.
336 */
337 private HashMap getEntries() throws IOException
338 {
339 synchronized(raf)
340 {
341 if (closed)
342 throw new IllegalStateException("ZipFile has closed: " + name);
343
344 if (entries == null)
345 readEntries();
346
347 return entries;
348 }
349 }
350
351 /**
352 * Searches for a zip entry in this archive with the given name.
353 *
354 * @param the name. May contain directory components separated by
355 * slashes ('/').
356 * @return the zip entry, or null if no entry with that name exists.
357 */
358 public ZipEntry getEntry(String name)
359 {
360 try
361 {
362 HashMap entries = getEntries();
363 ZipEntry entry = (ZipEntry) entries.get(name);
364 return entry != null ? (ZipEntry) entry.clone() : null;
365 }
366 catch (IOException ioe)
367 {
368 return null;
369 }
370 }
371
372
373 //access should be protected by synchronized(raf)
374 private byte[] locBuf = new byte[LOCHDR];
375
376 /**
377 * Checks, if the local header of the entry at index i matches the
378 * central directory, and returns the offset to the data.
379 *
380 * @param entry to check.
381 * @return the start offset of the (compressed) data.
382 *
383 * @exception IOException if a i/o error occured.
384 * @exception ZipException if the local header doesn't match the
385 * central directory header
386 */
387 private long checkLocalHeader(ZipEntry entry) throws IOException
388 {
389 synchronized (raf)
390 {
391 raf.seek(entry.offset);
392 raf.readFully(locBuf);
393
394 if (readLeInt(locBuf, 0) != LOCSIG)
395 throw new ZipException("Wrong Local header signature: " + name);
396
397 if (entry.getMethod() != readLeShort(locBuf, LOCHOW))
398 throw new ZipException("Compression method mismatch: " + name);
399
400 if (entry.getName().length() != readLeShort(locBuf, LOCNAM))
401 throw new ZipException("file name length mismatch: " + name);
402
403 int extraLen = entry.getName().length() + readLeShort(locBuf, LOCEXT);
404 return entry.offset + LOCHDR + extraLen;
405 }
406 }
407
408 /**
409 * Creates an input stream reading the given zip entry as
410 * uncompressed data. Normally zip entry should be an entry
411 * returned by getEntry() or entries().
412 *
413 * @param entry the entry to create an InputStream for.
414 * @return the input stream.
415 *
416 * @exception IOException if a i/o error occured.
417 * @exception ZipException if the Zip archive is malformed.
418 */
419 public InputStream getInputStream(ZipEntry entry) throws IOException
420 {
421 HashMap entries = getEntries();
422 String name = entry.getName();
423 ZipEntry zipEntry = (ZipEntry) entries.get(name);
424 if (zipEntry == null)
425 throw new NoSuchElementException(name);
426
427 long start = checkLocalHeader(zipEntry);
428 int method = zipEntry.getMethod();
429 InputStream is = new BufferedInputStream(new PartialInputStream
430 (raf, start, zipEntry.getCompressedSize()));
431 switch (method)
432 {
433 case ZipOutputStream.STORED:
434 return is;
435 case ZipOutputStream.DEFLATED:
436 return new InflaterInputStream(is, new Inflater(true));
437 default:
438 throw new ZipException("Unknown compression method " + method);
439 }
440 }
441
442 /**
443 * Returns the name of this zip file.
444 */
445 public String getName()
446 {
447 return name;
448 }
449
450 /**
451 * Returns the number of entries in this zip file.
452 */
453 public int size()
454 {
455 try
456 {
457 return getEntries().size();
458 }
459 catch (IOException ioe)
460 {
461 return 0;
462 }
463 }
464
465 private static class ZipEntryEnumeration implements Enumeration
466 {
467 private final Iterator elements;
468
469 public ZipEntryEnumeration(Iterator elements)
470 {
471 this.elements = elements;
472 }
473
474 public boolean hasMoreElements()
475 {
476 return elements.hasNext();
477 }
478
479 public Object nextElement()
480 {
481 /* We return a clone, just to be safe that the user doesn't
482 * change the entry.
483 */
484 return ((ZipEntry)elements.next()).clone();
485 }
486 }
487
488 private static class PartialInputStream extends InputStream
489 {
490 private final RandomAccessFile raf;
491 long filepos, end;
492
493 public PartialInputStream(RandomAccessFile raf, long start, long len)
494 {
495 this.raf = raf;
496 filepos = start;
497 end = start + len;
498 }
499
500 public int available()
501 {
502 long amount = end - filepos;
503 if (amount > Integer.MAX_VALUE)
504 return Integer.MAX_VALUE;
505 return (int) amount;
506 }
507
508 public int read() throws IOException
509 {
510 if (filepos == end)
511 return -1;
512 synchronized (raf)
513 {
514 raf.seek(filepos++);
515 return raf.read();
516 }
517 }
518
519 public int read(byte[] b, int off, int len) throws IOException
520 {
521 if (len > end - filepos)
522 {
523 len = (int) (end - filepos);
524 if (len == 0)
525 return -1;
526 }
527 synchronized (raf)
528 {
529 raf.seek(filepos);
530 int count = raf.read(b, off, len);
531 if (count > 0)
532 filepos += len;
533 return count;
534 }
535 }
536
537 public long skip(long amount)
538 {
539 if (amount < 0)
540 throw new IllegalArgumentException();
541 if (amount > end - filepos)
542 amount = end - filepos;
543 filepos += amount;
544 return amount;
545 }
546 }
547}
Note: See TracBrowser for help on using the repository browser.