2004-11-06 Michael Koch <konqueror@gmx.de>
[official-gcc.git] / libjava / java / util / zip / ZipFile.java
blob38ec9e6b5655c9f6365a956fad0484cf6a81c224
1 /* ZipFile.java --
2 Copyright (C) 2001, 2002, 2003, 2004 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19 02111-1307 USA.
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package java.util.zip;
41 import java.io.BufferedInputStream;
42 import java.io.DataInput;
43 import java.io.EOFException;
44 import java.io.File;
45 import java.io.IOException;
46 import java.io.InputStream;
47 import java.io.RandomAccessFile;
48 import java.util.Enumeration;
49 import java.util.HashMap;
50 import java.util.Iterator;
52 /**
53 * This class represents a Zip archive. You can ask for the contained
54 * entries, or get an input stream for a file entry. The entry is
55 * automatically decompressed.
57 * This class is thread safe: You can open input streams for arbitrary
58 * entries in different threads.
60 * @author Jochen Hoenicke
61 * @author Artur Biesiadowski
63 public class ZipFile implements ZipConstants
66 /**
67 * Mode flag to open a zip file for reading.
69 public static final int OPEN_READ = 0x1;
71 /**
72 * Mode flag to delete a zip file after reading.
74 public static final int OPEN_DELETE = 0x4;
76 // Name of this zip file.
77 private final String name;
79 // File from which zip entries are read.
80 private final RandomAccessFile raf;
82 // The entries of this zip file when initialized and not yet closed.
83 private HashMap entries;
85 private boolean closed = false;
87 /**
88 * Opens a Zip file with the given name for reading.
89 * @exception IOException if a i/o error occured.
90 * @exception ZipException if the file doesn't contain a valid zip
91 * archive.
93 public ZipFile(String name) throws ZipException, IOException
95 this.raf = new RandomAccessFile(name, "r");
96 this.name = name;
99 /**
100 * Opens a Zip file reading the given File.
101 * @exception IOException if a i/o error occured.
102 * @exception ZipException if the file doesn't contain a valid zip
103 * archive.
105 public ZipFile(File file) throws ZipException, IOException
107 this.raf = new RandomAccessFile(file, "r");
108 this.name = file.getPath();
112 * Opens a Zip file reading the given File in the given mode.
114 * If the OPEN_DELETE mode is specified, the zip file will be deleted at
115 * some time moment after it is opened. It will be deleted before the zip
116 * file is closed or the Virtual Machine exits.
118 * The contents of the zip file will be accessible until it is closed.
120 * The OPEN_DELETE mode is currently unimplemented in this library
122 * @since JDK1.3
123 * @param mode Must be one of OPEN_READ or OPEN_READ | OPEN_DELETE
125 * @exception IOException if a i/o error occured.
126 * @exception ZipException if the file doesn't contain a valid zip
127 * archive.
129 public ZipFile(File file, int mode) throws ZipException, IOException
131 if ((mode & OPEN_DELETE) != 0)
133 throw new IllegalArgumentException
134 ("OPEN_DELETE mode not supported yet in java.util.zip.ZipFile");
136 this.raf = new RandomAccessFile(file, "r");
137 this.name = file.getPath();
141 * Read an unsigned short in little endian byte order from the given
142 * DataInput stream using the given byte buffer.
144 * @param di DataInput stream to read from.
145 * @param b the byte buffer to read in (must be at least 2 bytes long).
146 * @return The value read.
148 * @exception IOException if a i/o error occured.
149 * @exception EOFException if the file ends prematurely
151 private int readLeShort(DataInput di, byte[] b) throws IOException
153 di.readFully(b, 0, 2);
154 return (b[0] & 0xff) | (b[1] & 0xff) << 8;
158 * Read an int in little endian byte order from the given
159 * DataInput stream using the given byte buffer.
161 * @param di DataInput stream to read from.
162 * @param b the byte buffer to read in (must be at least 4 bytes long).
163 * @return The value read.
165 * @exception IOException if a i/o error occured.
166 * @exception EOFException if the file ends prematurely
168 private int readLeInt(DataInput di, byte[] b) throws IOException
170 di.readFully(b, 0, 4);
171 return ((b[0] & 0xff) | (b[1] & 0xff) << 8)
172 | ((b[2] & 0xff) | (b[3] & 0xff) << 8) << 16;
176 * Read an unsigned short in little endian byte order from the given
177 * byte buffer at the given offset.
179 * @param b the byte array to read from.
180 * @param off the offset to read from.
181 * @return The value read.
183 private int readLeShort(byte[] b, int off)
185 return (b[off] & 0xff) | (b[off+1] & 0xff) << 8;
189 * Read an int in little endian byte order from the given
190 * byte buffer at the given offset.
192 * @param b the byte array to read from.
193 * @param off the offset to read from.
194 * @return The value read.
196 private int readLeInt(byte[] b, int off)
198 return ((b[off] & 0xff) | (b[off+1] & 0xff) << 8)
199 | ((b[off+2] & 0xff) | (b[off+3] & 0xff) << 8) << 16;
204 * Read the central directory of a zip file and fill the entries
205 * array. This is called exactly once when first needed. It is called
206 * while holding the lock on <code>raf</code>.
208 * @exception IOException if a i/o error occured.
209 * @exception ZipException if the central directory is malformed
211 private void readEntries() throws ZipException, IOException
213 /* Search for the End Of Central Directory. When a zip comment is
214 * present the directory may start earlier.
215 * FIXME: This searches the whole file in a very slow manner if the
216 * file isn't a zip file.
218 long pos = raf.length() - ENDHDR;
219 byte[] ebs = new byte[CENHDR];
223 if (pos < 0)
224 throw new ZipException
225 ("central directory not found, probably not a zip file: " + name);
226 raf.seek(pos--);
228 while (readLeInt(raf, ebs) != ENDSIG);
230 if (raf.skipBytes(ENDTOT - ENDNRD) != ENDTOT - ENDNRD)
231 throw new EOFException(name);
232 int count = readLeShort(raf, ebs);
233 if (raf.skipBytes(ENDOFF - ENDSIZ) != ENDOFF - ENDSIZ)
234 throw new EOFException(name);
235 int centralOffset = readLeInt(raf, ebs);
237 entries = new HashMap(count+count/2);
238 raf.seek(centralOffset);
240 byte[] buffer = new byte[16];
241 for (int i = 0; i < count; i++)
243 raf.readFully(ebs);
244 if (readLeInt(ebs, 0) != CENSIG)
245 throw new ZipException("Wrong Central Directory signature: " + name);
247 int method = readLeShort(ebs, CENHOW);
248 int dostime = readLeInt(ebs, CENTIM);
249 int crc = readLeInt(ebs, CENCRC);
250 int csize = readLeInt(ebs, CENSIZ);
251 int size = readLeInt(ebs, CENLEN);
252 int nameLen = readLeShort(ebs, CENNAM);
253 int extraLen = readLeShort(ebs, CENEXT);
254 int commentLen = readLeShort(ebs, CENCOM);
256 int offset = readLeInt(ebs, CENOFF);
258 int needBuffer = Math.max(nameLen, commentLen);
259 if (buffer.length < needBuffer)
260 buffer = new byte[needBuffer];
262 raf.readFully(buffer, 0, nameLen);
263 String name = new String(buffer, 0, 0, nameLen);
265 ZipEntry entry = new ZipEntry(name);
266 entry.setMethod(method);
267 entry.setCrc(crc & 0xffffffffL);
268 entry.setSize(size & 0xffffffffL);
269 entry.setCompressedSize(csize & 0xffffffffL);
270 entry.setDOSTime(dostime);
271 if (extraLen > 0)
273 byte[] extra = new byte[extraLen];
274 raf.readFully(extra);
275 entry.setExtra(extra);
277 if (commentLen > 0)
279 raf.readFully(buffer, 0, commentLen);
280 entry.setComment(new String(buffer, 0, commentLen));
282 entry.offset = offset;
283 entries.put(name, entry);
288 * Closes the ZipFile. This also closes all input streams given by
289 * this class. After this is called, no further method should be
290 * called.
292 * @exception IOException if a i/o error occured.
294 public void close() throws IOException
296 synchronized (raf)
298 closed = true;
299 entries = null;
300 raf.close();
305 * Calls the <code>close()</code> method when this ZipFile has not yet
306 * been explicitly closed.
308 protected void finalize() throws IOException
310 if (!closed && raf != null) close();
314 * Returns an enumeration of all Zip entries in this Zip file.
316 public Enumeration entries()
320 return new ZipEntryEnumeration(getEntries().values().iterator());
322 catch (IOException ioe)
324 return null;
329 * Checks that the ZipFile is still open and reads entries when necessary.
331 * @exception IllegalStateException when the ZipFile has already been closed.
332 * @exception IOEexception when the entries could not be read.
334 private HashMap getEntries() throws IOException
336 synchronized(raf)
338 if (closed)
339 throw new IllegalStateException("ZipFile has closed: " + name);
341 if (entries == null)
342 readEntries();
344 return entries;
349 * Searches for a zip entry in this archive with the given name.
351 * @param the name. May contain directory components separated by
352 * slashes ('/').
353 * @return the zip entry, or null if no entry with that name exists.
355 public ZipEntry getEntry(String name)
359 HashMap entries = getEntries();
360 ZipEntry entry = (ZipEntry) entries.get(name);
361 return entry != null ? (ZipEntry) entry.clone() : null;
363 catch (IOException ioe)
365 return null;
370 //access should be protected by synchronized(raf)
371 private byte[] locBuf = new byte[LOCHDR];
374 * Checks, if the local header of the entry at index i matches the
375 * central directory, and returns the offset to the data.
377 * @param entry to check.
378 * @return the start offset of the (compressed) data.
380 * @exception IOException if a i/o error occured.
381 * @exception ZipException if the local header doesn't match the
382 * central directory header
384 private long checkLocalHeader(ZipEntry entry) throws IOException
386 synchronized (raf)
388 raf.seek(entry.offset);
389 raf.readFully(locBuf);
391 if (readLeInt(locBuf, 0) != LOCSIG)
392 throw new ZipException("Wrong Local header signature: " + name);
394 if (entry.getMethod() != readLeShort(locBuf, LOCHOW))
395 throw new ZipException("Compression method mismatch: " + name);
397 if (entry.getName().length() != readLeShort(locBuf, LOCNAM))
398 throw new ZipException("file name length mismatch: " + name);
400 int extraLen = entry.getName().length() + readLeShort(locBuf, LOCEXT);
401 return entry.offset + LOCHDR + extraLen;
406 * Creates an input stream reading the given zip entry as
407 * uncompressed data. Normally zip entry should be an entry
408 * returned by getEntry() or entries().
410 * This implementation returns null if the requested entry does not
411 * exist. This decision is not obviously correct, however, it does
412 * appear to mirror Sun's implementation, and it is consistant with
413 * their javadoc. On the other hand, the old JCL book, 2nd Edition,
414 * claims that this should return a "non-null ZIP entry". We have
415 * chosen for now ignore the old book, as modern versions of Ant (an
416 * important application) depend on this behaviour. See discussion
417 * in this thread:
418 * http://gcc.gnu.org/ml/java-patches/2004-q2/msg00602.html
420 * @param entry the entry to create an InputStream for.
421 * @return the input stream, or null if the requested entry does not exist.
423 * @exception IOException if a i/o error occured.
424 * @exception ZipException if the Zip archive is malformed.
426 public InputStream getInputStream(ZipEntry entry) throws IOException
428 HashMap entries = getEntries();
429 String name = entry.getName();
430 ZipEntry zipEntry = (ZipEntry) entries.get(name);
431 if (zipEntry == null)
432 return null;
434 long start = checkLocalHeader(zipEntry);
435 int method = zipEntry.getMethod();
436 InputStream is = new BufferedInputStream(new PartialInputStream
437 (raf, start, zipEntry.getCompressedSize()));
438 switch (method)
440 case ZipOutputStream.STORED:
441 return is;
442 case ZipOutputStream.DEFLATED:
443 return new InflaterInputStream(is, new Inflater(true));
444 default:
445 throw new ZipException("Unknown compression method " + method);
450 * Returns the (path) name of this zip file.
452 public String getName()
454 return name;
458 * Returns the number of entries in this zip file.
460 public int size()
464 return getEntries().size();
466 catch (IOException ioe)
468 return 0;
472 private static class ZipEntryEnumeration implements Enumeration
474 private final Iterator elements;
476 public ZipEntryEnumeration(Iterator elements)
478 this.elements = elements;
481 public boolean hasMoreElements()
483 return elements.hasNext();
486 public Object nextElement()
488 /* We return a clone, just to be safe that the user doesn't
489 * change the entry.
491 return ((ZipEntry)elements.next()).clone();
495 private static class PartialInputStream extends InputStream
497 private final RandomAccessFile raf;
498 long filepos, end;
500 public PartialInputStream(RandomAccessFile raf, long start, long len)
502 this.raf = raf;
503 filepos = start;
504 end = start + len;
507 public int available()
509 long amount = end - filepos;
510 if (amount > Integer.MAX_VALUE)
511 return Integer.MAX_VALUE;
512 return (int) amount;
515 public int read() throws IOException
517 if (filepos == end)
518 return -1;
519 synchronized (raf)
521 raf.seek(filepos++);
522 return raf.read();
526 public int read(byte[] b, int off, int len) throws IOException
528 if (len > end - filepos)
530 len = (int) (end - filepos);
531 if (len == 0)
532 return -1;
534 synchronized (raf)
536 raf.seek(filepos);
537 int count = raf.read(b, off, len);
538 if (count > 0)
539 filepos += len;
540 return count;
544 public long skip(long amount)
546 if (amount < 0)
547 throw new IllegalArgumentException();
548 if (amount > end - filepos)
549 amount = end - filepos;
550 filepos += amount;
551 return amount;