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