Wed, 09 Apr 2008 13:41:45 +0100
5009937: hiding versus generics versus binary compatibility
Summary: missing implementation of JLS 8.4.8.3 (different arguments with same erasure not always triggering a compiler error)
Reviewed-by: jjg
1 package com.sun.tools.javac.zip;
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.*;
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);
33 public final static long NOT_MODIFIED = Long.MIN_VALUE;
35 private static Map<File, ZipFileIndex> zipFileIndexCache = new HashMap<File, ZipFileIndex>();
36 private static ReentrantLock lock = new ReentrantLock();
38 private static boolean NON_BATCH_MODE = System.getProperty("nonBatchMode") != null;// TODO: Use -XD compiler switch for this.
40 private Map<String, DirectoryEntry> directories = Collections.<String, DirectoryEntry>emptyMap();
41 private Set<String> allDirs = Collections.<String>emptySet();
43 // ZipFileIndex data entries
44 private File zipFile;
45 private long zipFileLastModified = NOT_MODIFIED;
46 private RandomAccessFile zipRandomFile;
47 private ZipFileIndexEntry[] entries;
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;
56 private boolean usePreindexedCache = false;
57 private String preindexedCacheLocation = null;
59 private boolean writeIndex = false;
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 }
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());
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 }
97 public boolean isOpen() {
98 lock.lock();
99 try {
100 return zipRandomFile != null;
101 }
102 finally {
103 lock.unlock();
104 }
105 }
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);
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 }
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 }
135 public static void clearCache() {
136 lock.lock();
137 try {
138 zipFileIndexCache.clear();
139 }
140 finally {
141 lock.unlock();
142 }
143 }
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 }
166 public static void removeFromCache(File file) {
167 lock.lock();
168 try {
169 zipFileIndexCache.remove(file);
170 }
171 finally {
172 lock.unlock();
173 }
174 }
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 }
186 for (ZipFileIndex zfi : indexes) {
187 zipFileIndexCache.put(zfi.zipFile, zfi);
188 }
189 }
190 finally {
191 lock.unlock();
192 }
193 }
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;
203 if (zipFile != null) {
204 this.zipFileLastModified = zipFile.lastModified();
205 }
207 // Validate integrity of the zip file
208 checkIndex();
209 }
211 public String toString() {
212 return "ZipFileIndex of file:(" + zipFile + ")";
213 }
215 // Just in case...
216 protected void finalize() {
217 closeFile();
218 }
220 private boolean isUpToDate() {
221 if (zipFile != null &&
222 ((!NON_BATCH_MODE) || zipFileLastModified == zipFile.lastModified()) &&
223 hasPopulatedData) {
224 return true;
225 }
227 return false;
228 }
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 }
241 if (zipRandomFile != null || isUpToDate) {
242 lastReferenceTimeStamp = System.currentTimeMillis();
243 return;
244 }
246 hasPopulatedData = true;
248 if (readIndex()) {
249 lastReferenceTimeStamp = System.currentTimeMillis();
250 return;
251 }
253 directories = Collections.<String, DirectoryEntry>emptyMap();
254 allDirs = Collections.<String>emptySet();
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 }
267 lastReferenceTimeStamp = System.currentTimeMillis();
268 }
270 private void openFile() throws FileNotFoundException {
271 if (zipRandomFile == null && zipFile != null) {
272 zipRandomFile = new RandomAccessFile(zipFile, "r");
273 }
274 }
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 }
284 public void close() {
285 lock.lock();
286 try {
287 writeIndex();
288 closeFile();
289 }
290 finally {
291 lock.unlock();
292 }
293 }
295 private void closeFile() {
296 if (zipRandomFile != null) {
297 try {
298 zipRandomFile.close();
299 } catch (IOException ex) {
300 }
301 zipRandomFile = null;
302 }
303 }
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 }
322 DirectoryEntry de = directories.get(noSeparator ? "" : path.substring(0, lastSepIndex));
324 lookFor = path.substring(noSeparator ? 0 : lastSepIndex + 1);
326 return de == null ? null : de.getEntry(lookFor);
327 }
328 catch (IOException e) {
329 return null;
330 }
331 finally {
332 lock.unlock();
333 }
334 }
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 }
344 lock.lock();
345 try {
346 checkIndex();
348 DirectoryEntry de = directories.get(path);
349 com.sun.tools.javac.util.List<String> ret = de == null ? null : de.getFiles();
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 }
364 public List<String> getAllDirectories(String path) {
366 if (File.separatorChar != '/') {
367 path = path.replace('/', File.separatorChar);
368 }
370 lock.lock();
371 try {
372 checkIndex();
373 path = path.intern();
375 DirectoryEntry de = directories.get(path);
376 com.sun.tools.javac.util.List<String> ret = de == null ? null : de.getDirectories();
378 if (ret == null) {
379 return com.sun.tools.javac.util.List.<String>nil();
380 }
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 }
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 }
403 allDirs = alldirs;
404 }
406 return allDirs;
407 }
408 catch (IOException e) {
409 return Collections.<String>emptySet();
410 }
411 finally {
412 lock.unlock();
413 }
414 }
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 }
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 }
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 }
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 }
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();
476 if (entry.isDir) {
477 return 0;
478 }
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 }
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 }
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 }
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 }
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 }
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);
551 // is this compressed - offset 8 in the ZipEntry header
552 if (get2ByteLittleEndian(header, 8) == 0)
553 return cbuf;
555 int size = entry.size;
556 byte[] buf = new byte[size];
557 if (inflate(cbuf, buf) != size)
558 throw new ZipException("corrupted zip file");
560 return buf;
561 }
563 /**
564 *
565 */
566 private int readBytes(ZipFileIndexEntry entry, byte[] buffer) throws IOException {
567 byte[] header = getHeader(entry);
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 }
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);
588 int count = inflate(cbuf, buffer);
589 if (count == -1)
590 throw new ZipException("corrupted zip file");
592 return entry.size;
593 }
595 //----------------------------------------------------------------------------
596 // Zip utilities
597 //----------------------------------------------------------------------------
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 }
610 /*
611 * Inflate using the java.util.zip.Inflater class
612 */
613 private static Inflater inflater;
614 private int inflate(byte[] src, byte[] dest) {
616 // construct the inflater object or reuse an existing one
617 if (inflater == null)
618 inflater = new Inflater(true);
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 }
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 }
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 }
647 /* ----------------------------------------------------------------------------
648 * ZipDirectory
649 * ----------------------------------------------------------------------------*/
651 private class ZipDirectory {
652 private String lastDir;
653 private int lastStart;
654 private int lastLen;
656 byte[] zipDir;
657 RandomAccessFile zipRandomFile = null;
658 ZipFileIndex zipFileIndex = null;
660 public ZipDirectory(RandomAccessFile zipRandomFile, long start, long end, ZipFileIndex index) throws IOException {
661 this.zipRandomFile = zipRandomFile;
662 this.zipFileIndex = index;
664 findCENRecord(start, end);
665 }
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;
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 }
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);
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 }
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 }
730 entries = entryList.toArray(new ZipFileIndexEntry[entryList.size()]);
731 Arrays.sort(entries);
732 } else {
733 cleanupState();
734 }
735 }
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 }
743 int dirStart = pos + 46;
744 int fileStart = dirStart;
745 int fileEnd = fileStart + get2ByteLittleEndian(zipDir, pos + 28);
747 if (zipFileIndex.symbolFilePrefixLength != 0 &&
748 ((fileEnd - fileStart) >= symbolFilePrefixLength)) {
749 dirStart += zipFileIndex.symbolFilePrefixLength;
750 fileStart += zipFileIndex.symbolFilePrefixLength;
751 }
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 }
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 }
776 // Sub directories
777 if (directory == null) {
778 lastStart = dirStart;
779 lastLen = fileStart - dirStart - 1;
781 directory = new String(zipDir, dirStart, lastLen, "UTF-8").intern();
782 lastDir = directory;
784 // Enter also all the parent directories
785 String tempDirectory = directory;
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 }
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"));
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 }
814 return pos + 46 +
815 get2ByteLittleEndian(zipDir, pos + 28) +
816 get2ByteLittleEndian(zipDir, pos + 30) +
817 get2ByteLittleEndian(zipDir, pos + 32);
818 }
819 }
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 }
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;
845 private long writtenOffsetOffset = 0;
847 private String dirName;
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();
853 private List<ZipFileIndexEntry> entries = new ArrayList<ZipFileIndexEntry>();
855 private ZipFileIndex zipFileIndex;
857 private int numEntries;
859 DirectoryEntry(String dirName, ZipFileIndex index) {
860 filesInited = false;
861 directoriesInited = false;
862 entriesInited = false;
864 if (File.separatorChar == '/') {
865 dirName.replace('\\', '/');
866 }
867 else {
868 dirName.replace('/', '\\');
869 }
871 this.dirName = dirName.intern();
872 this.zipFileIndex = index;
873 }
875 private com.sun.tools.javac.util.List<String> getFiles() {
876 if (filesInited) {
877 return zipFileEntriesFiles;
878 }
880 initEntries();
882 for (ZipFileIndexEntry e : entries) {
883 if (!e.isDir) {
884 zipFileEntriesFiles = zipFileEntriesFiles.append(e.name);
885 }
886 }
887 filesInited = true;
888 return zipFileEntriesFiles;
889 }
891 private com.sun.tools.javac.util.List<String> getDirectories() {
892 if (directoriesInited) {
893 return zipFileEntriesFiles;
894 }
896 initEntries();
898 for (ZipFileIndexEntry e : entries) {
899 if (e.isDir) {
900 zipFileEntriesDirectories = zipFileEntriesDirectories.append(e.name);
901 }
902 }
904 directoriesInited = true;
906 return zipFileEntriesDirectories;
907 }
909 private com.sun.tools.javac.util.List<ZipFileIndexEntry> getEntries() {
910 if (zipFileEntriesInited) {
911 return zipFileEntries;
912 }
914 initEntries();
916 zipFileEntries = com.sun.tools.javac.util.List.nil();
917 for (ZipFileIndexEntry zfie : entries) {
918 zipFileEntries = zipFileEntries.append(zfie);
919 }
921 zipFileEntriesInited = true;
923 return zipFileEntries;
924 }
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 }
933 return entries.get(index);
934 }
936 private void initEntries() {
937 if (entriesInited) {
938 return;
939 }
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;
947 boolean emptyList = false;
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);
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");
967 // Read isDir
968 boolean eIsDir = raf.readByte() == (byte)0 ? false : true;
970 // Read offset of bytes in the real Jar/Zip file
971 int eOffset = raf.readInt();
973 // Read size of the file in the real Jar/Zip file
974 int eSize = raf.readInt();
976 // Read compressed size of the file in the real Jar/Zip file
977 int eCsize = raf.readInt();
979 // Read java time stamp of the file in the real Jar/Zip file
980 long eJavaTimestamp = raf.readLong();
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 }
1004 entriesInited = true;
1005 }
1007 List<ZipFileIndexEntry> getEntriesAsCollection() {
1008 initEntries();
1010 return entries;
1011 }
1012 }
1014 private boolean readIndex() {
1015 if (triedToReadIndex || !usePreindexedCache) {
1016 return false;
1017 }
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");
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);
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 }
1067 return ret;
1068 }
1070 private boolean writeIndex() {
1071 boolean ret = false;
1072 if (readFromIndex || !usePreindexedCache) {
1073 return true;
1074 }
1076 if (!writeIndex) {
1077 return true;
1078 }
1080 File indexFile = getIndexFile();
1081 if (indexFile == null) {
1082 return false;
1083 }
1085 RandomAccessFile raf = null;
1086 long writtenSoFar = 0;
1087 try {
1088 raf = new RandomAccessFile(indexFile, "rw");
1090 raf.writeLong(zipFileLastModified);
1091 writtenSoFar += 8;
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;
1100 while(iterDirName.hasNext()) {
1101 String dirName = iterDirName.next();
1102 DirectoryEntry dirEntry = directories.get(dirName);
1104 directoriesToWrite.add(dirEntry);
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;
1112 raf.write(dirNameBytes);
1113 writtenSoFar += dirNameBytesLen;
1115 // Write the number of files in the dir
1116 List dirEntries = dirEntry.getEntriesAsCollection();
1117 raf.writeInt(dirEntries.size());
1118 writtenSoFar += 4;
1120 offsets.put(dirName, new Long(writtenSoFar));
1122 // Write the offset of the file's data in the dir
1123 dirEntry.writtenOffsetOffset = 0L;
1124 raf.writeLong(0L);
1125 writtenSoFar += 8;
1126 }
1128 for (DirectoryEntry de : directoriesToWrite) {
1129 // Fix up the offset in the directory table
1130 long currFP = raf.getFilePointer();
1132 long offsetOffset = offsets.get(de.dirName).longValue();
1133 raf.seek(offsetOffset);
1134 raf.writeLong(writtenSoFar);
1136 raf.seek(currFP);
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;
1149 // Write isDir
1150 raf.writeByte(zfie.isDir ? (byte)1 : (byte)0);
1151 writtenSoFar += 1;
1153 // Write offset of bytes in the real Jar/Zip file
1154 raf.writeInt(zfie.offset);
1155 writtenSoFar += 4;
1157 // Write size of the file in the real Jar/Zip file
1158 raf.writeInt(zfie.size);
1159 writtenSoFar += 4;
1161 // Write compressed size of the file in the real Jar/Zip file
1162 raf.writeInt(zfie.compressedSize);
1163 writtenSoFar += 4;
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 }
1182 return ret;
1183 }
1185 public boolean writeZipIndex() {
1186 lock.lock();
1187 try {
1188 return writeIndex();
1189 }
1190 finally {
1191 lock.unlock();
1192 }
1193 }
1195 private File getIndexFile() {
1196 if (zipIndexFile == null) {
1197 if (zipFile == null) {
1198 return null;
1199 }
1201 zipIndexFile = new File((preindexedCacheLocation == null ? "" : preindexedCacheLocation) +
1202 zipFile.getName() + ".index");
1203 }
1205 return zipIndexFile;
1206 }
1208 public File getZipFile() {
1209 return zipFile;
1210 }
1211 }