FileDocCategorySizeDatePackage
ZipFile.javaAPI DocApache Ant 1.7019260Wed Dec 13 06:16:20 GMT 2006org.apache.tools.zip

ZipFile

public class ZipFile extends Object
Replacement for java.util.ZipFile.

This class adds support for file name encodings other than UTF-8 (which is required to work on ZIP files created by native zip tools and is able to skip a preamble like the one found in self extracting archives. Furthermore it returns instances of org.apache.tools.zip.ZipEntry instead of java.util.zip.ZipEntry.

It doesn't extend java.util.zip.ZipFile as it would have to reimplement all methods anyway. Like java.util.ZipFile, it uses RandomAccessFile under the covers and supports compressed and uncompressed entries.

The method signatures mimic the ones of java.util.zip.ZipFile, with a couple of exceptions:

  • There is no getName method.
  • entries has been renamed to getEntries.
  • getEntries and getEntry return org.apache.tools.zip.ZipEntry instances.
  • close is allowed to throw IOException.

Fields Summary
private Hashtable
entries
Maps ZipEntrys to Longs, recording the offsets of the local file headers.
private Hashtable
nameMap
Maps String to ZipEntrys, name -> actual entry.
private String
encoding
The encoding to use for filenames and the file comment.

For a list of possible values see http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html. Defaults to the platform's default character encoding.

private RandomAccessFile
archive
The actual data source.
private static final int
CFH_LEN
private static final int
MIN_EOCD_SIZE
private static final int
CFD_LOCATOR_OFFSET
private static final long
LFH_OFFSET_FOR_FILENAME_LENGTH
Number of bytes in local file header up to the "length of filename" entry.
Constructors Summary
public ZipFile(File f)
Opens the given file for reading, assuming the platform's native encoding for file names.

param
f the archive.
throws
IOException if an error occurs while reading the file.


                                     
         
        this(f, null);
    
public ZipFile(String name)
Opens the given file for reading, assuming the platform's native encoding for file names.

param
name name of the archive.
throws
IOException if an error occurs while reading the file.

        this(new File(name), null);
    
public ZipFile(String name, String encoding)
Opens the given file for reading, assuming the specified encoding for file names.

param
name name of the archive.
param
encoding the encoding to use for file names
throws
IOException if an error occurs while reading the file.

        this(new File(name), encoding);
    
public ZipFile(File f, String encoding)
Opens the given file for reading, assuming the specified encoding for file names.

param
f the archive.
param
encoding the encoding to use for file names
throws
IOException if an error occurs while reading the file.

        this.encoding = encoding;
        archive = new RandomAccessFile(f, "r");
        try {
            populateFromCentralDirectory();
            resolveLocalFileHeaderData();
        } catch (IOException e) {
            try {
                archive.close();
            } catch (IOException e2) {
                // swallow, throw the original exception instead
            }
            throw e;
        }
    
Methods Summary
public voidclose()
Closes the archive.

throws
IOException if an error occurs closing the archive.

        archive.close();
    
public static voidcloseQuietly(org.apache.tools.zip.ZipFile zipfile)
close a zipfile quietly; throw no io fault, do nothing on a null parameter

param
zipfile file to close, can be null

        if (zipfile != null) {
            try {
                zipfile.close();
            } catch (IOException e) {
                //ignore
            }
        }
    
private static longdosToJavaTime(long dosTime)

        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.YEAR, (int) ((dosTime >> 25) & 0x7f) + 1980);
        cal.set(Calendar.MONTH, (int) ((dosTime >> 21) & 0x0f) - 1);
        cal.set(Calendar.DATE, (int) (dosTime >> 16) & 0x1f);
        cal.set(Calendar.HOUR_OF_DAY, (int) (dosTime >> 11) & 0x1f);
        cal.set(Calendar.MINUTE, (int) (dosTime >> 5) & 0x3f);
        cal.set(Calendar.SECOND, (int) (dosTime << 1) & 0x3e);
        return cal.getTime().getTime();
    
protected static java.util.DatefromDosTime(ZipLong zipDosTime)
Convert a DOS date/time field to a Date object.

param
zipDosTime contains the stored DOS time.
return
a Date instance corresponding to the given time.

        long dosTime = zipDosTime.getValue();
        return new Date(dosToJavaTime(dosTime));
    
public java.lang.StringgetEncoding()
The encoding to use for filenames and the file comment.

return
null if using the platform's default character encoding.

        return encoding;
    
public java.util.EnumerationgetEntries()
Returns all entries.

return
all entries as {@link ZipEntry} instances

        return entries.keys();
    
public ZipEntrygetEntry(java.lang.String name)
Returns a named entry - or null if no entry by that name exists.

param
name name of the entry.
return
the ZipEntry corresponding to the given name - or null if not present.

        return (ZipEntry) nameMap.get(name);
    
public java.io.InputStreamgetInputStream(ZipEntry ze)
Returns an InputStream for reading the contents of the given entry.

param
ze the entry to get the stream for.
return
a stream to read the entry from.
throws
IOException if unable to create an input stream from the zipenty
throws
ZipException if the zipentry has an unsupported compression method

        OffsetEntry offsetEntry = (OffsetEntry) entries.get(ze);
        if (offsetEntry == null) {
            return null;
        }
        long start = offsetEntry.dataOffset;
        BoundedInputStream bis =
            new BoundedInputStream(start, ze.getCompressedSize());
        switch (ze.getMethod()) {
            case ZipEntry.STORED:
                return bis;
            case ZipEntry.DEFLATED:
                bis.addDummy();
                return new InflaterInputStream(bis, new Inflater(true));
            default:
                throw new ZipException("Found unsupported compression method "
                                       + ze.getMethod());
        }
    
protected java.lang.StringgetString(byte[] bytes)
Retrieve a String from the given bytes using the encoding set for this ZipFile.

param
bytes the byte array to transform
return
String obtained by using the given encoding
throws
ZipException if the encoding cannot be recognized.

        if (encoding == null) {
            return new String(bytes);
        } else {
            try {
                return new String(bytes, encoding);
            } catch (UnsupportedEncodingException uee) {
                throw new ZipException(uee.getMessage());
            }
        }
    
private voidpopulateFromCentralDirectory()
Reads the central directory of the given archive and populates the internal tables with ZipEntry instances.

The ZipEntrys will know all data that can be obtained from the central directory alone, but not the data that requires the local file header or additional data to be read.


                                                        
      
          
        positionAtCentralDirectory();

        byte[] cfh = new byte[CFH_LEN];

        byte[] signatureBytes = new byte[4];
        archive.readFully(signatureBytes);
        long sig = ZipLong.getValue(signatureBytes);
        final long cfhSig = ZipLong.getValue(ZipOutputStream.CFH_SIG);
        while (sig == cfhSig) {
            archive.readFully(cfh);
            int off = 0;
            ZipEntry ze = new ZipEntry();

            int versionMadeBy = ZipShort.getValue(cfh, off);
            off += 2;
            ze.setPlatform((versionMadeBy >> 8) & 0x0F);

            off += 4; // skip version info and general purpose byte

            ze.setMethod(ZipShort.getValue(cfh, off));
            off += 2;

            // FIXME this is actually not very cpu cycles friendly as we are converting from
            // dos to java while the underlying Sun implementation will convert
            // from java to dos time for internal storage...
            long time = dosToJavaTime(ZipLong.getValue(cfh, off));
            ze.setTime(time);
            off += 4;

            ze.setCrc(ZipLong.getValue(cfh, off));
            off += 4;

            ze.setCompressedSize(ZipLong.getValue(cfh, off));
            off += 4;

            ze.setSize(ZipLong.getValue(cfh, off));
            off += 4;

            int fileNameLen = ZipShort.getValue(cfh, off);
            off += 2;

            int extraLen = ZipShort.getValue(cfh, off);
            off += 2;

            int commentLen = ZipShort.getValue(cfh, off);
            off += 2;

            off += 2; // disk number

            ze.setInternalAttributes(ZipShort.getValue(cfh, off));
            off += 2;

            ze.setExternalAttributes(ZipLong.getValue(cfh, off));
            off += 4;

            byte[] fileName = new byte[fileNameLen];
            archive.readFully(fileName);
            ze.setName(getString(fileName));


            // LFH offset,
            OffsetEntry offset = new OffsetEntry();
            offset.headerOffset = ZipLong.getValue(cfh, off);
            // data offset will be filled later
            entries.put(ze, offset);

            nameMap.put(ze.getName(), ze);

            archive.skipBytes(extraLen);

            byte[] comment = new byte[commentLen];
            archive.readFully(comment);
            ze.setComment(getString(comment));

            archive.readFully(signatureBytes);
            sig = ZipLong.getValue(signatureBytes);
        }
    
private voidpositionAtCentralDirectory()
Searches for the "End of central dir record", parses it and positions the stream at the first central directory record.


                             
      
          
        boolean found = false;
        long off = archive.length() - MIN_EOCD_SIZE;
        if (off >= 0) {
            archive.seek(off);
            byte[] sig = ZipOutputStream.EOCD_SIG;
            int curr = archive.read();
            while (curr != -1) {
                if (curr == sig[0]) {
                    curr = archive.read();
                    if (curr == sig[1]) {
                        curr = archive.read();
                        if (curr == sig[2]) {
                            curr = archive.read();
                            if (curr == sig[3]) {
                                found = true;
                                break;
                            }
                        }
                    }
                }
                archive.seek(--off);
                curr = archive.read();
            }
        }
        if (!found) {
            throw new ZipException("archive is not a ZIP archive");
        }
        archive.seek(off + CFD_LOCATOR_OFFSET);
        byte[] cfdOffset = new byte[4];
        archive.readFully(cfdOffset);
        archive.seek(ZipLong.getValue(cfdOffset));
    
private voidresolveLocalFileHeaderData()
Walks through all recorded entries and adds the data available from the local file header.

Also records the offsets for the data to read from the entries.


                                    
      
          
        Enumeration e = getEntries();
        while (e.hasMoreElements()) {
            ZipEntry ze = (ZipEntry) e.nextElement();
            OffsetEntry offsetEntry = (OffsetEntry) entries.get(ze);
            long offset = offsetEntry.headerOffset;
            archive.seek(offset + LFH_OFFSET_FOR_FILENAME_LENGTH);
            byte[] b = new byte[2];
            archive.readFully(b);
            int fileNameLen = ZipShort.getValue(b);
            archive.readFully(b);
            int extraFieldLen = ZipShort.getValue(b);
            archive.skipBytes(fileNameLen);
            byte[] localExtraData = new byte[extraFieldLen];
            archive.readFully(localExtraData);
            ze.setExtra(localExtraData);
            /*dataOffsets.put(ze,
                            new Long(offset + LFH_OFFSET_FOR_FILENAME_LENGTH
                                     + 2 + 2 + fileNameLen + extraFieldLen));
            */
            offsetEntry.dataOffset = offset + LFH_OFFSET_FOR_FILENAME_LENGTH
                                     + 2 + 2 + fileNameLen + extraFieldLen;
        }