langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/ZipFileIndex.java
changeset 34569 8b372e28f106
parent 34559 9229cc3b3802
parent 34568 afc0330fa0d4
child 34570 8a8f52a733dd
equal deleted inserted replaced
34559:9229cc3b3802 34569:8b372e28f106
     1 /*
       
     2  * Copyright (c) 2007, 2014, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 
       
    26 package com.sun.tools.javac.file;
       
    27 
       
    28 
       
    29 import java.io.FileNotFoundException;
       
    30 import java.io.IOException;
       
    31 import java.io.RandomAccessFile;
       
    32 import java.lang.ref.Reference;
       
    33 import java.lang.ref.SoftReference;
       
    34 import java.nio.file.Files;
       
    35 import java.nio.file.Path;
       
    36 import java.nio.file.Paths;
       
    37 import java.util.ArrayList;
       
    38 import java.util.Arrays;
       
    39 import java.util.Calendar;
       
    40 import java.util.Collections;
       
    41 import java.util.HashMap;
       
    42 import java.util.LinkedHashMap;
       
    43 import java.util.List;
       
    44 import java.util.Map;
       
    45 import java.util.Set;
       
    46 import java.util.zip.DataFormatException;
       
    47 import java.util.zip.Inflater;
       
    48 import java.util.zip.ZipException;
       
    49 
       
    50 import com.sun.tools.javac.file.RelativePath.RelativeDirectory;
       
    51 import com.sun.tools.javac.file.RelativePath.RelativeFile;
       
    52 
       
    53 /**
       
    54  * This class implements the building of index of a zip archive and access to
       
    55  * its context. It also uses a prebuilt index if available.
       
    56  * It supports invocations where it will serialize an optimized zip index file
       
    57  * to disk.
       
    58  *
       
    59  * In order to use a secondary index file, set "usezipindex" in the Options
       
    60  * object when JavacFileManager is invoked. (You can pass "-XDusezipindex" on
       
    61  * the command line.)
       
    62  *
       
    63  * Location where to look for/generate optimized zip index files can be
       
    64  * provided using "{@code -XDcachezipindexdir=<directory>}". If this flag is not
       
    65  * provided, the default location is the value of the "java.io.tmpdir" system
       
    66  * property.
       
    67  *
       
    68  * If "-XDwritezipindexfiles" is specified, there will be new optimized index
       
    69  * file created for each archive, used by the compiler for compilation, at the
       
    70  * location specified by the "cachezipindexdir" option.
       
    71  *
       
    72  * If system property nonBatchMode option is specified the compiler will use
       
    73  * timestamp checking to reindex the zip files if it is needed. In batch mode
       
    74  * the timestamps are not checked and the compiler uses the cached indexes.
       
    75  *
       
    76  * <p><b>This is NOT part of any supported API.
       
    77  * If you write code that depends on this, you do so at your own risk.
       
    78  * This code and its internal interfaces are subject to change or
       
    79  * deletion without notice.</b>
       
    80  */
       
    81 public class ZipFileIndex {
       
    82     private static final String MIN_CHAR = String.valueOf(Character.MIN_VALUE);
       
    83     private static final String MAX_CHAR = String.valueOf(Character.MAX_VALUE);
       
    84 
       
    85     public final static long NOT_MODIFIED = Long.MIN_VALUE;
       
    86 
       
    87 
       
    88     private static final boolean NON_BATCH_MODE = System.getProperty("nonBatchMode") != null;// TODO: Use -XD compiler switch for this.
       
    89 
       
    90     private Map<RelativeDirectory, DirectoryEntry> directories =
       
    91             Collections.<RelativeDirectory, DirectoryEntry>emptyMap();
       
    92     private Set<RelativeDirectory> allDirs =
       
    93             Collections.<RelativeDirectory>emptySet();
       
    94 
       
    95     // ZipFileIndex data entries
       
    96     final Path zipFile;
       
    97     private Reference<Path> absFileRef;
       
    98     long zipFileLastModified = NOT_MODIFIED;
       
    99     private RandomAccessFile zipRandomFile;
       
   100     private Entry[] entries;
       
   101 
       
   102     private boolean readFromIndex = false;
       
   103     private Path zipIndexFile = null;
       
   104     private boolean triedToReadIndex = false;
       
   105     final RelativeDirectory symbolFilePrefix;
       
   106     private final int symbolFilePrefixLength;
       
   107     private boolean hasPopulatedData = false;
       
   108     long lastReferenceTimeStamp = NOT_MODIFIED;
       
   109 
       
   110     private final boolean usePreindexedCache;
       
   111     private final String preindexedCacheLocation;
       
   112 
       
   113     private boolean writeIndex = false;
       
   114 
       
   115     private Map<String, SoftReference<RelativeDirectory>> relativeDirectoryCache = new HashMap<>();
       
   116 
       
   117 
       
   118     public synchronized boolean isOpen() {
       
   119         return (zipRandomFile != null);
       
   120     }
       
   121 
       
   122     ZipFileIndex(Path zipFile, RelativeDirectory symbolFilePrefix, boolean writeIndex,
       
   123             boolean useCache, String cacheLocation) throws IOException {
       
   124         this.zipFile = zipFile;
       
   125         this.symbolFilePrefix = symbolFilePrefix;
       
   126         this.symbolFilePrefixLength = (symbolFilePrefix == null ? 0 :
       
   127             symbolFilePrefix.getPath().getBytes("UTF-8").length);
       
   128         this.writeIndex = writeIndex;
       
   129         this.usePreindexedCache = useCache;
       
   130         this.preindexedCacheLocation = cacheLocation;
       
   131 
       
   132         if (zipFile != null) {
       
   133             this.zipFileLastModified = Files.getLastModifiedTime(zipFile).toMillis();
       
   134         }
       
   135 
       
   136         // Validate integrity of the zip file
       
   137         checkIndex();
       
   138     }
       
   139 
       
   140     @Override
       
   141     public String toString() {
       
   142         return "ZipFileIndex[" + zipFile + "]";
       
   143     }
       
   144 
       
   145     // Just in case...
       
   146     @Override
       
   147     protected void finalize() throws Throwable {
       
   148         closeFile();
       
   149         super.finalize();
       
   150     }
       
   151 
       
   152     private boolean isUpToDate() {
       
   153         try {
       
   154             return (zipFile != null
       
   155                     && ((!NON_BATCH_MODE) || zipFileLastModified == Files.getLastModifiedTime(zipFile).toMillis())
       
   156                     && hasPopulatedData);
       
   157         } catch (IOException ignore) {
       
   158         }
       
   159 
       
   160         return false;
       
   161     }
       
   162 
       
   163     /**
       
   164      * Here we need to make sure that the ZipFileIndex is valid. Check the timestamp of the file and
       
   165      * if its the same as the one at the time the index was build we don't need to reopen anything.
       
   166      */
       
   167     private void checkIndex() throws IOException {
       
   168         boolean isUpToDate = true;
       
   169         if (!isUpToDate()) {
       
   170             closeFile();
       
   171             isUpToDate = false;
       
   172         }
       
   173 
       
   174         if (zipRandomFile != null || isUpToDate) {
       
   175             lastReferenceTimeStamp = System.currentTimeMillis();
       
   176             return;
       
   177         }
       
   178 
       
   179         hasPopulatedData = true;
       
   180 
       
   181         if (readIndex()) {
       
   182             lastReferenceTimeStamp = System.currentTimeMillis();
       
   183             return;
       
   184         }
       
   185 
       
   186         directories = Collections.<RelativeDirectory, DirectoryEntry>emptyMap();
       
   187         allDirs = Collections.<RelativeDirectory>emptySet();
       
   188 
       
   189         try {
       
   190             openFile();
       
   191             long totalLength = zipRandomFile.length();
       
   192             ZipDirectory directory = new ZipDirectory(zipRandomFile, 0L, totalLength, this);
       
   193             directory.buildIndex();
       
   194         } finally {
       
   195             if (zipRandomFile != null) {
       
   196                 closeFile();
       
   197             }
       
   198         }
       
   199 
       
   200         lastReferenceTimeStamp = System.currentTimeMillis();
       
   201     }
       
   202 
       
   203     private void openFile() throws FileNotFoundException {
       
   204         if (zipRandomFile == null && zipFile != null) {
       
   205             zipRandomFile = new RandomAccessFile(zipFile.toFile(), "r");
       
   206         }
       
   207     }
       
   208 
       
   209     private void cleanupState() {
       
   210         // Make sure there is a valid but empty index if the file doesn't exist
       
   211         entries = Entry.EMPTY_ARRAY;
       
   212         directories = Collections.<RelativeDirectory, DirectoryEntry>emptyMap();
       
   213         zipFileLastModified = NOT_MODIFIED;
       
   214         allDirs = Collections.<RelativeDirectory>emptySet();
       
   215     }
       
   216 
       
   217     public synchronized void close() {
       
   218         writeIndex();
       
   219         closeFile();
       
   220     }
       
   221 
       
   222     private void closeFile() {
       
   223         if (zipRandomFile != null) {
       
   224             try {
       
   225                 zipRandomFile.close();
       
   226             } catch (IOException ex) {
       
   227             }
       
   228             zipRandomFile = null;
       
   229         }
       
   230     }
       
   231 
       
   232     /**
       
   233      * Returns the ZipFileIndexEntry for a path, if there is one.
       
   234      */
       
   235     synchronized Entry getZipIndexEntry(RelativePath path) {
       
   236         try {
       
   237             checkIndex();
       
   238             DirectoryEntry de = directories.get(path.dirname());
       
   239             String lookFor = path.basename();
       
   240             return (de == null) ? null : de.getEntry(lookFor);
       
   241         }
       
   242         catch (IOException e) {
       
   243             return null;
       
   244         }
       
   245     }
       
   246 
       
   247     /**
       
   248      * Returns a javac List of filenames within a directory in the ZipFileIndex.
       
   249      */
       
   250     public synchronized com.sun.tools.javac.util.List<String> getFiles(RelativeDirectory path) {
       
   251         try {
       
   252             checkIndex();
       
   253 
       
   254             DirectoryEntry de = directories.get(path);
       
   255             com.sun.tools.javac.util.List<String> ret = de == null ? null : de.getFiles();
       
   256 
       
   257             if (ret == null) {
       
   258                 return com.sun.tools.javac.util.List.<String>nil();
       
   259             }
       
   260             return ret;
       
   261         }
       
   262         catch (IOException e) {
       
   263             return com.sun.tools.javac.util.List.<String>nil();
       
   264         }
       
   265     }
       
   266 
       
   267     public synchronized List<String> getDirectories(RelativeDirectory path) {
       
   268         try {
       
   269             checkIndex();
       
   270 
       
   271             DirectoryEntry de = directories.get(path);
       
   272             com.sun.tools.javac.util.List<String> ret = de == null ? null : de.getDirectories();
       
   273 
       
   274             if (ret == null) {
       
   275                 return com.sun.tools.javac.util.List.<String>nil();
       
   276             }
       
   277 
       
   278             return ret;
       
   279         }
       
   280         catch (IOException e) {
       
   281             return com.sun.tools.javac.util.List.<String>nil();
       
   282         }
       
   283     }
       
   284 
       
   285     public synchronized Set<RelativeDirectory> getAllDirectories() {
       
   286         try {
       
   287             checkIndex();
       
   288             if (allDirs == Collections.EMPTY_SET) {
       
   289                 allDirs = new java.util.LinkedHashSet<>(directories.keySet());
       
   290             }
       
   291 
       
   292             return allDirs;
       
   293         }
       
   294         catch (IOException e) {
       
   295             return Collections.<RelativeDirectory>emptySet();
       
   296         }
       
   297     }
       
   298 
       
   299     /**
       
   300      * Tests if a specific path exists in the zip.  This method will return true
       
   301      * for file entries and directories.
       
   302      *
       
   303      * @param path A path within the zip.
       
   304      * @return True if the path is a file or dir, false otherwise.
       
   305      */
       
   306     public synchronized boolean contains(RelativePath path) {
       
   307         try {
       
   308             checkIndex();
       
   309             return getZipIndexEntry(path) != null;
       
   310         }
       
   311         catch (IOException e) {
       
   312             return false;
       
   313         }
       
   314     }
       
   315 
       
   316     public synchronized boolean isDirectory(RelativePath path) throws IOException {
       
   317         // The top level in a zip file is always a directory.
       
   318         if (path.getPath().length() == 0) {
       
   319             lastReferenceTimeStamp = System.currentTimeMillis();
       
   320             return true;
       
   321         }
       
   322 
       
   323         checkIndex();
       
   324         return directories.get(path) != null;
       
   325     }
       
   326 
       
   327     public synchronized long getLastModified(RelativeFile path) throws IOException {
       
   328         Entry entry = getZipIndexEntry(path);
       
   329         if (entry == null)
       
   330             throw new FileNotFoundException();
       
   331         return entry.getLastModified();
       
   332     }
       
   333 
       
   334     public synchronized int length(RelativeFile path) throws IOException {
       
   335         Entry entry = getZipIndexEntry(path);
       
   336         if (entry == null)
       
   337             throw new FileNotFoundException();
       
   338 
       
   339         if (entry.isDir) {
       
   340             return 0;
       
   341         }
       
   342 
       
   343         byte[] header = getHeader(entry);
       
   344         // entry is not compressed?
       
   345         if (get2ByteLittleEndian(header, 8) == 0) {
       
   346             return entry.compressedSize;
       
   347         } else {
       
   348             return entry.size;
       
   349         }
       
   350     }
       
   351 
       
   352     public synchronized byte[] read(RelativeFile path) throws IOException {
       
   353         Entry entry = getZipIndexEntry(path);
       
   354         if (entry == null)
       
   355             throw new FileNotFoundException("Path not found in ZIP: " + path.path);
       
   356         return read(entry);
       
   357     }
       
   358 
       
   359     synchronized byte[] read(Entry entry) throws IOException {
       
   360         openFile();
       
   361         byte[] result = readBytes(entry);
       
   362         closeFile();
       
   363         return result;
       
   364     }
       
   365 
       
   366     public synchronized int read(RelativeFile path, byte[] buffer) throws IOException {
       
   367         Entry entry = getZipIndexEntry(path);
       
   368         if (entry == null)
       
   369             throw new FileNotFoundException();
       
   370         return read(entry, buffer);
       
   371     }
       
   372 
       
   373     synchronized int read(Entry entry, byte[] buffer)
       
   374             throws IOException {
       
   375         int result = readBytes(entry, buffer);
       
   376         return  result;
       
   377     }
       
   378 
       
   379     private byte[] readBytes(Entry entry) throws IOException {
       
   380         byte[] header = getHeader(entry);
       
   381         int csize = entry.compressedSize;
       
   382         byte[] cbuf = new byte[csize];
       
   383         zipRandomFile.skipBytes(get2ByteLittleEndian(header, 26) + get2ByteLittleEndian(header, 28));
       
   384         zipRandomFile.readFully(cbuf, 0, csize);
       
   385 
       
   386         // is this compressed - offset 8 in the ZipEntry header
       
   387         if (get2ByteLittleEndian(header, 8) == 0)
       
   388             return cbuf;
       
   389 
       
   390         int size = entry.size;
       
   391         byte[] buf = new byte[size];
       
   392         if (inflate(cbuf, buf) != size)
       
   393             throw new ZipException("corrupted zip file");
       
   394 
       
   395         return buf;
       
   396     }
       
   397 
       
   398     /**
       
   399      *
       
   400      */
       
   401     private int readBytes(Entry entry, byte[] buffer) throws IOException {
       
   402         byte[] header = getHeader(entry);
       
   403 
       
   404         // entry is not compressed?
       
   405         if (get2ByteLittleEndian(header, 8) == 0) {
       
   406             zipRandomFile.skipBytes(get2ByteLittleEndian(header, 26) + get2ByteLittleEndian(header, 28));
       
   407             int offset = 0;
       
   408             int size = buffer.length;
       
   409             while (offset < size) {
       
   410                 int count = zipRandomFile.read(buffer, offset, size - offset);
       
   411                 if (count == -1)
       
   412                     break;
       
   413                 offset += count;
       
   414             }
       
   415             return entry.size;
       
   416         }
       
   417 
       
   418         int csize = entry.compressedSize;
       
   419         byte[] cbuf = new byte[csize];
       
   420         zipRandomFile.skipBytes(get2ByteLittleEndian(header, 26) + get2ByteLittleEndian(header, 28));
       
   421         zipRandomFile.readFully(cbuf, 0, csize);
       
   422 
       
   423         int count = inflate(cbuf, buffer);
       
   424         if (count == -1)
       
   425             throw new ZipException("corrupted zip file");
       
   426 
       
   427         return entry.size;
       
   428     }
       
   429 
       
   430     //----------------------------------------------------------------------------
       
   431     // Zip utilities
       
   432     //----------------------------------------------------------------------------
       
   433 
       
   434     private byte[] getHeader(Entry entry) throws IOException {
       
   435         zipRandomFile.seek(entry.offset);
       
   436         byte[] header = new byte[30];
       
   437         zipRandomFile.readFully(header);
       
   438         if (get4ByteLittleEndian(header, 0) != 0x04034b50)
       
   439             throw new ZipException("corrupted zip file");
       
   440         if ((get2ByteLittleEndian(header, 6) & 1) != 0)
       
   441             throw new ZipException("encrypted zip file"); // offset 6 in the header of the ZipFileEntry
       
   442         return header;
       
   443     }
       
   444 
       
   445   /*
       
   446    * Inflate using the java.util.zip.Inflater class
       
   447    */
       
   448     private SoftReference<Inflater> inflaterRef;
       
   449     private int inflate(byte[] src, byte[] dest) {
       
   450         Inflater inflater = (inflaterRef == null ? null : inflaterRef.get());
       
   451 
       
   452         // construct the inflater object or reuse an existing one
       
   453         if (inflater == null)
       
   454             inflaterRef = new SoftReference<>(inflater = new Inflater(true));
       
   455 
       
   456         inflater.reset();
       
   457         inflater.setInput(src);
       
   458         try {
       
   459             return inflater.inflate(dest);
       
   460         } catch (DataFormatException ex) {
       
   461             return -1;
       
   462         }
       
   463     }
       
   464 
       
   465     /**
       
   466      * return the two bytes buf[pos], buf[pos+1] as an unsigned integer in little
       
   467      * endian format.
       
   468      */
       
   469     private static int get2ByteLittleEndian(byte[] buf, int pos) {
       
   470         return (buf[pos] & 0xFF) + ((buf[pos+1] & 0xFF) << 8);
       
   471     }
       
   472 
       
   473     /**
       
   474      * return the 4 bytes buf[i..i+3] as an integer in little endian format.
       
   475      */
       
   476     private static int get4ByteLittleEndian(byte[] buf, int pos) {
       
   477         return (buf[pos] & 0xFF) + ((buf[pos + 1] & 0xFF) << 8) +
       
   478                 ((buf[pos + 2] & 0xFF) << 16) + ((buf[pos + 3] & 0xFF) << 24);
       
   479     }
       
   480 
       
   481     /* ----------------------------------------------------------------------------
       
   482      * ZipDirectory
       
   483      * ----------------------------------------------------------------------------*/
       
   484 
       
   485     private class ZipDirectory {
       
   486         private RelativeDirectory lastDir;
       
   487         private int lastStart;
       
   488         private int lastLen;
       
   489 
       
   490         byte[] zipDir;
       
   491         RandomAccessFile zipRandomFile = null;
       
   492         ZipFileIndex zipFileIndex = null;
       
   493 
       
   494         public ZipDirectory(RandomAccessFile zipRandomFile, long start, long end, ZipFileIndex index) throws IOException {
       
   495             this.zipRandomFile = zipRandomFile;
       
   496             this.zipFileIndex = index;
       
   497             hasValidHeader();
       
   498             findCENRecord(start, end);
       
   499         }
       
   500 
       
   501         /*
       
   502          * the zip entry signature should be at offset 0, otherwise allow the
       
   503          * calling logic to take evasive action by throwing ZipFormatException.
       
   504          */
       
   505         private boolean hasValidHeader() throws IOException {
       
   506             final long pos = zipRandomFile.getFilePointer();
       
   507             try {
       
   508                 if (zipRandomFile.read() == 'P') {
       
   509                     if (zipRandomFile.read() == 'K') {
       
   510                         if (zipRandomFile.read() == 0x03) {
       
   511                             if (zipRandomFile.read() == 0x04) {
       
   512                                 return true;
       
   513                             }
       
   514                         }
       
   515                     }
       
   516                 }
       
   517             } finally {
       
   518                 zipRandomFile.seek(pos);
       
   519             }
       
   520             throw new ZipFormatException("invalid zip magic");
       
   521         }
       
   522 
       
   523         /*
       
   524          * Reads zip file central directory.
       
   525          * For more details see readCEN in zip_util.c from the JDK sources.
       
   526          * This is a Java port of that function.
       
   527          */
       
   528         private void findCENRecord(long start, long end) throws IOException {
       
   529             long totalLength = end - start;
       
   530             int endbuflen = 1024;
       
   531             byte[] endbuf = new byte[endbuflen];
       
   532             long endbufend = end - start;
       
   533 
       
   534             // There is a variable-length field after the dir offset record. We need to do consequential search.
       
   535             while (endbufend >= 22) {
       
   536                 if (endbufend < endbuflen)
       
   537                     endbuflen = (int)endbufend;
       
   538                 long endbufpos = endbufend - endbuflen;
       
   539                 zipRandomFile.seek(start + endbufpos);
       
   540                 zipRandomFile.readFully(endbuf, 0, endbuflen);
       
   541                 int i = endbuflen - 22;
       
   542                 while (i >= 0 &&
       
   543                         !(endbuf[i] == 0x50 &&
       
   544                         endbuf[i + 1] == 0x4b &&
       
   545                         endbuf[i + 2] == 0x05 &&
       
   546                         endbuf[i + 3] == 0x06 &&
       
   547                         endbufpos + i + 22 +
       
   548                         get2ByteLittleEndian(endbuf, i + 20) == totalLength)) {
       
   549                     i--;
       
   550                 }
       
   551 
       
   552                 if (i >= 0) {
       
   553                     zipDir = new byte[get4ByteLittleEndian(endbuf, i + 12)];
       
   554                     int sz = get4ByteLittleEndian(endbuf, i + 16);
       
   555                     // a negative offset or the entries field indicates a
       
   556                     // potential zip64 archive
       
   557                     if (sz < 0 || get2ByteLittleEndian(endbuf, i + 10) == 0xffff) {
       
   558                         throw new ZipFormatException("detected a zip64 archive");
       
   559                     }
       
   560                     zipRandomFile.seek(start + sz);
       
   561                     zipRandomFile.readFully(zipDir, 0, zipDir.length);
       
   562                     return;
       
   563                 } else {
       
   564                     endbufend = endbufpos + 21;
       
   565                 }
       
   566             }
       
   567             throw new ZipException("cannot read zip file");
       
   568         }
       
   569 
       
   570         private void buildIndex() throws IOException {
       
   571             int len = zipDir.length;
       
   572 
       
   573             // Add each of the files
       
   574             if (len > 0) {
       
   575                 directories = new LinkedHashMap<>();
       
   576                 ArrayList<Entry> entryList = new ArrayList<>();
       
   577                 for (int pos = 0; pos < len; ) {
       
   578                     pos = readEntry(pos, entryList, directories);
       
   579                 }
       
   580 
       
   581                 // Add the accumulated dirs into the same list
       
   582                 for (RelativeDirectory d: directories.keySet()) {
       
   583                     // use shared RelativeDirectory objects for parent dirs
       
   584                     RelativeDirectory parent = getRelativeDirectory(d.dirname().getPath());
       
   585                     String file = d.basename();
       
   586                     Entry zipFileIndexEntry = new Entry(parent, file);
       
   587                     zipFileIndexEntry.isDir = true;
       
   588                     entryList.add(zipFileIndexEntry);
       
   589                 }
       
   590 
       
   591                 entries = entryList.toArray(new Entry[entryList.size()]);
       
   592                 Arrays.sort(entries);
       
   593             } else {
       
   594                 cleanupState();
       
   595             }
       
   596         }
       
   597 
       
   598         private int readEntry(int pos, List<Entry> entryList,
       
   599                 Map<RelativeDirectory, DirectoryEntry> directories) throws IOException {
       
   600             if (get4ByteLittleEndian(zipDir, pos) != 0x02014b50) {
       
   601                 throw new ZipException("cannot read zip file entry");
       
   602             }
       
   603 
       
   604             int dirStart = pos + 46;
       
   605             int fileStart = dirStart;
       
   606             int fileEnd = fileStart + get2ByteLittleEndian(zipDir, pos + 28);
       
   607 
       
   608             if (zipFileIndex.symbolFilePrefixLength != 0 &&
       
   609                     ((fileEnd - fileStart) >= symbolFilePrefixLength)) {
       
   610                 dirStart += zipFileIndex.symbolFilePrefixLength;
       
   611                fileStart += zipFileIndex.symbolFilePrefixLength;
       
   612             }
       
   613             // Force any '\' to '/'. Keep the position of the last separator.
       
   614             for (int index = fileStart; index < fileEnd; index++) {
       
   615                 byte nextByte = zipDir[index];
       
   616                 if (nextByte == (byte)'\\') {
       
   617                     zipDir[index] = (byte)'/';
       
   618                     fileStart = index + 1;
       
   619                 } else if (nextByte == (byte)'/') {
       
   620                     fileStart = index + 1;
       
   621                 }
       
   622             }
       
   623 
       
   624             RelativeDirectory directory = null;
       
   625             if (fileStart == dirStart)
       
   626                 directory = getRelativeDirectory("");
       
   627             else if (lastDir != null && lastLen == fileStart - dirStart - 1) {
       
   628                 int index = lastLen - 1;
       
   629                 while (zipDir[lastStart + index] == zipDir[dirStart + index]) {
       
   630                     if (index == 0) {
       
   631                         directory = lastDir;
       
   632                         break;
       
   633                     }
       
   634                     index--;
       
   635                 }
       
   636             }
       
   637 
       
   638             // Sub directories
       
   639             if (directory == null) {
       
   640                 lastStart = dirStart;
       
   641                 lastLen = fileStart - dirStart - 1;
       
   642 
       
   643                 directory = getRelativeDirectory(new String(zipDir, dirStart, lastLen, "UTF-8"));
       
   644                 lastDir = directory;
       
   645 
       
   646                 // Enter also all the parent directories
       
   647                 RelativeDirectory tempDirectory = directory;
       
   648 
       
   649                 while (directories.get(tempDirectory) == null) {
       
   650                     directories.put(tempDirectory, new DirectoryEntry(tempDirectory, zipFileIndex));
       
   651                     if (tempDirectory.path.indexOf("/") == tempDirectory.path.length() - 1)
       
   652                         break;
       
   653                     else {
       
   654                         // use shared RelativeDirectory objects for parent dirs
       
   655                         tempDirectory = getRelativeDirectory(tempDirectory.dirname().getPath());
       
   656                     }
       
   657                 }
       
   658             }
       
   659             else {
       
   660                 if (directories.get(directory) == null) {
       
   661                     directories.put(directory, new DirectoryEntry(directory, zipFileIndex));
       
   662                 }
       
   663             }
       
   664 
       
   665             // For each dir create also a file
       
   666             if (fileStart != fileEnd) {
       
   667                 Entry entry = new Entry(directory,
       
   668                         new String(zipDir, fileStart, fileEnd - fileStart, "UTF-8"));
       
   669 
       
   670                 entry.setNativeTime(get4ByteLittleEndian(zipDir, pos + 12));
       
   671                 entry.compressedSize = get4ByteLittleEndian(zipDir, pos + 20);
       
   672                 entry.size = get4ByteLittleEndian(zipDir, pos + 24);
       
   673                 entry.offset = get4ByteLittleEndian(zipDir, pos + 42);
       
   674                 entryList.add(entry);
       
   675             }
       
   676 
       
   677             return pos + 46 +
       
   678                     get2ByteLittleEndian(zipDir, pos + 28) +
       
   679                     get2ByteLittleEndian(zipDir, pos + 30) +
       
   680                     get2ByteLittleEndian(zipDir, pos + 32);
       
   681         }
       
   682     }
       
   683 
       
   684     /**
       
   685      * Returns the last modified timestamp of a zip file.
       
   686      * @return long
       
   687      */
       
   688     public long getZipFileLastModified() throws IOException {
       
   689         synchronized (this) {
       
   690             checkIndex();
       
   691             return zipFileLastModified;
       
   692         }
       
   693     }
       
   694 
       
   695     /** ------------------------------------------------------------------------
       
   696      *  DirectoryEntry class
       
   697      * -------------------------------------------------------------------------*/
       
   698 
       
   699     static class DirectoryEntry {
       
   700         private boolean filesInited;
       
   701         private boolean directoriesInited;
       
   702         private boolean zipFileEntriesInited;
       
   703         private boolean entriesInited;
       
   704 
       
   705         private long writtenOffsetOffset = 0;
       
   706 
       
   707         private RelativeDirectory dirName;
       
   708 
       
   709         private com.sun.tools.javac.util.List<String> zipFileEntriesFiles = com.sun.tools.javac.util.List.<String>nil();
       
   710         private com.sun.tools.javac.util.List<String> zipFileEntriesDirectories = com.sun.tools.javac.util.List.<String>nil();
       
   711         private com.sun.tools.javac.util.List<Entry>  zipFileEntries = com.sun.tools.javac.util.List.<Entry>nil();
       
   712 
       
   713         private List<Entry> entries = new ArrayList<>();
       
   714 
       
   715         private ZipFileIndex zipFileIndex;
       
   716 
       
   717         private int numEntries;
       
   718 
       
   719         DirectoryEntry(RelativeDirectory dirName, ZipFileIndex index) {
       
   720             filesInited = false;
       
   721             directoriesInited = false;
       
   722             entriesInited = false;
       
   723 
       
   724             this.dirName = dirName;
       
   725             this.zipFileIndex = index;
       
   726         }
       
   727 
       
   728         private com.sun.tools.javac.util.List<String> getFiles() {
       
   729             if (!filesInited) {
       
   730                 initEntries();
       
   731                 for (Entry e : entries) {
       
   732                     if (!e.isDir) {
       
   733                         zipFileEntriesFiles = zipFileEntriesFiles.append(e.name);
       
   734                     }
       
   735                 }
       
   736                 filesInited = true;
       
   737             }
       
   738             return zipFileEntriesFiles;
       
   739         }
       
   740 
       
   741         private com.sun.tools.javac.util.List<String> getDirectories() {
       
   742             if (!directoriesInited) {
       
   743                 initEntries();
       
   744                 for (Entry e : entries) {
       
   745                     if (e.isDir) {
       
   746                         zipFileEntriesDirectories = zipFileEntriesDirectories.append(e.name);
       
   747                     }
       
   748                 }
       
   749                 directoriesInited = true;
       
   750             }
       
   751             return zipFileEntriesDirectories;
       
   752         }
       
   753 
       
   754         private com.sun.tools.javac.util.List<Entry> getEntries() {
       
   755             if (!zipFileEntriesInited) {
       
   756                 initEntries();
       
   757                 zipFileEntries = com.sun.tools.javac.util.List.nil();
       
   758                 for (Entry zfie : entries) {
       
   759                     zipFileEntries = zipFileEntries.append(zfie);
       
   760                 }
       
   761                 zipFileEntriesInited = true;
       
   762             }
       
   763             return zipFileEntries;
       
   764         }
       
   765 
       
   766         private Entry getEntry(String rootName) {
       
   767             initEntries();
       
   768             int index = Collections.binarySearch(entries, new Entry(dirName, rootName));
       
   769             if (index < 0) {
       
   770                 return null;
       
   771             }
       
   772 
       
   773             return entries.get(index);
       
   774         }
       
   775 
       
   776         private void initEntries() {
       
   777             if (entriesInited) {
       
   778                 return;
       
   779             }
       
   780 
       
   781             if (!zipFileIndex.readFromIndex) {
       
   782                 int from = -Arrays.binarySearch(zipFileIndex.entries,
       
   783                         new Entry(dirName, ZipFileIndex.MIN_CHAR)) - 1;
       
   784                 int to = -Arrays.binarySearch(zipFileIndex.entries,
       
   785                         new Entry(dirName, MAX_CHAR)) - 1;
       
   786 
       
   787                 for (int i = from; i < to; i++) {
       
   788                     entries.add(zipFileIndex.entries[i]);
       
   789                 }
       
   790             } else {
       
   791                 Path indexFile = zipFileIndex.getIndexFile();
       
   792                 if (indexFile != null) {
       
   793                     RandomAccessFile raf = null;
       
   794                     try {
       
   795                         raf = new RandomAccessFile(indexFile.toFile(), "r");
       
   796                         raf.seek(writtenOffsetOffset);
       
   797 
       
   798                         for (int nFiles = 0; nFiles < numEntries; nFiles++) {
       
   799                             // Read the name bytes
       
   800                             int zfieNameBytesLen = raf.readInt();
       
   801                             byte [] zfieNameBytes = new byte[zfieNameBytesLen];
       
   802                             raf.read(zfieNameBytes);
       
   803                             String eName = new String(zfieNameBytes, "UTF-8");
       
   804 
       
   805                             // Read isDir
       
   806                             boolean eIsDir = raf.readByte() == (byte)0 ? false : true;
       
   807 
       
   808                             // Read offset of bytes in the real Jar/Zip file
       
   809                             int eOffset = raf.readInt();
       
   810 
       
   811                             // Read size of the file in the real Jar/Zip file
       
   812                             int eSize = raf.readInt();
       
   813 
       
   814                             // Read compressed size of the file in the real Jar/Zip file
       
   815                             int eCsize = raf.readInt();
       
   816 
       
   817                             // Read java time stamp of the file in the real Jar/Zip file
       
   818                             long eJavaTimestamp = raf.readLong();
       
   819 
       
   820                             Entry rfie = new Entry(dirName, eName);
       
   821                             rfie.isDir = eIsDir;
       
   822                             rfie.offset = eOffset;
       
   823                             rfie.size = eSize;
       
   824                             rfie.compressedSize = eCsize;
       
   825                             rfie.javatime = eJavaTimestamp;
       
   826                             entries.add(rfie);
       
   827                         }
       
   828                     } catch (Throwable t) {
       
   829                         // Do nothing
       
   830                     } finally {
       
   831                         try {
       
   832                             if (raf != null) {
       
   833                                 raf.close();
       
   834                             }
       
   835                         } catch (Throwable t) {
       
   836                             // Do nothing
       
   837                         }
       
   838                     }
       
   839                 }
       
   840             }
       
   841 
       
   842             entriesInited = true;
       
   843         }
       
   844 
       
   845         List<Entry> getEntriesAsCollection() {
       
   846             initEntries();
       
   847 
       
   848             return entries;
       
   849         }
       
   850     }
       
   851 
       
   852     private boolean readIndex() {
       
   853         if (triedToReadIndex || !usePreindexedCache) {
       
   854             return false;
       
   855         }
       
   856 
       
   857         boolean ret = false;
       
   858         synchronized (this) {
       
   859             triedToReadIndex = true;
       
   860             RandomAccessFile raf = null;
       
   861             try {
       
   862                 Path indexFileName = getIndexFile();
       
   863                 raf = new RandomAccessFile(indexFileName.toFile(), "r");
       
   864 
       
   865                 long fileStamp = raf.readLong();
       
   866                 if (Files.getLastModifiedTime(zipFile).toMillis() != fileStamp) {
       
   867                     ret = false;
       
   868                 } else {
       
   869                     directories = new LinkedHashMap<>();
       
   870                     int numDirs = raf.readInt();
       
   871                     for (int nDirs = 0; nDirs < numDirs; nDirs++) {
       
   872                         int dirNameBytesLen = raf.readInt();
       
   873                         byte [] dirNameBytes = new byte[dirNameBytesLen];
       
   874                         raf.read(dirNameBytes);
       
   875 
       
   876                         RelativeDirectory dirNameStr = getRelativeDirectory(new String(dirNameBytes, "UTF-8"));
       
   877                         DirectoryEntry de = new DirectoryEntry(dirNameStr, this);
       
   878                         de.numEntries = raf.readInt();
       
   879                         de.writtenOffsetOffset = raf.readLong();
       
   880                         directories.put(dirNameStr, de);
       
   881                     }
       
   882                     ret = true;
       
   883                     zipFileLastModified = fileStamp;
       
   884                 }
       
   885             } catch (Throwable t) {
       
   886                 // Do nothing
       
   887             } finally {
       
   888                 if (raf != null) {
       
   889                     try {
       
   890                         raf.close();
       
   891                     } catch (Throwable tt) {
       
   892                         // Do nothing
       
   893                     }
       
   894                 }
       
   895             }
       
   896             if (ret == true) {
       
   897                 readFromIndex = true;
       
   898             }
       
   899         }
       
   900 
       
   901         return ret;
       
   902     }
       
   903 
       
   904     private boolean writeIndex() {
       
   905         boolean ret = false;
       
   906         if (readFromIndex || !usePreindexedCache) {
       
   907             return true;
       
   908         }
       
   909 
       
   910         if (!writeIndex) {
       
   911             return true;
       
   912         }
       
   913 
       
   914         Path indexFile = getIndexFile();
       
   915         if (indexFile == null) {
       
   916             return false;
       
   917         }
       
   918 
       
   919         RandomAccessFile raf = null;
       
   920         long writtenSoFar = 0;
       
   921         try {
       
   922             raf = new RandomAccessFile(indexFile.toFile(), "rw");
       
   923 
       
   924             raf.writeLong(zipFileLastModified);
       
   925             writtenSoFar += 8;
       
   926 
       
   927             List<DirectoryEntry> directoriesToWrite = new ArrayList<>();
       
   928             Map<RelativeDirectory, Long> offsets = new HashMap<>();
       
   929             raf.writeInt(directories.keySet().size());
       
   930             writtenSoFar += 4;
       
   931 
       
   932             for (RelativeDirectory dirName: directories.keySet()) {
       
   933                 DirectoryEntry dirEntry = directories.get(dirName);
       
   934 
       
   935                 directoriesToWrite.add(dirEntry);
       
   936 
       
   937                 // Write the dir name bytes
       
   938                 byte [] dirNameBytes = dirName.getPath().getBytes("UTF-8");
       
   939                 int dirNameBytesLen = dirNameBytes.length;
       
   940                 raf.writeInt(dirNameBytesLen);
       
   941                 writtenSoFar += 4;
       
   942 
       
   943                 raf.write(dirNameBytes);
       
   944                 writtenSoFar += dirNameBytesLen;
       
   945 
       
   946                 // Write the number of files in the dir
       
   947                 List<Entry> dirEntries = dirEntry.getEntriesAsCollection();
       
   948                 raf.writeInt(dirEntries.size());
       
   949                 writtenSoFar += 4;
       
   950 
       
   951                 offsets.put(dirName, new Long(writtenSoFar));
       
   952 
       
   953                 // Write the offset of the file's data in the dir
       
   954                 dirEntry.writtenOffsetOffset = 0L;
       
   955                 raf.writeLong(0L);
       
   956                 writtenSoFar += 8;
       
   957             }
       
   958 
       
   959             for (DirectoryEntry de : directoriesToWrite) {
       
   960                 // Fix up the offset in the directory table
       
   961                 long currFP = raf.getFilePointer();
       
   962 
       
   963                 long offsetOffset = offsets.get(de.dirName).longValue();
       
   964                 raf.seek(offsetOffset);
       
   965                 raf.writeLong(writtenSoFar);
       
   966 
       
   967                 raf.seek(currFP);
       
   968 
       
   969                 // Now write each of the files in the DirectoryEntry
       
   970                 List<Entry> list = de.getEntriesAsCollection();
       
   971                 for (Entry zfie : list) {
       
   972                     // Write the name bytes
       
   973                     byte [] zfieNameBytes = zfie.name.getBytes("UTF-8");
       
   974                     int zfieNameBytesLen = zfieNameBytes.length;
       
   975                     raf.writeInt(zfieNameBytesLen);
       
   976                     writtenSoFar += 4;
       
   977                     raf.write(zfieNameBytes);
       
   978                     writtenSoFar += zfieNameBytesLen;
       
   979 
       
   980                     // Write isDir
       
   981                     raf.writeByte(zfie.isDir ? (byte)1 : (byte)0);
       
   982                     writtenSoFar += 1;
       
   983 
       
   984                     // Write offset of bytes in the real Jar/Zip file
       
   985                     raf.writeInt(zfie.offset);
       
   986                     writtenSoFar += 4;
       
   987 
       
   988                     // Write size of the file in the real Jar/Zip file
       
   989                     raf.writeInt(zfie.size);
       
   990                     writtenSoFar += 4;
       
   991 
       
   992                     // Write compressed size of the file in the real Jar/Zip file
       
   993                     raf.writeInt(zfie.compressedSize);
       
   994                     writtenSoFar += 4;
       
   995 
       
   996                     // Write java time stamp of the file in the real Jar/Zip file
       
   997                     raf.writeLong(zfie.getLastModified());
       
   998                     writtenSoFar += 8;
       
   999                 }
       
  1000             }
       
  1001         } catch (Throwable t) {
       
  1002             // Do nothing
       
  1003         } finally {
       
  1004             try {
       
  1005                 if (raf != null) {
       
  1006                     raf.close();
       
  1007                 }
       
  1008             } catch(IOException ioe) {
       
  1009                 // Do nothing
       
  1010             }
       
  1011         }
       
  1012 
       
  1013         return ret;
       
  1014     }
       
  1015 
       
  1016     public boolean writeZipIndex() {
       
  1017         synchronized (this) {
       
  1018             return writeIndex();
       
  1019         }
       
  1020     }
       
  1021 
       
  1022     private Path getIndexFile() {
       
  1023         if (zipIndexFile == null) {
       
  1024             if (zipFile == null) {
       
  1025                 return null;
       
  1026             }
       
  1027 
       
  1028             zipIndexFile = Paths.get((preindexedCacheLocation == null ? "" : preindexedCacheLocation) +
       
  1029                     zipFile.getFileName() + ".index");
       
  1030         }
       
  1031 
       
  1032         return zipIndexFile;
       
  1033     }
       
  1034 
       
  1035     public Path getZipFile() {
       
  1036         return zipFile;
       
  1037     }
       
  1038 
       
  1039     Path getAbsoluteFile() {
       
  1040         Path absFile = (absFileRef == null ? null : absFileRef.get());
       
  1041         if (absFile == null) {
       
  1042             absFile = zipFile.toAbsolutePath();
       
  1043             absFileRef = new SoftReference<>(absFile);
       
  1044         }
       
  1045         return absFile;
       
  1046     }
       
  1047 
       
  1048     private RelativeDirectory getRelativeDirectory(String path) {
       
  1049         RelativeDirectory rd;
       
  1050         SoftReference<RelativeDirectory> ref = relativeDirectoryCache.get(path);
       
  1051         if (ref != null) {
       
  1052             rd = ref.get();
       
  1053             if (rd != null)
       
  1054                 return rd;
       
  1055         }
       
  1056         rd = new RelativeDirectory(path);
       
  1057         relativeDirectoryCache.put(path, new SoftReference<>(rd));
       
  1058         return rd;
       
  1059     }
       
  1060 
       
  1061     static class Entry implements Comparable<Entry> {
       
  1062         public static final Entry[] EMPTY_ARRAY = {};
       
  1063 
       
  1064         // Directory related
       
  1065         RelativeDirectory dir;
       
  1066         boolean isDir;
       
  1067 
       
  1068         // File related
       
  1069         String name;
       
  1070 
       
  1071         int offset;
       
  1072         int size;
       
  1073         int compressedSize;
       
  1074         long javatime;
       
  1075 
       
  1076         private int nativetime;
       
  1077 
       
  1078         public Entry(RelativePath path) {
       
  1079             this(path.dirname(), path.basename());
       
  1080         }
       
  1081 
       
  1082         public Entry(RelativeDirectory directory, String name) {
       
  1083             this.dir = directory;
       
  1084             this.name = name;
       
  1085         }
       
  1086 
       
  1087         public String getName() {
       
  1088             return new RelativeFile(dir, name).getPath();
       
  1089         }
       
  1090 
       
  1091         public String getFileName() {
       
  1092             return name;
       
  1093         }
       
  1094 
       
  1095         public long getLastModified() {
       
  1096             if (javatime == 0) {
       
  1097                     javatime = dosToJavaTime(nativetime);
       
  1098             }
       
  1099             return javatime;
       
  1100         }
       
  1101 
       
  1102         // based on dosToJavaTime in java.util.Zip, but avoiding the
       
  1103         // use of deprecated Date constructor
       
  1104         private static long dosToJavaTime(int dtime) {
       
  1105             Calendar c = Calendar.getInstance();
       
  1106             c.set(Calendar.YEAR,        ((dtime >> 25) & 0x7f) + 1980);
       
  1107             c.set(Calendar.MONTH,       ((dtime >> 21) & 0x0f) - 1);
       
  1108             c.set(Calendar.DATE,        ((dtime >> 16) & 0x1f));
       
  1109             c.set(Calendar.HOUR_OF_DAY, ((dtime >> 11) & 0x1f));
       
  1110             c.set(Calendar.MINUTE,      ((dtime >>  5) & 0x3f));
       
  1111             c.set(Calendar.SECOND,      ((dtime <<  1) & 0x3e));
       
  1112             c.set(Calendar.MILLISECOND, 0);
       
  1113             return c.getTimeInMillis();
       
  1114         }
       
  1115 
       
  1116         void setNativeTime(int natTime) {
       
  1117             nativetime = natTime;
       
  1118         }
       
  1119 
       
  1120         public boolean isDirectory() {
       
  1121             return isDir;
       
  1122         }
       
  1123 
       
  1124         public int compareTo(Entry other) {
       
  1125             RelativeDirectory otherD = other.dir;
       
  1126             if (dir != otherD) {
       
  1127                 int c = dir.compareTo(otherD);
       
  1128                 if (c != 0)
       
  1129                     return c;
       
  1130             }
       
  1131             return name.compareTo(other.name);
       
  1132         }
       
  1133 
       
  1134         @Override
       
  1135         public boolean equals(Object o) {
       
  1136             if (!(o instanceof Entry))
       
  1137                 return false;
       
  1138             Entry other = (Entry) o;
       
  1139             return dir.equals(other.dir) && name.equals(other.name);
       
  1140         }
       
  1141 
       
  1142         @Override
       
  1143         public int hashCode() {
       
  1144             int hash = 7;
       
  1145             hash = 97 * hash + (this.dir != null ? this.dir.hashCode() : 0);
       
  1146             hash = 97 * hash + (this.name != null ? this.name.hashCode() : 0);
       
  1147             return hash;
       
  1148         }
       
  1149 
       
  1150         @Override
       
  1151         public String toString() {
       
  1152             return isDir ? ("Dir:" + dir + " : " + name) :
       
  1153                 (dir + ":" + name);
       
  1154         }
       
  1155     }
       
  1156 
       
  1157     /*
       
  1158      * Exception primarily used to implement a failover, used exclusively here.
       
  1159      */
       
  1160 
       
  1161     static final class ZipFormatException extends IOException {
       
  1162         private static final long serialVersionUID = 8000196834066748623L;
       
  1163         protected ZipFormatException(String message) {
       
  1164             super(message);
       
  1165         }
       
  1166 
       
  1167         protected ZipFormatException(String message, Throwable cause) {
       
  1168             super(message, cause);
       
  1169         }
       
  1170     }
       
  1171 }