Remove old autovect-branch by moving to "dead" directory.
[official-gcc.git] / old-autovect-branch / libjava / classpath / java / util / zip / ZipFile.java
blob4be845ea781101bb39217d189fdb7979a6e781df
1 /* ZipFile.java --
2 Copyright (C) 2001, 2002, 2003, 2004, 2005
3 Free Software Foundation, Inc.
5 This file is part of GNU Classpath.
7 GNU Classpath is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2, or (at your option)
10 any later version.
12 GNU Classpath is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with GNU Classpath; see the file COPYING. If not, write to the
19 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20 02110-1301 USA.
22 Linking this library statically or dynamically with other modules is
23 making a combined work based on this library. Thus, the terms and
24 conditions of the GNU General Public License cover the whole
25 combination.
27 As a special exception, the copyright holders of this library give you
28 permission to link this library with independent modules to produce an
29 executable, regardless of the license terms of these independent
30 modules, and to copy and distribute the resulting executable under
31 terms of your choice, provided that you also meet, for each linked
32 independent module, the terms and conditions of the license of that
33 module. An independent module is a module which is not derived from
34 or based on this library. If you modify this library, you may extend
35 this exception to your version of the library, but you are not
36 obligated to do so. If you do not wish to do so, delete this
37 exception statement from your version. */
40 package java.util.zip;
42 import gnu.java.util.EmptyEnumeration;
44 import java.io.BufferedInputStream;
45 import java.io.DataInput;
46 import java.io.EOFException;
47 import java.io.File;
48 import java.io.IOException;
49 import java.io.InputStream;
50 import java.io.RandomAccessFile;
51 import java.io.UnsupportedEncodingException;
52 import java.util.Enumeration;
53 import java.util.HashMap;
54 import java.util.Iterator;
56 /**
57 * This class represents a Zip archive. You can ask for the contained
58 * entries, or get an input stream for a file entry. The entry is
59 * automatically decompressed.
61 * This class is thread safe: You can open input streams for arbitrary
62 * entries in different threads.
64 * @author Jochen Hoenicke
65 * @author Artur Biesiadowski
67 public class ZipFile implements ZipConstants
70 /**
71 * Mode flag to open a zip file for reading.
73 public static final int OPEN_READ = 0x1;
75 /**
76 * Mode flag to delete a zip file after reading.
78 public static final int OPEN_DELETE = 0x4;
80 // Name of this zip file.
81 private final String name;
83 // File from which zip entries are read.
84 private final RandomAccessFile raf;
86 // The entries of this zip file when initialized and not yet closed.
87 private HashMap entries;
89 private boolean closed = false;
91 /**
92 * Opens a Zip file with the given name for reading.
93 * @exception IOException if a i/o error occured.
94 * @exception ZipException if the file doesn't contain a valid zip
95 * archive.
97 public ZipFile(String name) throws ZipException, IOException
99 this.raf = new RandomAccessFile(name, "r");
100 this.name = name;
101 checkZipFile();
105 * Opens a Zip file reading the given File.
106 * @exception IOException if a i/o error occured.
107 * @exception ZipException if the file doesn't contain a valid zip
108 * archive.
110 public ZipFile(File file) throws ZipException, IOException
112 this.raf = new RandomAccessFile(file, "r");
113 this.name = file.getPath();
114 checkZipFile();
118 * Opens a Zip file reading the given File in the given mode.
120 * If the OPEN_DELETE mode is specified, the zip file will be deleted at
121 * some time moment after it is opened. It will be deleted before the zip
122 * file is closed or the Virtual Machine exits.
124 * The contents of the zip file will be accessible until it is closed.
126 * @since JDK1.3
127 * @param mode Must be one of OPEN_READ or OPEN_READ | OPEN_DELETE
129 * @exception IOException if a i/o error occured.
130 * @exception ZipException if the file doesn't contain a valid zip
131 * archive.
133 public ZipFile(File file, int mode) throws ZipException, IOException
135 if (mode != OPEN_READ && mode != (OPEN_READ | OPEN_DELETE))
136 throw new IllegalArgumentException("invalid mode");
137 if ((mode & OPEN_DELETE) != 0)
138 file.deleteOnExit();
139 this.raf = new RandomAccessFile(file, "r");
140 this.name = file.getPath();
141 checkZipFile();
144 private void checkZipFile() throws IOException, ZipException
146 byte[] magicBuf = new byte[4];
147 boolean validRead = true;
149 try
151 raf.readFully(magicBuf);
153 catch (EOFException eof)
155 validRead = false;
158 if (validRead == false || readLeInt(magicBuf, 0) != LOCSIG)
160 raf.close();
161 throw new ZipException("Not a valid zip file");
166 * Checks if file is closed and throws an exception.
168 private void checkClosed()
170 if (closed)
171 throw new IllegalStateException("ZipFile has closed: " + name);
175 * Read an unsigned short in little endian byte order from the given
176 * DataInput stream using the given byte buffer.
178 * @param di DataInput stream to read from.
179 * @param b the byte buffer to read in (must be at least 2 bytes long).
180 * @return The value read.
182 * @exception IOException if a i/o error occured.
183 * @exception EOFException if the file ends prematurely
185 private int readLeShort(DataInput di, byte[] b) throws IOException
187 di.readFully(b, 0, 2);
188 return (b[0] & 0xff) | (b[1] & 0xff) << 8;
192 * Read an int in little endian byte order from the given
193 * DataInput stream using the given byte buffer.
195 * @param di DataInput stream to read from.
196 * @param b the byte buffer to read in (must be at least 4 bytes long).
197 * @return The value read.
199 * @exception IOException if a i/o error occured.
200 * @exception EOFException if the file ends prematurely
202 private int readLeInt(DataInput di, byte[] b) throws IOException
204 di.readFully(b, 0, 4);
205 return ((b[0] & 0xff) | (b[1] & 0xff) << 8)
206 | ((b[2] & 0xff) | (b[3] & 0xff) << 8) << 16;
210 * Read an unsigned short in little endian byte order from the given
211 * byte buffer at the given offset.
213 * @param b the byte array to read from.
214 * @param off the offset to read from.
215 * @return The value read.
217 private int readLeShort(byte[] b, int off)
219 return (b[off] & 0xff) | (b[off+1] & 0xff) << 8;
223 * Read an int in little endian byte order from the given
224 * byte buffer at the given offset.
226 * @param b the byte array to read from.
227 * @param off the offset to read from.
228 * @return The value read.
230 private int readLeInt(byte[] b, int off)
232 return ((b[off] & 0xff) | (b[off+1] & 0xff) << 8)
233 | ((b[off+2] & 0xff) | (b[off+3] & 0xff) << 8) << 16;
238 * Read the central directory of a zip file and fill the entries
239 * array. This is called exactly once when first needed. It is called
240 * while holding the lock on <code>raf</code>.
242 * @exception IOException if a i/o error occured.
243 * @exception ZipException if the central directory is malformed
245 private void readEntries() throws ZipException, IOException
247 /* Search for the End Of Central Directory. When a zip comment is
248 * present the directory may start earlier.
249 * FIXME: This searches the whole file in a very slow manner if the
250 * file isn't a zip file.
252 long pos = raf.length() - ENDHDR;
253 byte[] ebs = new byte[CENHDR];
257 if (pos < 0)
258 throw new ZipException
259 ("central directory not found, probably not a zip file: " + name);
260 raf.seek(pos--);
262 while (readLeInt(raf, ebs) != ENDSIG);
264 if (raf.skipBytes(ENDTOT - ENDNRD) != ENDTOT - ENDNRD)
265 throw new EOFException(name);
266 int count = readLeShort(raf, ebs);
267 if (raf.skipBytes(ENDOFF - ENDSIZ) != ENDOFF - ENDSIZ)
268 throw new EOFException(name);
269 int centralOffset = readLeInt(raf, ebs);
271 entries = new HashMap(count+count/2);
272 raf.seek(centralOffset);
274 byte[] buffer = new byte[16];
275 for (int i = 0; i < count; i++)
277 raf.readFully(ebs);
278 if (readLeInt(ebs, 0) != CENSIG)
279 throw new ZipException("Wrong Central Directory signature: " + name);
281 int method = readLeShort(ebs, CENHOW);
282 int dostime = readLeInt(ebs, CENTIM);
283 int crc = readLeInt(ebs, CENCRC);
284 int csize = readLeInt(ebs, CENSIZ);
285 int size = readLeInt(ebs, CENLEN);
286 int nameLen = readLeShort(ebs, CENNAM);
287 int extraLen = readLeShort(ebs, CENEXT);
288 int commentLen = readLeShort(ebs, CENCOM);
290 int offset = readLeInt(ebs, CENOFF);
292 int needBuffer = Math.max(nameLen, commentLen);
293 if (buffer.length < needBuffer)
294 buffer = new byte[needBuffer];
296 raf.readFully(buffer, 0, nameLen);
297 String name;
300 name = new String(buffer, 0, nameLen, "UTF-8");
302 catch (UnsupportedEncodingException uee)
304 throw new AssertionError(uee);
307 ZipEntry entry = new ZipEntry(name);
308 entry.setMethod(method);
309 entry.setCrc(crc & 0xffffffffL);
310 entry.setSize(size & 0xffffffffL);
311 entry.setCompressedSize(csize & 0xffffffffL);
312 entry.setDOSTime(dostime);
313 if (extraLen > 0)
315 byte[] extra = new byte[extraLen];
316 raf.readFully(extra);
317 entry.setExtra(extra);
319 if (commentLen > 0)
321 raf.readFully(buffer, 0, commentLen);
324 entry.setComment(new String(buffer, 0, commentLen, "UTF-8"));
326 catch (UnsupportedEncodingException uee)
328 throw new AssertionError(uee);
331 entry.offset = offset;
332 entries.put(name, entry);
337 * Closes the ZipFile. This also closes all input streams given by
338 * this class. After this is called, no further method should be
339 * called.
341 * @exception IOException if a i/o error occured.
343 public void close() throws IOException
345 RandomAccessFile raf = this.raf;
346 if (raf == null)
347 return;
349 synchronized (raf)
351 closed = true;
352 entries = null;
353 raf.close();
358 * Calls the <code>close()</code> method when this ZipFile has not yet
359 * been explicitly closed.
361 protected void finalize() throws IOException
363 if (!closed && raf != null) close();
367 * Returns an enumeration of all Zip entries in this Zip file.
369 * @exception IllegalStateException when the ZipFile has already been closed
371 public Enumeration entries()
373 checkClosed();
377 return new ZipEntryEnumeration(getEntries().values().iterator());
379 catch (IOException ioe)
381 return EmptyEnumeration.getInstance();
386 * Checks that the ZipFile is still open and reads entries when necessary.
388 * @exception IllegalStateException when the ZipFile has already been closed.
389 * @exception IOException when the entries could not be read.
391 private HashMap getEntries() throws IOException
393 synchronized(raf)
395 checkClosed();
397 if (entries == null)
398 readEntries();
400 return entries;
405 * Searches for a zip entry in this archive with the given name.
407 * @param name the name. May contain directory components separated by
408 * slashes ('/').
409 * @return the zip entry, or null if no entry with that name exists.
411 * @exception IllegalStateException when the ZipFile has already been closed
413 public ZipEntry getEntry(String name)
415 checkClosed();
419 HashMap entries = getEntries();
420 ZipEntry entry = (ZipEntry) entries.get(name);
421 // If we didn't find it, maybe it's a directory.
422 if (entry == null && !name.endsWith("/"))
423 entry = (ZipEntry) entries.get(name + '/');
424 return entry != null ? new ZipEntry(entry, name) : null;
426 catch (IOException ioe)
428 return null;
433 //access should be protected by synchronized(raf)
434 private byte[] locBuf = new byte[LOCHDR];
437 * Checks, if the local header of the entry at index i matches the
438 * central directory, and returns the offset to the data.
440 * @param entry to check.
441 * @return the start offset of the (compressed) data.
443 * @exception IOException if a i/o error occured.
444 * @exception ZipException if the local header doesn't match the
445 * central directory header
447 private long checkLocalHeader(ZipEntry entry) throws IOException
449 synchronized (raf)
451 raf.seek(entry.offset);
452 raf.readFully(locBuf);
454 if (readLeInt(locBuf, 0) != LOCSIG)
455 throw new ZipException("Wrong Local header signature: " + name);
457 if (entry.getMethod() != readLeShort(locBuf, LOCHOW))
458 throw new ZipException("Compression method mismatch: " + name);
460 if (entry.getName().length() != readLeShort(locBuf, LOCNAM))
461 throw new ZipException("file name length mismatch: " + name);
463 int extraLen = entry.getName().length() + readLeShort(locBuf, LOCEXT);
464 return entry.offset + LOCHDR + extraLen;
469 * Creates an input stream reading the given zip entry as
470 * uncompressed data. Normally zip entry should be an entry
471 * returned by getEntry() or entries().
473 * This implementation returns null if the requested entry does not
474 * exist. This decision is not obviously correct, however, it does
475 * appear to mirror Sun's implementation, and it is consistant with
476 * their javadoc. On the other hand, the old JCL book, 2nd Edition,
477 * claims that this should return a "non-null ZIP entry". We have
478 * chosen for now ignore the old book, as modern versions of Ant (an
479 * important application) depend on this behaviour. See discussion
480 * in this thread:
481 * http://gcc.gnu.org/ml/java-patches/2004-q2/msg00602.html
483 * @param entry the entry to create an InputStream for.
484 * @return the input stream, or null if the requested entry does not exist.
486 * @exception IllegalStateException when the ZipFile has already been closed
487 * @exception IOException if a i/o error occured.
488 * @exception ZipException if the Zip archive is malformed.
490 public InputStream getInputStream(ZipEntry entry) throws IOException
492 checkClosed();
494 HashMap entries = getEntries();
495 String name = entry.getName();
496 ZipEntry zipEntry = (ZipEntry) entries.get(name);
497 if (zipEntry == null)
498 return null;
500 long start = checkLocalHeader(zipEntry);
501 int method = zipEntry.getMethod();
502 InputStream is = new BufferedInputStream(new PartialInputStream
503 (raf, start, zipEntry.getCompressedSize()));
504 switch (method)
506 case ZipOutputStream.STORED:
507 return is;
508 case ZipOutputStream.DEFLATED:
509 return new InflaterInputStream(is, new Inflater(true));
510 default:
511 throw new ZipException("Unknown compression method " + method);
516 * Returns the (path) name of this zip file.
518 public String getName()
520 return name;
524 * Returns the number of entries in this zip file.
526 * @exception IllegalStateException when the ZipFile has already been closed
528 public int size()
530 checkClosed();
534 return getEntries().size();
536 catch (IOException ioe)
538 return 0;
542 private static class ZipEntryEnumeration implements Enumeration
544 private final Iterator elements;
546 public ZipEntryEnumeration(Iterator elements)
548 this.elements = elements;
551 public boolean hasMoreElements()
553 return elements.hasNext();
556 public Object nextElement()
558 /* We return a clone, just to be safe that the user doesn't
559 * change the entry.
561 return ((ZipEntry)elements.next()).clone();
565 private static class PartialInputStream extends InputStream
567 private final RandomAccessFile raf;
568 long filepos, end;
570 public PartialInputStream(RandomAccessFile raf, long start, long len)
572 this.raf = raf;
573 filepos = start;
574 end = start + len;
577 public int available()
579 long amount = end - filepos;
580 if (amount > Integer.MAX_VALUE)
581 return Integer.MAX_VALUE;
582 return (int) amount;
585 public int read() throws IOException
587 if (filepos == end)
588 return -1;
589 synchronized (raf)
591 raf.seek(filepos++);
592 return raf.read();
596 public int read(byte[] b, int off, int len) throws IOException
598 if (len > end - filepos)
600 len = (int) (end - filepos);
601 if (len == 0)
602 return -1;
604 synchronized (raf)
606 raf.seek(filepos);
607 int count = raf.read(b, off, len);
608 if (count > 0)
609 filepos += len;
610 return count;
614 public long skip(long amount)
616 if (amount < 0)
617 throw new IllegalArgumentException();
618 if (amount > end - filepos)
619 amount = end - filepos;
620 filepos += amount;
621 return amount;