Merge from the pain train
[official-gcc.git] / libjava / java / util / zip / ZipFile.java
blobb2dbe705bbf9c5a5fdbc7283253950d2a89b6f7f
1 /* ZipFile.java --
2 Copyright (C) 2001, 2002, 2003, 2004, 2005 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 * @since JDK1.3
121 * @param mode Must be one of OPEN_READ or OPEN_READ | OPEN_DELETE
123 * @exception IOException if a i/o error occured.
124 * @exception ZipException if the file doesn't contain a valid zip
125 * archive.
127 public ZipFile(File file, int mode) throws ZipException, IOException
129 if (mode != OPEN_READ && mode != (OPEN_READ | OPEN_DELETE))
130 throw new IllegalArgumentException("invalid mode");
131 if ((mode & OPEN_DELETE) != 0)
132 file.deleteOnExit();
133 this.raf = new RandomAccessFile(file, "r");
134 this.name = file.getPath();
138 * Read an unsigned short in little endian byte order from the given
139 * DataInput stream using the given byte buffer.
141 * @param di DataInput stream to read from.
142 * @param b the byte buffer to read in (must be at least 2 bytes long).
143 * @return The value read.
145 * @exception IOException if a i/o error occured.
146 * @exception EOFException if the file ends prematurely
148 private int readLeShort(DataInput di, byte[] b) throws IOException
150 di.readFully(b, 0, 2);
151 return (b[0] & 0xff) | (b[1] & 0xff) << 8;
155 * Read an int in little endian byte order from the given
156 * DataInput stream using the given byte buffer.
158 * @param di DataInput stream to read from.
159 * @param b the byte buffer to read in (must be at least 4 bytes long).
160 * @return The value read.
162 * @exception IOException if a i/o error occured.
163 * @exception EOFException if the file ends prematurely
165 private int readLeInt(DataInput di, byte[] b) throws IOException
167 di.readFully(b, 0, 4);
168 return ((b[0] & 0xff) | (b[1] & 0xff) << 8)
169 | ((b[2] & 0xff) | (b[3] & 0xff) << 8) << 16;
173 * Read an unsigned short in little endian byte order from the given
174 * byte buffer at the given offset.
176 * @param b the byte array to read from.
177 * @param off the offset to read from.
178 * @return The value read.
180 private int readLeShort(byte[] b, int off)
182 return (b[off] & 0xff) | (b[off+1] & 0xff) << 8;
186 * Read an int in little endian byte order from the given
187 * byte buffer at the given offset.
189 * @param b the byte array to read from.
190 * @param off the offset to read from.
191 * @return The value read.
193 private int readLeInt(byte[] b, int off)
195 return ((b[off] & 0xff) | (b[off+1] & 0xff) << 8)
196 | ((b[off+2] & 0xff) | (b[off+3] & 0xff) << 8) << 16;
201 * Read the central directory of a zip file and fill the entries
202 * array. This is called exactly once when first needed. It is called
203 * while holding the lock on <code>raf</code>.
205 * @exception IOException if a i/o error occured.
206 * @exception ZipException if the central directory is malformed
208 private void readEntries() throws ZipException, IOException
210 /* Search for the End Of Central Directory. When a zip comment is
211 * present the directory may start earlier.
212 * FIXME: This searches the whole file in a very slow manner if the
213 * file isn't a zip file.
215 long pos = raf.length() - ENDHDR;
216 byte[] ebs = new byte[CENHDR];
220 if (pos < 0)
221 throw new ZipException
222 ("central directory not found, probably not a zip file: " + name);
223 raf.seek(pos--);
225 while (readLeInt(raf, ebs) != ENDSIG);
227 if (raf.skipBytes(ENDTOT - ENDNRD) != ENDTOT - ENDNRD)
228 throw new EOFException(name);
229 int count = readLeShort(raf, ebs);
230 if (raf.skipBytes(ENDOFF - ENDSIZ) != ENDOFF - ENDSIZ)
231 throw new EOFException(name);
232 int centralOffset = readLeInt(raf, ebs);
234 entries = new HashMap(count+count/2);
235 raf.seek(centralOffset);
237 byte[] buffer = new byte[16];
238 for (int i = 0; i < count; i++)
240 raf.readFully(ebs);
241 if (readLeInt(ebs, 0) != CENSIG)
242 throw new ZipException("Wrong Central Directory signature: " + name);
244 int method = readLeShort(ebs, CENHOW);
245 int dostime = readLeInt(ebs, CENTIM);
246 int crc = readLeInt(ebs, CENCRC);
247 int csize = readLeInt(ebs, CENSIZ);
248 int size = readLeInt(ebs, CENLEN);
249 int nameLen = readLeShort(ebs, CENNAM);
250 int extraLen = readLeShort(ebs, CENEXT);
251 int commentLen = readLeShort(ebs, CENCOM);
253 int offset = readLeInt(ebs, CENOFF);
255 int needBuffer = Math.max(nameLen, commentLen);
256 if (buffer.length < needBuffer)
257 buffer = new byte[needBuffer];
259 raf.readFully(buffer, 0, nameLen);
260 String name = new String(buffer, 0, 0, nameLen);
262 ZipEntry entry = new ZipEntry(name);
263 entry.setMethod(method);
264 entry.setCrc(crc & 0xffffffffL);
265 entry.setSize(size & 0xffffffffL);
266 entry.setCompressedSize(csize & 0xffffffffL);
267 entry.setDOSTime(dostime);
268 if (extraLen > 0)
270 byte[] extra = new byte[extraLen];
271 raf.readFully(extra);
272 entry.setExtra(extra);
274 if (commentLen > 0)
276 raf.readFully(buffer, 0, commentLen);
277 entry.setComment(new String(buffer, 0, commentLen));
279 entry.offset = offset;
280 entries.put(name, entry);
285 * Closes the ZipFile. This also closes all input streams given by
286 * this class. After this is called, no further method should be
287 * called.
289 * @exception IOException if a i/o error occured.
291 public void close() throws IOException
293 synchronized (raf)
295 closed = true;
296 entries = null;
297 raf.close();
302 * Calls the <code>close()</code> method when this ZipFile has not yet
303 * been explicitly closed.
305 protected void finalize() throws IOException
307 if (!closed && raf != null) close();
311 * Returns an enumeration of all Zip entries in this Zip file.
313 public Enumeration entries()
317 return new ZipEntryEnumeration(getEntries().values().iterator());
319 catch (IOException ioe)
321 return null;
326 * Checks that the ZipFile is still open and reads entries when necessary.
328 * @exception IllegalStateException when the ZipFile has already been closed.
329 * @exception IOEexception when the entries could not be read.
331 private HashMap getEntries() throws IOException
333 synchronized(raf)
335 if (closed)
336 throw new IllegalStateException("ZipFile has closed: " + name);
338 if (entries == null)
339 readEntries();
341 return entries;
346 * Searches for a zip entry in this archive with the given name.
348 * @param the name. May contain directory components separated by
349 * slashes ('/').
350 * @return the zip entry, or null if no entry with that name exists.
352 public ZipEntry getEntry(String name)
356 HashMap entries = getEntries();
357 ZipEntry entry = (ZipEntry) entries.get(name);
358 return entry != null ? (ZipEntry) entry.clone() : null;
360 catch (IOException ioe)
362 return null;
367 //access should be protected by synchronized(raf)
368 private byte[] locBuf = new byte[LOCHDR];
371 * Checks, if the local header of the entry at index i matches the
372 * central directory, and returns the offset to the data.
374 * @param entry to check.
375 * @return the start offset of the (compressed) data.
377 * @exception IOException if a i/o error occured.
378 * @exception ZipException if the local header doesn't match the
379 * central directory header
381 private long checkLocalHeader(ZipEntry entry) throws IOException
383 synchronized (raf)
385 raf.seek(entry.offset);
386 raf.readFully(locBuf);
388 if (readLeInt(locBuf, 0) != LOCSIG)
389 throw new ZipException("Wrong Local header signature: " + name);
391 if (entry.getMethod() != readLeShort(locBuf, LOCHOW))
392 throw new ZipException("Compression method mismatch: " + name);
394 if (entry.getName().length() != readLeShort(locBuf, LOCNAM))
395 throw new ZipException("file name length mismatch: " + name);
397 int extraLen = entry.getName().length() + readLeShort(locBuf, LOCEXT);
398 return entry.offset + LOCHDR + extraLen;
403 * Creates an input stream reading the given zip entry as
404 * uncompressed data. Normally zip entry should be an entry
405 * returned by getEntry() or entries().
407 * This implementation returns null if the requested entry does not
408 * exist. This decision is not obviously correct, however, it does
409 * appear to mirror Sun's implementation, and it is consistant with
410 * their javadoc. On the other hand, the old JCL book, 2nd Edition,
411 * claims that this should return a "non-null ZIP entry". We have
412 * chosen for now ignore the old book, as modern versions of Ant (an
413 * important application) depend on this behaviour. See discussion
414 * in this thread:
415 * http://gcc.gnu.org/ml/java-patches/2004-q2/msg00602.html
417 * @param entry the entry to create an InputStream for.
418 * @return the input stream, or null if the requested entry does not exist.
420 * @exception IOException if a i/o error occured.
421 * @exception ZipException if the Zip archive is malformed.
423 public InputStream getInputStream(ZipEntry entry) throws IOException
425 HashMap entries = getEntries();
426 String name = entry.getName();
427 ZipEntry zipEntry = (ZipEntry) entries.get(name);
428 if (zipEntry == null)
429 return null;
431 long start = checkLocalHeader(zipEntry);
432 int method = zipEntry.getMethod();
433 InputStream is = new BufferedInputStream(new PartialInputStream
434 (raf, start, zipEntry.getCompressedSize()));
435 switch (method)
437 case ZipOutputStream.STORED:
438 return is;
439 case ZipOutputStream.DEFLATED:
440 return new InflaterInputStream(is, new Inflater(true));
441 default:
442 throw new ZipException("Unknown compression method " + method);
447 * Returns the (path) name of this zip file.
449 public String getName()
451 return name;
455 * Returns the number of entries in this zip file.
457 public int size()
461 return getEntries().size();
463 catch (IOException ioe)
465 return 0;
469 private static class ZipEntryEnumeration implements Enumeration
471 private final Iterator elements;
473 public ZipEntryEnumeration(Iterator elements)
475 this.elements = elements;
478 public boolean hasMoreElements()
480 return elements.hasNext();
483 public Object nextElement()
485 /* We return a clone, just to be safe that the user doesn't
486 * change the entry.
488 return ((ZipEntry)elements.next()).clone();
492 private static class PartialInputStream extends InputStream
494 private final RandomAccessFile raf;
495 long filepos, end;
497 public PartialInputStream(RandomAccessFile raf, long start, long len)
499 this.raf = raf;
500 filepos = start;
501 end = start + len;
504 public int available()
506 long amount = end - filepos;
507 if (amount > Integer.MAX_VALUE)
508 return Integer.MAX_VALUE;
509 return (int) amount;
512 public int read() throws IOException
514 if (filepos == end)
515 return -1;
516 synchronized (raf)
518 raf.seek(filepos++);
519 return raf.read();
523 public int read(byte[] b, int off, int len) throws IOException
525 if (len > end - filepos)
527 len = (int) (end - filepos);
528 if (len == 0)
529 return -1;
531 synchronized (raf)
533 raf.seek(filepos);
534 int count = raf.read(b, off, len);
535 if (count > 0)
536 filepos += len;
537 return count;
541 public long skip(long amount)
543 if (amount < 0)
544 throw new IllegalArgumentException();
545 if (amount > end - filepos)
546 amount = end - filepos;
547 filepos += amount;
548 return amount;