langtools/src/share/classes/com/sun/tools/javac/zip/ZipFileIndex.java
changeset 10 06bc494ca11e
child 655 1ebc7ce89018
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/share/classes/com/sun/tools/javac/zip/ZipFileIndex.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,1211 @@
+package com.sun.tools.javac.zip;
+
+import java.io.*;
+import java.text.MessageFormat;
+import java.util.*;
+import java.util.List;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.zip.*;
+
+/** This class implements building of index of a zip archive and access to it's context.
+ *  It also uses prebuild index if available. It supports invocations where it will
+ *  serialize an optimized zip index file to disk.
+ *
+ *  In oreder to use secondary index file make sure the option "usezipindex" is in the Options object,
+ *  when JavacFileManager is invoked. (You can pass "-XDusezipindex" on the command line.
+ *
+ *  Location where to look for/generate optimized zip index files can be provided using
+ *  "-XDcachezipindexdir=<directory>". If this flag is not provided, the dfault location is
+ *  the value of the "java.io.tmpdir" system property.
+ *
+ *  If key "-XDwritezipindexfiles" is specified, there will be new optimized index file
+ *  created for each archive, used by the compiler for compilation, at location,
+ *  specified by "cachezipindexdir" option.
+ *
+ * If nonBatchMode option is specified (-XDnonBatchMode) the compiler will use timestamp
+ * checking to reindex the zip files if it is needed. In batch mode the timestamps are not checked
+ * and the compiler uses the cached indexes.
+ */
+public class ZipFileIndex {
+    private static final String MIN_CHAR = String.valueOf(Character.MIN_VALUE);
+    private static final String MAX_CHAR = String.valueOf(Character.MAX_VALUE);
+
+    public final static long NOT_MODIFIED = Long.MIN_VALUE;
+
+    private static Map<File, ZipFileIndex> zipFileIndexCache = new HashMap<File, ZipFileIndex>();
+    private static ReentrantLock lock = new ReentrantLock();
+
+    private static boolean NON_BATCH_MODE = System.getProperty("nonBatchMode") != null;// TODO: Use -XD compiler switch for this.
+
+    private Map<String, DirectoryEntry> directories = Collections.<String, DirectoryEntry>emptyMap();
+    private Set<String> allDirs = Collections.<String>emptySet();
+
+    // ZipFileIndex data entries
+    private File zipFile;
+    private long zipFileLastModified = NOT_MODIFIED;
+    private RandomAccessFile zipRandomFile;
+    private ZipFileIndexEntry[] entries;
+
+    private boolean readFromIndex = false;
+    private File zipIndexFile = null;
+    private boolean triedToReadIndex = false;
+    private int symbolFilePrefixLength = 0;
+    private boolean hasPopulatedData = false;
+    private long lastReferenceTimeStamp = NOT_MODIFIED;
+
+    private boolean usePreindexedCache = false;
+    private String preindexedCacheLocation = null;
+
+    private boolean writeIndex = false;
+
+    /**
+     * Returns a list of all ZipFileIndex entries
+     *
+     * @return A list of ZipFileIndex entries, or an empty list
+     */
+    public static List<ZipFileIndex> getZipFileIndexes() {
+        return getZipFileIndexes(false);
+    }
+
+    /**
+     * Returns a list of all ZipFileIndex entries
+     *
+     * @param openedOnly If true it returns a list of only opened ZipFileIndex entries, otherwise
+     *                   all ZipFileEntry(s) are included into the list.
+     * @return A list of ZipFileIndex entries, or an empty list
+     */
+    public static List<ZipFileIndex> getZipFileIndexes(boolean openedOnly) {
+        List<ZipFileIndex> zipFileIndexes = new ArrayList<ZipFileIndex>();
+        lock.lock();
+        try {
+            zipFileIndexes.addAll(zipFileIndexCache.values());
+
+            if (openedOnly) {
+                for(ZipFileIndex elem : zipFileIndexes) {
+                    if (!elem.isOpen()) {
+                        zipFileIndexes.remove(elem);
+                    }
+                }
+            }
+        }
+        finally {
+            lock.unlock();
+        }
+        return zipFileIndexes;
+    }
+
+    public boolean isOpen() {
+        lock.lock();
+        try {
+            return zipRandomFile != null;
+        }
+        finally {
+            lock.unlock();
+        }
+    }
+
+    public static ZipFileIndex getZipFileIndex(File zipFile, int symbolFilePrefixLen, boolean useCache, String cacheLocation, boolean writeIndex) throws IOException {
+        ZipFileIndex zi = null;
+        lock.lock();
+        try {
+            zi = getExistingZipIndex(zipFile);
+
+            if (zi == null || (zi != null && zipFile.lastModified() != zi.zipFileLastModified)) {
+                zi = new ZipFileIndex(zipFile, symbolFilePrefixLen, writeIndex,
+                        useCache, cacheLocation);
+                zipFileIndexCache.put(zipFile, zi);
+            }
+        }
+        finally {
+            lock.unlock();
+        }
+        return zi;
+    }
+
+    public static ZipFileIndex getExistingZipIndex(File zipFile) {
+        lock.lock();
+        try {
+            return zipFileIndexCache.get(zipFile);
+        }
+        finally {
+            lock.unlock();
+        }
+    }
+
+    public static void clearCache() {
+        lock.lock();
+        try {
+            zipFileIndexCache.clear();
+        }
+        finally {
+            lock.unlock();
+        }
+    }
+
+    public static void clearCache(long timeNotUsed) {
+        lock.lock();
+        try {
+            Iterator<File> cachedFileIterator = zipFileIndexCache.keySet().iterator();
+            while (cachedFileIterator.hasNext()) {
+                File cachedFile = cachedFileIterator.next();
+                ZipFileIndex cachedZipIndex = zipFileIndexCache.get(cachedFile);
+                if (cachedZipIndex != null) {
+                    long timeToTest = cachedZipIndex.lastReferenceTimeStamp + timeNotUsed;
+                    if (timeToTest < cachedZipIndex.lastReferenceTimeStamp || // Overflow...
+                            System.currentTimeMillis() > timeToTest) {
+                        zipFileIndexCache.remove(cachedFile);
+                    }
+                }
+            }
+        }
+        finally {
+            lock.unlock();
+        }
+    }
+
+    public static void removeFromCache(File file) {
+        lock.lock();
+        try {
+            zipFileIndexCache.remove(file);
+        }
+        finally {
+            lock.unlock();
+        }
+    }
+
+    /** Sets already opened list of ZipFileIndexes from an outside client
+      * of the compiler. This functionality should be used in a non-batch clients of the compiler.
+      */
+    public static void setOpenedIndexes(List<ZipFileIndex>indexes) throws IllegalStateException {
+        lock.lock();
+        try {
+            if (zipFileIndexCache.isEmpty()) {
+                throw new IllegalStateException("Setting opened indexes should be called only when the ZipFileCache is empty. Call JavacFileManager.flush() before calling this method.");
+            }
+
+            for (ZipFileIndex zfi : indexes) {
+                zipFileIndexCache.put(zfi.zipFile, zfi);
+            }
+        }
+        finally {
+            lock.unlock();
+        }
+    }
+
+    private ZipFileIndex(File zipFile, int symbolFilePrefixLen, boolean writeIndex,
+            boolean useCache, String cacheLocation) throws IOException {
+        this.zipFile = zipFile;
+        this.symbolFilePrefixLength = symbolFilePrefixLen;
+        this.writeIndex = writeIndex;
+        this.usePreindexedCache = useCache;
+        this.preindexedCacheLocation = cacheLocation;
+
+        if (zipFile != null) {
+            this.zipFileLastModified = zipFile.lastModified();
+        }
+
+        // Validate integrity of the zip file
+        checkIndex();
+    }
+
+    public String toString() {
+        return "ZipFileIndex of file:(" + zipFile + ")";
+    }
+
+    // Just in case...
+    protected void finalize() {
+        closeFile();
+    }
+
+    private boolean isUpToDate() {
+        if (zipFile != null &&
+                ((!NON_BATCH_MODE) || zipFileLastModified == zipFile.lastModified()) &&
+                hasPopulatedData) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Here we need to make sure that the ZipFileIndex is valid. Check the timestamp of the file and
+     * if its the same as the one at the time the index was build we don't need to reopen anything.
+     */
+    private void checkIndex() throws IOException {
+        boolean isUpToDate = true;
+        if (!isUpToDate()) {
+            closeFile();
+            isUpToDate = false;
+        }
+
+        if (zipRandomFile != null || isUpToDate) {
+            lastReferenceTimeStamp = System.currentTimeMillis();
+            return;
+        }
+
+        hasPopulatedData = true;
+
+        if (readIndex()) {
+            lastReferenceTimeStamp = System.currentTimeMillis();
+            return;
+        }
+
+        directories = Collections.<String, DirectoryEntry>emptyMap();
+        allDirs = Collections.<String>emptySet();
+
+        try {
+            openFile();
+            long totalLength = zipRandomFile.length();
+            ZipDirectory directory = new ZipDirectory(zipRandomFile, 0L, totalLength, this);
+            directory.buildIndex();
+        } finally {
+            if (zipRandomFile != null) {
+                closeFile();
+            }
+        }
+
+        lastReferenceTimeStamp = System.currentTimeMillis();
+    }
+
+    private void openFile() throws FileNotFoundException {
+        if (zipRandomFile == null && zipFile != null) {
+            zipRandomFile = new RandomAccessFile(zipFile, "r");
+        }
+    }
+
+    private void cleanupState() {
+        // Make sure there is a valid but empty index if the file doesn't exist
+        entries = ZipFileIndexEntry.EMPTY_ARRAY;
+        directories = Collections.<String, DirectoryEntry>emptyMap();
+        zipFileLastModified = NOT_MODIFIED;
+        allDirs = Collections.<String>emptySet();
+    }
+
+    public void close() {
+        lock.lock();
+        try {
+            writeIndex();
+            closeFile();
+        }
+        finally {
+            lock.unlock();
+        }
+    }
+
+    private void closeFile() {
+        if (zipRandomFile != null) {
+            try {
+                zipRandomFile.close();
+            } catch (IOException ex) {
+            }
+            zipRandomFile = null;
+        }
+    }
+
+    /**
+     * Returns the ZipFileIndexEntry for an absolute path, if there is one.
+     */
+    public ZipFileIndexEntry getZipIndexEntry(String path) {
+        if (File.separatorChar != '/') {
+            path = path.replace('/', File.separatorChar);
+        }
+        lock.lock();
+        try {
+            checkIndex();
+            String lookFor = "";
+            int lastSepIndex = path.lastIndexOf(File.separatorChar);
+            boolean noSeparator = false;
+            if (lastSepIndex == -1) {
+                noSeparator = true;
+            }
+
+            DirectoryEntry de = directories.get(noSeparator ? "" : path.substring(0, lastSepIndex));
+
+            lookFor = path.substring(noSeparator ? 0 : lastSepIndex + 1);
+
+            return de == null ? null : de.getEntry(lookFor);
+        }
+        catch (IOException e) {
+            return null;
+        }
+        finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Returns a javac List of filenames within an absolute path in the ZipFileIndex.
+     */
+    public com.sun.tools.javac.util.List<String> getFiles(String path) {
+        if (File.separatorChar != '/') {
+            path = path.replace('/', File.separatorChar);
+        }
+
+        lock.lock();
+        try {
+            checkIndex();
+
+            DirectoryEntry de = directories.get(path);
+            com.sun.tools.javac.util.List<String> ret = de == null ? null : de.getFiles();
+
+            if (ret == null) {
+                return com.sun.tools.javac.util.List.<String>nil();
+            }
+            return ret;
+        }
+        catch (IOException e) {
+            return com.sun.tools.javac.util.List.<String>nil();
+        }
+        finally {
+            lock.unlock();
+        }
+    }
+
+    public List<String> getAllDirectories(String path) {
+
+        if (File.separatorChar != '/') {
+            path = path.replace('/', File.separatorChar);
+        }
+
+        lock.lock();
+        try {
+            checkIndex();
+            path = path.intern();
+
+            DirectoryEntry de = directories.get(path);
+            com.sun.tools.javac.util.List<String> ret = de == null ? null : de.getDirectories();
+
+            if (ret == null) {
+                return com.sun.tools.javac.util.List.<String>nil();
+            }
+
+            return ret;
+        }
+        catch (IOException e) {
+            return com.sun.tools.javac.util.List.<String>nil();
+        }
+        finally {
+            lock.unlock();
+        }
+    }
+
+    public Set<String> getAllDirectories() {
+        lock.lock();
+        try {
+            checkIndex();
+            if (allDirs == Collections.EMPTY_SET) {
+                Set<String> alldirs = new HashSet<String>();
+                Iterator<String> dirsIter = directories.keySet().iterator();
+                while (dirsIter.hasNext()) {
+                    alldirs.add(new String(dirsIter.next()));
+                }
+
+                allDirs = alldirs;
+            }
+
+            return allDirs;
+        }
+        catch (IOException e) {
+            return Collections.<String>emptySet();
+        }
+        finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Tests if a specific path exists in the zip.  This method will return true
+     * for file entries and directories.
+     *
+     * @param path A path within the zip.
+     * @return True if the path is a file or dir, false otherwise.
+     */
+    public boolean contains(String path) {
+        lock.lock();
+        try {
+            checkIndex();
+            return getZipIndexEntry(path) != null;
+        }
+        catch (IOException e) {
+            return false;
+        }
+        finally {
+            lock.unlock();
+        }
+    }
+
+    public boolean isDirectory(String path) throws IOException {
+        lock.lock();
+        try {
+            // The top level in a zip file is always a directory.
+            if (path.length() == 0) {
+                lastReferenceTimeStamp = System.currentTimeMillis();
+                return true;
+            }
+
+            if (File.separatorChar != '/')
+                path = path.replace('/', File.separatorChar);
+            checkIndex();
+            return directories.get(path) != null;
+        }
+        finally {
+            lock.unlock();
+        }
+    }
+
+    public long getLastModified(String path) throws IOException {
+        lock.lock();
+        try {
+            ZipFileIndexEntry entry = getZipIndexEntry(path);
+            if (entry == null)
+                throw new FileNotFoundException();
+            return entry.getLastModified();
+        }
+        finally {
+            lock.unlock();
+        }
+    }
+
+    public int length(String path) throws IOException {
+        lock.lock();
+        try {
+            ZipFileIndexEntry entry = getZipIndexEntry(path);
+            if (entry == null)
+                throw new FileNotFoundException();
+
+            if (entry.isDir) {
+                return 0;
+            }
+
+            byte[] header = getHeader(entry);
+            // entry is not compressed?
+            if (get2ByteLittleEndian(header, 8) == 0) {
+                return entry.compressedSize;
+            } else {
+                return entry.size;
+            }
+        }
+        finally {
+            lock.unlock();
+        }
+    }
+
+    public byte[] read(String path) throws IOException {
+        lock.lock();
+        try {
+            ZipFileIndexEntry entry = getZipIndexEntry(path);
+            if (entry == null)
+                throw new FileNotFoundException(MessageFormat.format("Path not found in ZIP: {0}", path));
+            return read(entry);
+        }
+        finally {
+            lock.unlock();
+        }
+    }
+
+    public byte[] read(ZipFileIndexEntry entry) throws IOException {
+        lock.lock();
+        try {
+            openFile();
+            byte[] result = readBytes(entry);
+            closeFile();
+            return result;
+        }
+        finally {
+            lock.unlock();
+        }
+    }
+
+    public int read(String path, byte[] buffer) throws IOException {
+        lock.lock();
+        try {
+            ZipFileIndexEntry entry = getZipIndexEntry(path);
+            if (entry == null)
+                throw new FileNotFoundException();
+            return read(entry, buffer);
+        }
+        finally {
+            lock.unlock();
+        }
+    }
+
+    public int read(ZipFileIndexEntry entry, byte[] buffer)
+            throws IOException {
+        lock.lock();
+        try {
+            int result = readBytes(entry, buffer);
+            return result;
+        }
+        finally {
+            lock.unlock();
+        }
+    }
+
+    private byte[] readBytes(ZipFileIndexEntry entry) throws IOException {
+        byte[] header = getHeader(entry);
+        int csize = entry.compressedSize;
+        byte[] cbuf = new byte[csize];
+        zipRandomFile.skipBytes(get2ByteLittleEndian(header, 26) + get2ByteLittleEndian(header, 28));
+        zipRandomFile.readFully(cbuf, 0, csize);
+
+        // is this compressed - offset 8 in the ZipEntry header
+        if (get2ByteLittleEndian(header, 8) == 0)
+            return cbuf;
+
+        int size = entry.size;
+        byte[] buf = new byte[size];
+        if (inflate(cbuf, buf) != size)
+            throw new ZipException("corrupted zip file");
+
+        return buf;
+    }
+
+    /**
+     *
+     */
+    private int readBytes(ZipFileIndexEntry entry, byte[] buffer) throws IOException {
+        byte[] header = getHeader(entry);
+
+        // entry is not compressed?
+        if (get2ByteLittleEndian(header, 8) == 0) {
+            zipRandomFile.skipBytes(get2ByteLittleEndian(header, 26) + get2ByteLittleEndian(header, 28));
+            int offset = 0;
+            int size = buffer.length;
+            while (offset < size) {
+                int count = zipRandomFile.read(buffer, offset, size - offset);
+                if (count == -1)
+                    break;
+                offset += count;
+            }
+            return entry.size;
+        }
+
+        int csize = entry.compressedSize;
+        byte[] cbuf = new byte[csize];
+        zipRandomFile.skipBytes(get2ByteLittleEndian(header, 26) + get2ByteLittleEndian(header, 28));
+        zipRandomFile.readFully(cbuf, 0, csize);
+
+        int count = inflate(cbuf, buffer);
+        if (count == -1)
+            throw new ZipException("corrupted zip file");
+
+        return entry.size;
+    }
+
+    //----------------------------------------------------------------------------
+    // Zip utilities
+    //----------------------------------------------------------------------------
+
+    private byte[] getHeader(ZipFileIndexEntry entry) throws IOException {
+        zipRandomFile.seek(entry.offset);
+        byte[] header = new byte[30];
+        zipRandomFile.readFully(header);
+        if (get4ByteLittleEndian(header, 0) != 0x04034b50)
+            throw new ZipException("corrupted zip file");
+        if ((get2ByteLittleEndian(header, 6) & 1) != 0)
+            throw new ZipException("encrypted zip file"); // offset 6 in the header of the ZipFileEntry
+        return header;
+    }
+
+  /*
+   * Inflate using the java.util.zip.Inflater class
+   */
+    private static Inflater inflater;
+    private int inflate(byte[] src, byte[] dest) {
+
+        // construct the inflater object or reuse an existing one
+        if (inflater == null)
+            inflater = new Inflater(true);
+
+        synchronized (inflater) {
+            inflater.reset();
+            inflater.setInput(src);
+            try {
+                return inflater.inflate(dest);
+            } catch (DataFormatException ex) {
+                return -1;
+            }
+        }
+    }
+
+    /**
+     * return the two bytes buf[pos], buf[pos+1] as an unsigned integer in little
+     * endian format.
+     */
+    private static int get2ByteLittleEndian(byte[] buf, int pos) {
+        return (buf[pos] & 0xFF) + ((buf[pos+1] & 0xFF) << 8);
+    }
+
+    /**
+     * return the 4 bytes buf[i..i+3] as an integer in little endian format.
+     */
+    private static int get4ByteLittleEndian(byte[] buf, int pos) {
+        return (buf[pos] & 0xFF) + ((buf[pos + 1] & 0xFF) << 8) +
+                ((buf[pos + 2] & 0xFF) << 16) + ((buf[pos + 3] & 0xFF) << 24);
+    }
+
+    /* ----------------------------------------------------------------------------
+     * ZipDirectory
+     * ----------------------------------------------------------------------------*/
+
+    private class ZipDirectory {
+        private String lastDir;
+        private int lastStart;
+        private int lastLen;
+
+        byte[] zipDir;
+        RandomAccessFile zipRandomFile = null;
+        ZipFileIndex zipFileIndex = null;
+
+        public ZipDirectory(RandomAccessFile zipRandomFile, long start, long end, ZipFileIndex index) throws IOException {
+            this.zipRandomFile = zipRandomFile;
+            this.zipFileIndex = index;
+
+            findCENRecord(start, end);
+        }
+
+        /*
+         * Reads zip file central directory.
+         * For more details see readCEN in zip_util.c from the JDK sources.
+         * This is a Java port of that function.
+         */
+        private void findCENRecord(long start, long end) throws IOException {
+            long totalLength = end - start;
+            int endbuflen = 1024;
+            byte[] endbuf = new byte[endbuflen];
+            long endbufend = end - start;
+
+            // There is a variable-length field after the dir offset record. We need to do consequential search.
+            while (endbufend >= 22) {
+                if (endbufend < endbuflen)
+                    endbuflen = (int)endbufend;
+                long endbufpos = endbufend - endbuflen;
+                zipRandomFile.seek(start + endbufpos);
+                zipRandomFile.readFully(endbuf, 0, endbuflen);
+                int i = endbuflen - 22;
+                while (i >= 0 &&
+                        !(endbuf[i] == 0x50 &&
+                        endbuf[i + 1] == 0x4b &&
+                        endbuf[i + 2] == 0x05 &&
+                        endbuf[i + 3] == 0x06 &&
+                        endbufpos + i + 22 +
+                        get2ByteLittleEndian(endbuf, i + 20) == totalLength)) {
+                    i--;
+                }
+
+                if (i >= 0) {
+                    zipDir = new byte[get4ByteLittleEndian(endbuf, i + 12) + 2];
+                    zipDir[0] = endbuf[i + 10];
+                    zipDir[1] = endbuf[i + 11];
+                    zipRandomFile.seek(start + get4ByteLittleEndian(endbuf, i + 16));
+                    zipRandomFile.readFully(zipDir, 2, zipDir.length - 2);
+                    return;
+                } else {
+                    endbufend = endbufpos + 21;
+                }
+            }
+            throw new ZipException("cannot read zip file");
+        }
+        private void buildIndex() throws IOException {
+            int entryCount = get2ByteLittleEndian(zipDir, 0);
+
+            entries = new ZipFileIndexEntry[entryCount];
+            // Add each of the files
+            if (entryCount > 0) {
+                directories = new HashMap<String, DirectoryEntry>();
+                ArrayList<ZipFileIndexEntry> entryList = new ArrayList<ZipFileIndexEntry>();
+                int pos = 2;
+                for (int i = 0; i < entryCount; i++) {
+                    pos = readEntry(pos, entryList, directories);
+                }
+
+                // Add the accumulated dirs into the same list
+                Iterator i = directories.keySet().iterator();
+                while (i.hasNext()) {
+                    ZipFileIndexEntry zipFileIndexEntry = new ZipFileIndexEntry( (String) i.next());
+                    zipFileIndexEntry.isDir = true;
+                    entryList.add(zipFileIndexEntry);
+                }
+
+                entries = entryList.toArray(new ZipFileIndexEntry[entryList.size()]);
+                Arrays.sort(entries);
+            } else {
+                cleanupState();
+            }
+        }
+
+        private int readEntry(int pos, List<ZipFileIndexEntry> entryList,
+                Map<String, DirectoryEntry> directories) throws IOException {
+            if (get4ByteLittleEndian(zipDir, pos) != 0x02014b50) {
+                throw new ZipException("cannot read zip file entry");
+            }
+
+            int dirStart = pos + 46;
+            int fileStart = dirStart;
+            int fileEnd = fileStart + get2ByteLittleEndian(zipDir, pos + 28);
+
+            if (zipFileIndex.symbolFilePrefixLength != 0 &&
+                    ((fileEnd - fileStart) >= symbolFilePrefixLength)) {
+                dirStart += zipFileIndex.symbolFilePrefixLength;
+               fileStart += zipFileIndex.symbolFilePrefixLength;
+            }
+
+            // Use the OS's path separator. Keep the position of the last one.
+            for (int index = fileStart; index < fileEnd; index++) {
+                byte nextByte = zipDir[index];
+                if (nextByte == (byte)'\\' || nextByte == (byte)'/') {
+                    zipDir[index] = (byte)File.separatorChar;
+                    fileStart = index + 1;
+                }
+            }
+
+            String directory = null;
+            if (fileStart == dirStart)
+                directory = "";
+            else if (lastDir != null && lastLen == fileStart - dirStart - 1) {
+                int index = lastLen - 1;
+                while (zipDir[lastStart + index] == zipDir[dirStart + index]) {
+                    if (index == 0) {
+                        directory = lastDir;
+                        break;
+                    }
+                    index--;
+                }
+            }
+
+            // Sub directories
+            if (directory == null) {
+                lastStart = dirStart;
+                lastLen = fileStart - dirStart - 1;
+
+                directory = new String(zipDir, dirStart, lastLen, "UTF-8").intern();
+                lastDir = directory;
+
+                // Enter also all the parent directories
+                String tempDirectory = directory;
+
+                while (directories.get(tempDirectory) == null) {
+                    directories.put(tempDirectory, new DirectoryEntry(tempDirectory, zipFileIndex));
+                    int separator = tempDirectory.lastIndexOf(File.separatorChar);
+                    if (separator == -1)
+                        break;
+                    tempDirectory = tempDirectory.substring(0, separator);
+                }
+            }
+            else {
+                directory = directory.intern();
+                if (directories.get(directory) == null) {
+                    directories.put(directory, new DirectoryEntry(directory, zipFileIndex));
+                }
+            }
+
+            // For each dir create also a file
+            if (fileStart != fileEnd) {
+                ZipFileIndexEntry entry = new ZipFileIndexEntry(directory,
+                        new String(zipDir, fileStart, fileEnd - fileStart, "UTF-8"));
+
+                entry.setNativeTime(get4ByteLittleEndian(zipDir, pos + 12));
+                entry.compressedSize = get4ByteLittleEndian(zipDir, pos + 20);
+                entry.size = get4ByteLittleEndian(zipDir, pos + 24);
+                entry.offset = get4ByteLittleEndian(zipDir, pos + 42);
+                entryList.add(entry);
+            }
+
+            return pos + 46 +
+                    get2ByteLittleEndian(zipDir, pos + 28) +
+                    get2ByteLittleEndian(zipDir, pos + 30) +
+                    get2ByteLittleEndian(zipDir, pos + 32);
+        }
+    }
+
+    /**
+     * Returns the last modified timestamp of a zip file.
+     * @return long
+     */
+    public long getZipFileLastModified() throws IOException {
+        lock.lock();
+        try {
+            checkIndex();
+            return zipFileLastModified;
+        }
+        finally {
+            lock.unlock();
+        }
+    }
+
+    /** ------------------------------------------------------------------------
+     *  DirectoryEntry class
+     * -------------------------------------------------------------------------*/
+    static class DirectoryEntry {
+        private boolean filesInited;
+        private boolean directoriesInited;
+        private boolean zipFileEntriesInited;
+        private boolean entriesInited;
+
+        private long writtenOffsetOffset = 0;
+
+        private String dirName;
+
+        private com.sun.tools.javac.util.List<String> zipFileEntriesFiles = com.sun.tools.javac.util.List.<String>nil();
+        private com.sun.tools.javac.util.List<String> zipFileEntriesDirectories = com.sun.tools.javac.util.List.<String>nil();
+        private com.sun.tools.javac.util.List<ZipFileIndexEntry>  zipFileEntries = com.sun.tools.javac.util.List.<ZipFileIndexEntry>nil();
+
+        private List<ZipFileIndexEntry> entries = new ArrayList<ZipFileIndexEntry>();
+
+        private ZipFileIndex zipFileIndex;
+
+        private int numEntries;
+
+        DirectoryEntry(String dirName, ZipFileIndex index) {
+        filesInited = false;
+            directoriesInited = false;
+            entriesInited = false;
+
+            if (File.separatorChar == '/') {
+                dirName.replace('\\', '/');
+            }
+            else {
+                dirName.replace('/', '\\');
+            }
+
+            this.dirName = dirName.intern();
+            this.zipFileIndex = index;
+        }
+
+        private com.sun.tools.javac.util.List<String> getFiles() {
+            if (filesInited) {
+                return zipFileEntriesFiles;
+            }
+
+            initEntries();
+
+            for (ZipFileIndexEntry e : entries) {
+                if (!e.isDir) {
+                    zipFileEntriesFiles = zipFileEntriesFiles.append(e.name);
+                }
+            }
+            filesInited = true;
+            return zipFileEntriesFiles;
+        }
+
+        private com.sun.tools.javac.util.List<String> getDirectories() {
+            if (directoriesInited) {
+                return zipFileEntriesFiles;
+            }
+
+            initEntries();
+
+            for (ZipFileIndexEntry e : entries) {
+                if (e.isDir) {
+                    zipFileEntriesDirectories = zipFileEntriesDirectories.append(e.name);
+                }
+            }
+
+            directoriesInited = true;
+
+            return zipFileEntriesDirectories;
+        }
+
+        private com.sun.tools.javac.util.List<ZipFileIndexEntry> getEntries() {
+            if (zipFileEntriesInited) {
+                return zipFileEntries;
+            }
+
+            initEntries();
+
+            zipFileEntries = com.sun.tools.javac.util.List.nil();
+            for (ZipFileIndexEntry zfie : entries) {
+                zipFileEntries = zipFileEntries.append(zfie);
+            }
+
+            zipFileEntriesInited = true;
+
+            return zipFileEntries;
+        }
+
+        private ZipFileIndexEntry getEntry(String rootName) {
+            initEntries();
+            int index = Collections.binarySearch(entries, new ZipFileIndexEntry(dirName, rootName));
+            if (index < 0) {
+                return null;
+            }
+
+            return entries.get(index);
+        }
+
+        private void initEntries() {
+            if (entriesInited) {
+                return;
+            }
+
+            if (!zipFileIndex.readFromIndex) {
+                int from = -Arrays.binarySearch(zipFileIndex.entries,
+                        new ZipFileIndexEntry(dirName, ZipFileIndex.MIN_CHAR)) - 1;
+                int to = -Arrays.binarySearch(zipFileIndex.entries,
+                        new ZipFileIndexEntry(dirName, MAX_CHAR)) - 1;
+
+                boolean emptyList = false;
+
+                for (int i = from; i < to; i++) {
+                    entries.add(zipFileIndex.entries[i]);
+                }
+            } else {
+                File indexFile = zipFileIndex.getIndexFile();
+                if (indexFile != null) {
+                    RandomAccessFile raf = null;
+                    try {
+                        raf = new RandomAccessFile(indexFile, "r");
+                        raf.seek(writtenOffsetOffset);
+
+                        for (int nFiles = 0; nFiles < numEntries; nFiles++) {
+                            // Read the name bytes
+                            int zfieNameBytesLen = raf.readInt();
+                            byte [] zfieNameBytes = new byte[zfieNameBytesLen];
+                            raf.read(zfieNameBytes);
+                            String eName = new String(zfieNameBytes, "UTF-8");
+
+                            // Read isDir
+                            boolean eIsDir = raf.readByte() == (byte)0 ? false : true;
+
+                            // Read offset of bytes in the real Jar/Zip file
+                            int eOffset = raf.readInt();
+
+                            // Read size of the file in the real Jar/Zip file
+                            int eSize = raf.readInt();
+
+                            // Read compressed size of the file in the real Jar/Zip file
+                            int eCsize = raf.readInt();
+
+                            // Read java time stamp of the file in the real Jar/Zip file
+                            long eJavaTimestamp = raf.readLong();
+
+                            ZipFileIndexEntry rfie = new ZipFileIndexEntry(dirName, eName);
+                            rfie.isDir = eIsDir;
+                            rfie.offset = eOffset;
+                            rfie.size = eSize;
+                            rfie.compressedSize = eCsize;
+                            rfie.javatime = eJavaTimestamp;
+                            entries.add(rfie);
+                        }
+                    } catch (Throwable t) {
+                        // Do nothing
+                    } finally {
+                        try {
+                            if (raf == null) {
+                                raf.close();
+                            }
+                        } catch (Throwable t) {
+                            // Do nothing
+                        }
+                    }
+                }
+            }
+
+            entriesInited = true;
+        }
+
+        List<ZipFileIndexEntry> getEntriesAsCollection() {
+            initEntries();
+
+            return entries;
+        }
+    }
+
+    private boolean readIndex() {
+        if (triedToReadIndex || !usePreindexedCache) {
+            return false;
+        }
+
+        boolean ret = false;
+        lock.lock();
+        try {
+            triedToReadIndex = true;
+            RandomAccessFile raf = null;
+            try {
+                File indexFileName = getIndexFile();
+                raf = new RandomAccessFile(indexFileName, "r");
+
+                long fileStamp = raf.readLong();
+                if (zipFile.lastModified() != fileStamp) {
+                    ret = false;
+                } else {
+                    directories = new HashMap<String, DirectoryEntry>();
+                    int numDirs = raf.readInt();
+                    for (int nDirs = 0; nDirs < numDirs; nDirs++) {
+                        int dirNameBytesLen = raf.readInt();
+                        byte [] dirNameBytes = new byte[dirNameBytesLen];
+                        raf.read(dirNameBytes);
+
+                        String dirNameStr = new String(dirNameBytes, "UTF-8");
+                        DirectoryEntry de = new DirectoryEntry(dirNameStr, this);
+                        de.numEntries = raf.readInt();
+                        de.writtenOffsetOffset = raf.readLong();
+                        directories.put(dirNameStr, de);
+                    }
+                    ret = true;
+                    zipFileLastModified = fileStamp;
+                }
+            } catch (Throwable t) {
+                // Do nothing
+            } finally {
+                if (raf != null) {
+                    try {
+                        raf.close();
+                    } catch (Throwable tt) {
+                        // Do nothing
+                    }
+                }
+            }
+            if (ret == true) {
+                readFromIndex = true;
+            }
+        }
+        finally {
+            lock.unlock();
+        }
+
+        return ret;
+    }
+
+    private boolean writeIndex() {
+        boolean ret = false;
+        if (readFromIndex || !usePreindexedCache) {
+            return true;
+        }
+
+        if (!writeIndex) {
+            return true;
+        }
+
+        File indexFile = getIndexFile();
+        if (indexFile == null) {
+            return false;
+        }
+
+        RandomAccessFile raf = null;
+        long writtenSoFar = 0;
+        try {
+            raf = new RandomAccessFile(indexFile, "rw");
+
+            raf.writeLong(zipFileLastModified);
+            writtenSoFar += 8;
+
+
+            Iterator<String> iterDirName = directories.keySet().iterator();
+            List<DirectoryEntry> directoriesToWrite = new ArrayList<DirectoryEntry>();
+            Map<String, Long> offsets = new HashMap<String, Long>();
+            raf.writeInt(directories.keySet().size());
+            writtenSoFar += 4;
+
+            while(iterDirName.hasNext()) {
+                String dirName = iterDirName.next();
+                DirectoryEntry dirEntry = directories.get(dirName);
+
+                directoriesToWrite.add(dirEntry);
+
+                // Write the dir name bytes
+                byte [] dirNameBytes = dirName.getBytes("UTF-8");
+                int dirNameBytesLen = dirNameBytes.length;
+                raf.writeInt(dirNameBytesLen);
+                writtenSoFar += 4;
+
+                raf.write(dirNameBytes);
+                writtenSoFar += dirNameBytesLen;
+
+                // Write the number of files in the dir
+                List dirEntries = dirEntry.getEntriesAsCollection();
+                raf.writeInt(dirEntries.size());
+                writtenSoFar += 4;
+
+                offsets.put(dirName, new Long(writtenSoFar));
+
+                // Write the offset of the file's data in the dir
+                dirEntry.writtenOffsetOffset = 0L;
+                raf.writeLong(0L);
+                writtenSoFar += 8;
+            }
+
+            for (DirectoryEntry de : directoriesToWrite) {
+                // Fix up the offset in the directory table
+                long currFP = raf.getFilePointer();
+
+                long offsetOffset = offsets.get(de.dirName).longValue();
+                raf.seek(offsetOffset);
+                raf.writeLong(writtenSoFar);
+
+                raf.seek(currFP);
+
+                // Now write each of the files in the DirectoryEntry
+                List<ZipFileIndexEntry> entries = de.getEntriesAsCollection();
+                for (ZipFileIndexEntry zfie : entries) {
+                    // Write the name bytes
+                    byte [] zfieNameBytes = zfie.name.getBytes("UTF-8");
+                    int zfieNameBytesLen = zfieNameBytes.length;
+                    raf.writeInt(zfieNameBytesLen);
+                    writtenSoFar += 4;
+                    raf.write(zfieNameBytes);
+                    writtenSoFar += zfieNameBytesLen;
+
+                    // Write isDir
+                    raf.writeByte(zfie.isDir ? (byte)1 : (byte)0);
+                    writtenSoFar += 1;
+
+                    // Write offset of bytes in the real Jar/Zip file
+                    raf.writeInt(zfie.offset);
+                    writtenSoFar += 4;
+
+                    // Write size of the file in the real Jar/Zip file
+                    raf.writeInt(zfie.size);
+                    writtenSoFar += 4;
+
+                    // Write compressed size of the file in the real Jar/Zip file
+                    raf.writeInt(zfie.compressedSize);
+                    writtenSoFar += 4;
+
+                    // Write java time stamp of the file in the real Jar/Zip file
+                    raf.writeLong(zfie.getLastModified());
+                    writtenSoFar += 8;
+                }
+            }
+        } catch (Throwable t) {
+            // Do nothing
+        } finally {
+            try {
+                if (raf != null) {
+                    raf.close();
+                }
+            } catch(IOException ioe) {
+                // Do nothing
+            }
+        }
+
+        return ret;
+    }
+
+    public boolean writeZipIndex() {
+        lock.lock();
+        try {
+            return writeIndex();
+        }
+        finally {
+            lock.unlock();
+        }
+    }
+
+    private File getIndexFile() {
+        if (zipIndexFile == null) {
+            if (zipFile == null) {
+                return null;
+            }
+
+            zipIndexFile = new File((preindexedCacheLocation == null ? "" : preindexedCacheLocation) +
+                    zipFile.getName() + ".index");
+        }
+
+        return zipIndexFile;
+    }
+
+    public File getZipFile() {
+        return zipFile;
+    }
+}