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