langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/ZipFileIndex.java
changeset 25874 83c19f00452c
parent 22163 3651128c74eb
child 27225 8369cde9152a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/ZipFileIndex.java	Sun Aug 17 15:52:32 2014 +0100
@@ -0,0 +1,1168 @@
+/*
+ * Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.tools.javac.file;
+
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.lang.ref.Reference;
+import java.lang.ref.SoftReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+import java.util.zip.ZipException;
+
+import com.sun.tools.javac.file.RelativePath.RelativeDirectory;
+import com.sun.tools.javac.file.RelativePath.RelativeFile;
+
+/**
+ * This class implements the building of index of a zip archive and access to
+ * its context. It also uses a prebuilt index if available.
+ * It supports invocations where it will serialize an optimized zip index file
+ * to disk.
+ *
+ * In order to use a secondary index file, set "usezipindex" 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 "{@code -XDcachezipindexdir=<directory>}". If this flag is not
+ * provided, the default location is the value of the "java.io.tmpdir" system
+ * property.
+ *
+ * If "-XDwritezipindexfiles" is specified, there will be new optimized index
+ * file created for each archive, used by the compiler for compilation, at the
+ * location specified by the "cachezipindexdir" option.
+ *
+ * If system property nonBatchMode option is specified 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.
+ *
+ * <p><b>This is NOT part of any supported API.
+ * If you write code that depends on this, you do so at your own risk.
+ * This code and its internal interfaces are subject to change or
+ * deletion without notice.</b>
+ */
+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 final boolean NON_BATCH_MODE = System.getProperty("nonBatchMode") != null;// TODO: Use -XD compiler switch for this.
+
+    private Map<RelativeDirectory, DirectoryEntry> directories =
+            Collections.<RelativeDirectory, DirectoryEntry>emptyMap();
+    private Set<RelativeDirectory> allDirs =
+            Collections.<RelativeDirectory>emptySet();
+
+    // ZipFileIndex data entries
+    final File zipFile;
+    private Reference<File> absFileRef;
+    long zipFileLastModified = NOT_MODIFIED;
+    private RandomAccessFile zipRandomFile;
+    private Entry[] entries;
+
+    private boolean readFromIndex = false;
+    private File zipIndexFile = null;
+    private boolean triedToReadIndex = false;
+    final RelativeDirectory symbolFilePrefix;
+    private final int symbolFilePrefixLength;
+    private boolean hasPopulatedData = false;
+    long lastReferenceTimeStamp = NOT_MODIFIED;
+
+    private final boolean usePreindexedCache;
+    private final String preindexedCacheLocation;
+
+    private boolean writeIndex = false;
+
+    private Map<String, SoftReference<RelativeDirectory>> relativeDirectoryCache = new HashMap<>();
+
+
+    public synchronized boolean isOpen() {
+        return (zipRandomFile != null);
+    }
+
+    ZipFileIndex(File zipFile, RelativeDirectory symbolFilePrefix, boolean writeIndex,
+            boolean useCache, String cacheLocation) throws IOException {
+        this.zipFile = zipFile;
+        this.symbolFilePrefix = symbolFilePrefix;
+        this.symbolFilePrefixLength = (symbolFilePrefix == null ? 0 :
+            symbolFilePrefix.getPath().getBytes("UTF-8").length);
+        this.writeIndex = writeIndex;
+        this.usePreindexedCache = useCache;
+        this.preindexedCacheLocation = cacheLocation;
+
+        if (zipFile != null) {
+            this.zipFileLastModified = zipFile.lastModified();
+        }
+
+        // Validate integrity of the zip file
+        checkIndex();
+    }
+
+    @Override
+    public String toString() {
+        return "ZipFileIndex[" + zipFile + "]";
+    }
+
+    // Just in case...
+    @Override
+    protected void finalize() throws Throwable {
+        closeFile();
+        super.finalize();
+    }
+
+    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.<RelativeDirectory, DirectoryEntry>emptyMap();
+        allDirs = Collections.<RelativeDirectory>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 = Entry.EMPTY_ARRAY;
+        directories = Collections.<RelativeDirectory, DirectoryEntry>emptyMap();
+        zipFileLastModified = NOT_MODIFIED;
+        allDirs = Collections.<RelativeDirectory>emptySet();
+    }
+
+    public synchronized void close() {
+        writeIndex();
+        closeFile();
+    }
+
+    private void closeFile() {
+        if (zipRandomFile != null) {
+            try {
+                zipRandomFile.close();
+            } catch (IOException ex) {
+            }
+            zipRandomFile = null;
+        }
+    }
+
+    /**
+     * Returns the ZipFileIndexEntry for a path, if there is one.
+     */
+    synchronized Entry getZipIndexEntry(RelativePath path) {
+        try {
+            checkIndex();
+            DirectoryEntry de = directories.get(path.dirname());
+            String lookFor = path.basename();
+            return (de == null) ? null : de.getEntry(lookFor);
+        }
+        catch (IOException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Returns a javac List of filenames within a directory in the ZipFileIndex.
+     */
+    public synchronized com.sun.tools.javac.util.List<String> getFiles(RelativeDirectory path) {
+        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();
+        }
+    }
+
+    public synchronized List<String> getDirectories(RelativeDirectory path) {
+        try {
+            checkIndex();
+
+            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();
+        }
+    }
+
+    public synchronized Set<RelativeDirectory> getAllDirectories() {
+        try {
+            checkIndex();
+            if (allDirs == Collections.EMPTY_SET) {
+                allDirs = new java.util.LinkedHashSet<>(directories.keySet());
+            }
+
+            return allDirs;
+        }
+        catch (IOException e) {
+            return Collections.<RelativeDirectory>emptySet();
+        }
+    }
+
+    /**
+     * 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 synchronized boolean contains(RelativePath path) {
+        try {
+            checkIndex();
+            return getZipIndexEntry(path) != null;
+        }
+        catch (IOException e) {
+            return false;
+        }
+    }
+
+    public synchronized boolean isDirectory(RelativePath path) throws IOException {
+        // The top level in a zip file is always a directory.
+        if (path.getPath().length() == 0) {
+            lastReferenceTimeStamp = System.currentTimeMillis();
+            return true;
+        }
+
+        checkIndex();
+        return directories.get(path) != null;
+    }
+
+    public synchronized long getLastModified(RelativeFile path) throws IOException {
+        Entry entry = getZipIndexEntry(path);
+        if (entry == null)
+            throw new FileNotFoundException();
+        return entry.getLastModified();
+    }
+
+    public synchronized int length(RelativeFile path) throws IOException {
+        Entry 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;
+        }
+    }
+
+    public synchronized byte[] read(RelativeFile path) throws IOException {
+        Entry entry = getZipIndexEntry(path);
+        if (entry == null)
+            throw new FileNotFoundException("Path not found in ZIP: " + path.path);
+        return read(entry);
+    }
+
+    synchronized byte[] read(Entry entry) throws IOException {
+        openFile();
+        byte[] result = readBytes(entry);
+        closeFile();
+        return result;
+    }
+
+    public synchronized int read(RelativeFile path, byte[] buffer) throws IOException {
+        Entry entry = getZipIndexEntry(path);
+        if (entry == null)
+            throw new FileNotFoundException();
+        return read(entry, buffer);
+    }
+
+    synchronized int read(Entry entry, byte[] buffer)
+            throws IOException {
+        int result = readBytes(entry, buffer);
+        return  result;
+    }
+
+    private byte[] readBytes(Entry 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(Entry 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(Entry 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 SoftReference<Inflater> inflaterRef;
+    private int inflate(byte[] src, byte[] dest) {
+        Inflater inflater = (inflaterRef == null ? null : inflaterRef.get());
+
+        // construct the inflater object or reuse an existing one
+        if (inflater == null)
+            inflaterRef = new SoftReference<>(inflater = new Inflater(true));
+
+        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 RelativeDirectory 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;
+            hasValidHeader();
+            findCENRecord(start, end);
+        }
+
+        /*
+         * the zip entry signature should be at offset 0, otherwise allow the
+         * calling logic to take evasive action by throwing ZipFormatException.
+         */
+        private boolean hasValidHeader() throws IOException {
+            final long pos = zipRandomFile.getFilePointer();
+            try {
+                if (zipRandomFile.read() == 'P') {
+                    if (zipRandomFile.read() == 'K') {
+                        if (zipRandomFile.read() == 0x03) {
+                            if (zipRandomFile.read() == 0x04) {
+                                return true;
+                            }
+                        }
+                    }
+                }
+            } finally {
+                zipRandomFile.seek(pos);
+            }
+            throw new ZipFormatException("invalid zip magic");
+        }
+
+        /*
+         * 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)];
+                    int sz = get4ByteLittleEndian(endbuf, i + 16);
+                    // a negative offset or the entries field indicates a
+                    // potential zip64 archive
+                    if (sz < 0 || get2ByteLittleEndian(endbuf, i + 10) == 0xffff) {
+                        throw new ZipFormatException("detected a zip64 archive");
+                    }
+                    zipRandomFile.seek(start + sz);
+                    zipRandomFile.readFully(zipDir, 0, zipDir.length);
+                    return;
+                } else {
+                    endbufend = endbufpos + 21;
+                }
+            }
+            throw new ZipException("cannot read zip file");
+        }
+
+        private void buildIndex() throws IOException {
+            int len = zipDir.length;
+
+            // Add each of the files
+            if (len > 0) {
+                directories = new LinkedHashMap<>();
+                ArrayList<Entry> entryList = new ArrayList<>();
+                for (int pos = 0; pos < len; ) {
+                    pos = readEntry(pos, entryList, directories);
+                }
+
+                // Add the accumulated dirs into the same list
+                for (RelativeDirectory d: directories.keySet()) {
+                    // use shared RelativeDirectory objects for parent dirs
+                    RelativeDirectory parent = getRelativeDirectory(d.dirname().getPath());
+                    String file = d.basename();
+                    Entry zipFileIndexEntry = new Entry(parent, file);
+                    zipFileIndexEntry.isDir = true;
+                    entryList.add(zipFileIndexEntry);
+                }
+
+                entries = entryList.toArray(new Entry[entryList.size()]);
+                Arrays.sort(entries);
+            } else {
+                cleanupState();
+            }
+        }
+
+        private int readEntry(int pos, List<Entry> entryList,
+                Map<RelativeDirectory, 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;
+            }
+            // Force any '\' to '/'. Keep the position of the last separator.
+            for (int index = fileStart; index < fileEnd; index++) {
+                byte nextByte = zipDir[index];
+                if (nextByte == (byte)'\\') {
+                    zipDir[index] = (byte)'/';
+                    fileStart = index + 1;
+                } else if (nextByte == (byte)'/') {
+                    fileStart = index + 1;
+                }
+            }
+
+            RelativeDirectory directory = null;
+            if (fileStart == dirStart)
+                directory = getRelativeDirectory("");
+            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 = getRelativeDirectory(new String(zipDir, dirStart, lastLen, "UTF-8"));
+                lastDir = directory;
+
+                // Enter also all the parent directories
+                RelativeDirectory tempDirectory = directory;
+
+                while (directories.get(tempDirectory) == null) {
+                    directories.put(tempDirectory, new DirectoryEntry(tempDirectory, zipFileIndex));
+                    if (tempDirectory.path.indexOf("/") == tempDirectory.path.length() - 1)
+                        break;
+                    else {
+                        // use shared RelativeDirectory objects for parent dirs
+                        tempDirectory = getRelativeDirectory(tempDirectory.dirname().getPath());
+                    }
+                }
+            }
+            else {
+                if (directories.get(directory) == null) {
+                    directories.put(directory, new DirectoryEntry(directory, zipFileIndex));
+                }
+            }
+
+            // For each dir create also a file
+            if (fileStart != fileEnd) {
+                Entry entry = new Entry(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 {
+        synchronized (this) {
+            checkIndex();
+            return zipFileLastModified;
+        }
+    }
+
+    /** ------------------------------------------------------------------------
+     *  DirectoryEntry class
+     * -------------------------------------------------------------------------*/
+
+    static class DirectoryEntry {
+        private boolean filesInited;
+        private boolean directoriesInited;
+        private boolean zipFileEntriesInited;
+        private boolean entriesInited;
+
+        private long writtenOffsetOffset = 0;
+
+        private RelativeDirectory 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<Entry>  zipFileEntries = com.sun.tools.javac.util.List.<Entry>nil();
+
+        private List<Entry> entries = new ArrayList<>();
+
+        private ZipFileIndex zipFileIndex;
+
+        private int numEntries;
+
+        DirectoryEntry(RelativeDirectory dirName, ZipFileIndex index) {
+            filesInited = false;
+            directoriesInited = false;
+            entriesInited = false;
+
+            this.dirName = dirName;
+            this.zipFileIndex = index;
+        }
+
+        private com.sun.tools.javac.util.List<String> getFiles() {
+            if (!filesInited) {
+                initEntries();
+                for (Entry 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) {
+                initEntries();
+                for (Entry e : entries) {
+                    if (e.isDir) {
+                        zipFileEntriesDirectories = zipFileEntriesDirectories.append(e.name);
+                    }
+                }
+                directoriesInited = true;
+            }
+            return zipFileEntriesDirectories;
+        }
+
+        private com.sun.tools.javac.util.List<Entry> getEntries() {
+            if (!zipFileEntriesInited) {
+                initEntries();
+                zipFileEntries = com.sun.tools.javac.util.List.nil();
+                for (Entry zfie : entries) {
+                    zipFileEntries = zipFileEntries.append(zfie);
+                }
+                zipFileEntriesInited = true;
+            }
+            return zipFileEntries;
+        }
+
+        private Entry getEntry(String rootName) {
+            initEntries();
+            int index = Collections.binarySearch(entries, new Entry(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 Entry(dirName, ZipFileIndex.MIN_CHAR)) - 1;
+                int to = -Arrays.binarySearch(zipFileIndex.entries,
+                        new Entry(dirName, MAX_CHAR)) - 1;
+
+                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();
+
+                            Entry rfie = new Entry(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<Entry> getEntriesAsCollection() {
+            initEntries();
+
+            return entries;
+        }
+    }
+
+    private boolean readIndex() {
+        if (triedToReadIndex || !usePreindexedCache) {
+            return false;
+        }
+
+        boolean ret = false;
+        synchronized (this) {
+            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 LinkedHashMap<>();
+                    int numDirs = raf.readInt();
+                    for (int nDirs = 0; nDirs < numDirs; nDirs++) {
+                        int dirNameBytesLen = raf.readInt();
+                        byte [] dirNameBytes = new byte[dirNameBytesLen];
+                        raf.read(dirNameBytes);
+
+                        RelativeDirectory dirNameStr = getRelativeDirectory(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;
+            }
+        }
+
+        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;
+
+            List<DirectoryEntry> directoriesToWrite = new ArrayList<>();
+            Map<RelativeDirectory, Long> offsets = new HashMap<>();
+            raf.writeInt(directories.keySet().size());
+            writtenSoFar += 4;
+
+            for (RelativeDirectory dirName: directories.keySet()) {
+                DirectoryEntry dirEntry = directories.get(dirName);
+
+                directoriesToWrite.add(dirEntry);
+
+                // Write the dir name bytes
+                byte [] dirNameBytes = dirName.getPath().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<Entry> 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<Entry> list = de.getEntriesAsCollection();
+                for (Entry zfie : list) {
+                    // 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() {
+        synchronized (this) {
+            return writeIndex();
+        }
+    }
+
+    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;
+    }
+
+    File getAbsoluteFile() {
+        File absFile = (absFileRef == null ? null : absFileRef.get());
+        if (absFile == null) {
+            absFile = zipFile.getAbsoluteFile();
+            absFileRef = new SoftReference<>(absFile);
+        }
+        return absFile;
+    }
+
+    private RelativeDirectory getRelativeDirectory(String path) {
+        RelativeDirectory rd;
+        SoftReference<RelativeDirectory> ref = relativeDirectoryCache.get(path);
+        if (ref != null) {
+            rd = ref.get();
+            if (rd != null)
+                return rd;
+        }
+        rd = new RelativeDirectory(path);
+        relativeDirectoryCache.put(path, new SoftReference<>(rd));
+        return rd;
+    }
+
+    static class Entry implements Comparable<Entry> {
+        public static final Entry[] EMPTY_ARRAY = {};
+
+        // Directory related
+        RelativeDirectory dir;
+        boolean isDir;
+
+        // File related
+        String name;
+
+        int offset;
+        int size;
+        int compressedSize;
+        long javatime;
+
+        private int nativetime;
+
+        public Entry(RelativePath path) {
+            this(path.dirname(), path.basename());
+        }
+
+        public Entry(RelativeDirectory directory, String name) {
+            this.dir = directory;
+            this.name = name;
+        }
+
+        public String getName() {
+            return new RelativeFile(dir, name).getPath();
+        }
+
+        public String getFileName() {
+            return name;
+        }
+
+        public long getLastModified() {
+            if (javatime == 0) {
+                    javatime = dosToJavaTime(nativetime);
+            }
+            return javatime;
+        }
+
+        // based on dosToJavaTime in java.util.Zip, but avoiding the
+        // use of deprecated Date constructor
+        private static long dosToJavaTime(int dtime) {
+            Calendar c = Calendar.getInstance();
+            c.set(Calendar.YEAR,        ((dtime >> 25) & 0x7f) + 1980);
+            c.set(Calendar.MONTH,       ((dtime >> 21) & 0x0f) - 1);
+            c.set(Calendar.DATE,        ((dtime >> 16) & 0x1f));
+            c.set(Calendar.HOUR_OF_DAY, ((dtime >> 11) & 0x1f));
+            c.set(Calendar.MINUTE,      ((dtime >>  5) & 0x3f));
+            c.set(Calendar.SECOND,      ((dtime <<  1) & 0x3e));
+            c.set(Calendar.MILLISECOND, 0);
+            return c.getTimeInMillis();
+        }
+
+        void setNativeTime(int natTime) {
+            nativetime = natTime;
+        }
+
+        public boolean isDirectory() {
+            return isDir;
+        }
+
+        public int compareTo(Entry other) {
+            RelativeDirectory otherD = other.dir;
+            if (dir != otherD) {
+                int c = dir.compareTo(otherD);
+                if (c != 0)
+                    return c;
+            }
+            return name.compareTo(other.name);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof Entry))
+                return false;
+            Entry other = (Entry) o;
+            return dir.equals(other.dir) && name.equals(other.name);
+        }
+
+        @Override
+        public int hashCode() {
+            int hash = 7;
+            hash = 97 * hash + (this.dir != null ? this.dir.hashCode() : 0);
+            hash = 97 * hash + (this.name != null ? this.name.hashCode() : 0);
+            return hash;
+        }
+
+        @Override
+        public String toString() {
+            return isDir ? ("Dir:" + dir + " : " + name) :
+                (dir + ":" + name);
+        }
+    }
+
+    /*
+     * Exception primarily used to implement a failover, used exclusively here.
+     */
+
+    static final class ZipFormatException extends IOException {
+        private static final long serialVersionUID = 8000196834066748623L;
+        protected ZipFormatException(String message) {
+            super(message);
+        }
+
+        protected ZipFormatException(String message, Throwable cause) {
+            super(message, cause);
+        }
+    }
+}