001 /*
002 * Copyright 2007-2008 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package com.sun.tools.javac.file;
027
028
029 import java.io.File;
030 import java.io.FileNotFoundException;
031 import java.io.IOException;
032 import java.io.RandomAccessFile;
033 import java.lang.ref.SoftReference;
034 import java.util.ArrayList;
035 import java.util.Arrays;
036 import java.util.Calendar;
037 import java.util.Collections;
038 import java.util.HashMap;
039 import java.util.HashSet;
040 import java.util.Iterator;
041 import java.util.List;
042 import java.util.Map;
043 import java.util.Set;
044 import java.util.concurrent.locks.ReentrantLock;
045 import java.util.zip.DataFormatException;
046 import java.util.zip.Inflater;
047 import java.util.zip.ZipException;
048
049 import com.sun.tools.javac.file.RelativePath.RelativeDirectory;
050 import com.sun.tools.javac.file.RelativePath.RelativeFile;
051
052 /** This class implements building of index of a zip archive and access to it's context.
053 * It also uses prebuild index if available. It supports invocations where it will
054 * serialize an optimized zip index file to disk.
055 *
056 * In oreder to use secondary index file make sure the option "usezipindex" is in the Options object,
057 * when JavacFileManager is invoked. (You can pass "-XDusezipindex" on the command line.
058 *
059 * Location where to look for/generate optimized zip index files can be provided using
060 * "-XDcachezipindexdir=<directory>". If this flag is not provided, the dfault location is
061 * the value of the "java.io.tmpdir" system property.
062 *
063 * If key "-XDwritezipindexfiles" is specified, there will be new optimized index file
064 * created for each archive, used by the compiler for compilation, at location,
065 * specified by "cachezipindexdir" option.
066 *
067 * If nonBatchMode option is specified (-XDnonBatchMode) the compiler will use timestamp
068 * checking to reindex the zip files if it is needed. In batch mode the timestamps are not checked
069 * and the compiler uses the cached indexes.
070 */
071 public class ZipFileIndex {
072 private static final String MIN_CHAR = String.valueOf(Character.MIN_VALUE);
073 private static final String MAX_CHAR = String.valueOf(Character.MAX_VALUE);
074
075 public final static long NOT_MODIFIED = Long.MIN_VALUE;
076
077 private static Map<File, ZipFileIndex> zipFileIndexCache = new HashMap<File, ZipFileIndex>();
078 private static ReentrantLock lock = new ReentrantLock();
079
080 private static boolean NON_BATCH_MODE = System.getProperty("nonBatchMode") != null;// TODO: Use -XD compiler switch for this.
081
082 private Map<RelativeDirectory, DirectoryEntry> directories = Collections.<RelativeDirectory, DirectoryEntry>emptyMap();
083 private Set<RelativeDirectory> allDirs = Collections.<RelativeDirectory>emptySet();
084
085 // ZipFileIndex data entries
086 private File zipFile;
087 private long zipFileLastModified = NOT_MODIFIED;
088 private RandomAccessFile zipRandomFile;
089 private Entry[] entries;
090
091 private boolean readFromIndex = false;
092 private File zipIndexFile = null;
093 private boolean triedToReadIndex = false;
094 final RelativeDirectory symbolFilePrefix;
095 private int symbolFilePrefixLength = 0;
096 private boolean hasPopulatedData = false;
097 private long lastReferenceTimeStamp = NOT_MODIFIED;
098
099 private boolean usePreindexedCache = false;
100 private String preindexedCacheLocation = null;
101
102 private boolean writeIndex = false;
103
104 private Map <String, SoftReference<RelativeDirectory>> relativeDirectoryCache =
105 new HashMap<String, SoftReference<RelativeDirectory>>();
106
107 /**
108 * Returns a list of all ZipFileIndex entries
109 *
110 * @return A list of ZipFileIndex entries, or an empty list
111 */
112 public static List<ZipFileIndex> getZipFileIndexes() {
113 return getZipFileIndexes(false);
114 }
115
116 /**
117 * Returns a list of all ZipFileIndex entries
118 *
119 * @param openedOnly If true it returns a list of only opened ZipFileIndex entries, otherwise
120 * all ZipFileEntry(s) are included into the list.
121 * @return A list of ZipFileIndex entries, or an empty list
122 */
123 public static List<ZipFileIndex> getZipFileIndexes(boolean openedOnly) {
124 List<ZipFileIndex> zipFileIndexes = new ArrayList<ZipFileIndex>();
125 lock.lock();
126 try {
127 zipFileIndexes.addAll(zipFileIndexCache.values());
128
129 if (openedOnly) {
130 for(ZipFileIndex elem : zipFileIndexes) {
131 if (!elem.isOpen()) {
132 zipFileIndexes.remove(elem);
133 }
134 }
135 }
136 }
137 finally {
138 lock.unlock();
139 }
140 return zipFileIndexes;
141 }
142
143 public boolean isOpen() {
144 lock.lock();
145 try {
146 return zipRandomFile != null;
147 }
148 finally {
149 lock.unlock();
150 }
151 }
152
153 public static ZipFileIndex getZipFileIndex(File zipFile,
154 RelativeDirectory symbolFilePrefix,
155 boolean useCache, String cacheLocation,
156 boolean writeIndex) throws IOException {
157 ZipFileIndex zi = null;
158 lock.lock();
159 try {
160 zi = getExistingZipIndex(zipFile);
161
162 if (zi == null || (zi != null && zipFile.lastModified() != zi.zipFileLastModified)) {
163 zi = new ZipFileIndex(zipFile, symbolFilePrefix, writeIndex,
164 useCache, cacheLocation);
165 zipFileIndexCache.put(zipFile, zi);
166 }
167 }
168 finally {
169 lock.unlock();
170 }
171 return zi;
172 }
173
174 public static ZipFileIndex getExistingZipIndex(File zipFile) {
175 lock.lock();
176 try {
177 return zipFileIndexCache.get(zipFile);
178 }
179 finally {
180 lock.unlock();
181 }
182 }
183
184 public static void clearCache() {
185 lock.lock();
186 try {
187 zipFileIndexCache.clear();
188 }
189 finally {
190 lock.unlock();
191 }
192 }
193
194 public static void clearCache(long timeNotUsed) {
195 lock.lock();
196 try {
197 Iterator<File> cachedFileIterator = zipFileIndexCache.keySet().iterator();
198 while (cachedFileIterator.hasNext()) {
199 File cachedFile = cachedFileIterator.next();
200 ZipFileIndex cachedZipIndex = zipFileIndexCache.get(cachedFile);
201 if (cachedZipIndex != null) {
202 long timeToTest = cachedZipIndex.lastReferenceTimeStamp + timeNotUsed;
203 if (timeToTest < cachedZipIndex.lastReferenceTimeStamp || // Overflow...
204 System.currentTimeMillis() > timeToTest) {
205 zipFileIndexCache.remove(cachedFile);
206 }
207 }
208 }
209 }
210 finally {
211 lock.unlock();
212 }
213 }
214
215 public static void removeFromCache(File file) {
216 lock.lock();
217 try {
218 zipFileIndexCache.remove(file);
219 }
220 finally {
221 lock.unlock();
222 }
223 }
224
225 /** Sets already opened list of ZipFileIndexes from an outside client
226 * of the compiler. This functionality should be used in a non-batch clients of the compiler.
227 */
228 public static void setOpenedIndexes(List<ZipFileIndex>indexes) throws IllegalStateException {
229 lock.lock();
230 try {
231 if (zipFileIndexCache.isEmpty()) {
232 throw new IllegalStateException("Setting opened indexes should be called only when the ZipFileCache is empty. Call JavacFileManager.flush() before calling this method.");
233 }
234
235 for (ZipFileIndex zfi : indexes) {
236 zipFileIndexCache.put(zfi.zipFile, zfi);
237 }
238 }
239 finally {
240 lock.unlock();
241 }
242 }
243
244 private ZipFileIndex(File zipFile, RelativeDirectory symbolFilePrefix, boolean writeIndex,
245 boolean useCache, String cacheLocation) throws IOException {
246 this.zipFile = zipFile;
247 this.symbolFilePrefix = symbolFilePrefix;
248 this.symbolFilePrefixLength = (symbolFilePrefix == null ? 0 :
249 symbolFilePrefix.getPath().getBytes("UTF-8").length);
250 this.writeIndex = writeIndex;
251 this.usePreindexedCache = useCache;
252 this.preindexedCacheLocation = cacheLocation;
253
254 if (zipFile != null) {
255 this.zipFileLastModified = zipFile.lastModified();
256 }
257
258 // Validate integrity of the zip file
259 checkIndex();
260 }
261
262 public String toString() {
263 return "ZipFileIndex[" + zipFile + "]";
264 }
265
266 // Just in case...
267 protected void finalize() {
268 closeFile();
269 }
270
271 private boolean isUpToDate() {
272 if (zipFile != null &&
273 ((!NON_BATCH_MODE) || zipFileLastModified == zipFile.lastModified()) &&
274 hasPopulatedData) {
275 return true;
276 }
277
278 return false;
279 }
280
281 /**
282 * Here we need to make sure that the ZipFileIndex is valid. Check the timestamp of the file and
283 * if its the same as the one at the time the index was build we don't need to reopen anything.
284 */
285 private void checkIndex() throws IOException {
286 boolean isUpToDate = true;
287 if (!isUpToDate()) {
288 closeFile();
289 isUpToDate = false;
290 }
291
292 if (zipRandomFile != null || isUpToDate) {
293 lastReferenceTimeStamp = System.currentTimeMillis();
294 return;
295 }
296
297 hasPopulatedData = true;
298
299 if (readIndex()) {
300 lastReferenceTimeStamp = System.currentTimeMillis();
301 return;
302 }
303
304 directories = Collections.<RelativeDirectory, DirectoryEntry>emptyMap();
305 allDirs = Collections.<RelativeDirectory>emptySet();
306
307 try {
308 openFile();
309 long totalLength = zipRandomFile.length();
310 ZipDirectory directory = new ZipDirectory(zipRandomFile, 0L, totalLength, this);
311 directory.buildIndex();
312 } finally {
313 if (zipRandomFile != null) {
314 closeFile();
315 }
316 }
317
318 lastReferenceTimeStamp = System.currentTimeMillis();
319 }
320
321 private void openFile() throws FileNotFoundException {
322 if (zipRandomFile == null && zipFile != null) {
323 zipRandomFile = new RandomAccessFile(zipFile, "r");
324 }
325 }
326
327 private void cleanupState() {
328 // Make sure there is a valid but empty index if the file doesn't exist
329 entries = Entry.EMPTY_ARRAY;
330 directories = Collections.<RelativeDirectory, DirectoryEntry>emptyMap();
331 zipFileLastModified = NOT_MODIFIED;
332 allDirs = Collections.<RelativeDirectory>emptySet();
333 }
334
335 public void close() {
336 lock.lock();
337 try {
338 writeIndex();
339 closeFile();
340 }
341 finally {
342 lock.unlock();
343 }
344 }
345
346 private void closeFile() {
347 if (zipRandomFile != null) {
348 try {
349 zipRandomFile.close();
350 } catch (IOException ex) {
351 }
352 zipRandomFile = null;
353 }
354 }
355
356 /**
357 * Returns the ZipFileIndexEntry for an absolute path, if there is one.
358 */
359 Entry getZipIndexEntry(RelativePath path) {
360 lock.lock();
361 try {
362 checkIndex();
363 DirectoryEntry de = directories.get(path.dirname());
364 String lookFor = path.basename();
365 return de == null ? null : de.getEntry(lookFor);
366 }
367 catch (IOException e) {
368 return null;
369 }
370 finally {
371 lock.unlock();
372 }
373 }
374
375 /**
376 * Returns a javac List of filenames within an absolute path in the ZipFileIndex.
377 */
378 public com.sun.tools.javac.util.List<String> getFiles(RelativeDirectory path) {
379 lock.lock();
380 try {
381 checkIndex();
382
383 DirectoryEntry de = directories.get(path);
384 com.sun.tools.javac.util.List<String> ret = de == null ? null : de.getFiles();
385
386 if (ret == null) {
387 return com.sun.tools.javac.util.List.<String>nil();
388 }
389 return ret;
390 }
391 catch (IOException e) {
392 return com.sun.tools.javac.util.List.<String>nil();
393 }
394 finally {
395 lock.unlock();
396 }
397 }
398
399 public List<String> getDirectories(RelativeDirectory path) {
400 lock.lock();
401 try {
402 checkIndex();
403
404 DirectoryEntry de = directories.get(path);
405 com.sun.tools.javac.util.List<String> ret = de == null ? null : de.getDirectories();
406
407 if (ret == null) {
408 return com.sun.tools.javac.util.List.<String>nil();
409 }
410
411 return ret;
412 }
413 catch (IOException e) {
414 return com.sun.tools.javac.util.List.<String>nil();
415 }
416 finally {
417 lock.unlock();
418 }
419 }
420
421 public Set<RelativeDirectory> getAllDirectories() {
422 lock.lock();
423 try {
424 checkIndex();
425 if (allDirs == Collections.EMPTY_SET) {
426 allDirs = new HashSet<RelativeDirectory>(directories.keySet());
427 }
428
429 return allDirs;
430 }
431 catch (IOException e) {
432 return Collections.<RelativeDirectory>emptySet();
433 }
434 finally {
435 lock.unlock();
436 }
437 }
438
439 /**
440 * Tests if a specific path exists in the zip. This method will return true
441 * for file entries and directories.
442 *
443 * @param path A path within the zip.
444 * @return True if the path is a file or dir, false otherwise.
445 */
446 public boolean contains(RelativePath path) {
447 lock.lock();
448 try {
449 checkIndex();
450 return getZipIndexEntry(path) != null;
451 }
452 catch (IOException e) {
453 return false;
454 }
455 finally {
456 lock.unlock();
457 }
458 }
459
460 public boolean isDirectory(RelativePath path) throws IOException {
461 lock.lock();
462 try {
463 // The top level in a zip file is always a directory.
464 if (path.getPath().length() == 0) {
465 lastReferenceTimeStamp = System.currentTimeMillis();
466 return true;
467 }
468
469 checkIndex();
470 return directories.get(path) != null;
471 }
472 finally {
473 lock.unlock();
474 }
475 }
476
477 public long getLastModified(RelativeFile path) throws IOException {
478 lock.lock();
479 try {
480 Entry entry = getZipIndexEntry(path);
481 if (entry == null)
482 throw new FileNotFoundException();
483 return entry.getLastModified();
484 }
485 finally {
486 lock.unlock();
487 }
488 }
489
490 public int length(RelativeFile path) throws IOException {
491 lock.lock();
492 try {
493 Entry entry = getZipIndexEntry(path);
494 if (entry == null)
495 throw new FileNotFoundException();
496
497 if (entry.isDir) {
498 return 0;
499 }
500
501 byte[] header = getHeader(entry);
502 // entry is not compressed?
503 if (get2ByteLittleEndian(header, 8) == 0) {
504 return entry.compressedSize;
505 } else {
506 return entry.size;
507 }
508 }
509 finally {
510 lock.unlock();
511 }
512 }
513
514 public byte[] read(RelativeFile path) throws IOException {
515 lock.lock();
516 try {
517 Entry entry = getZipIndexEntry(path);
518 if (entry == null)
519 throw new FileNotFoundException("Path not found in ZIP: " + path.path);
520 return read(entry);
521 }
522 finally {
523 lock.unlock();
524 }
525 }
526
527 byte[] read(Entry entry) throws IOException {
528 lock.lock();
529 try {
530 openFile();
531 byte[] result = readBytes(entry);
532 closeFile();
533 return result;
534 }
535 finally {
536 lock.unlock();
537 }
538 }
539
540 public int read(RelativeFile path, byte[] buffer) throws IOException {
541 lock.lock();
542 try {
543 Entry entry = getZipIndexEntry(path);
544 if (entry == null)
545 throw new FileNotFoundException();
546 return read(entry, buffer);
547 }
548 finally {
549 lock.unlock();
550 }
551 }
552
553 int read(Entry entry, byte[] buffer)
554 throws IOException {
555 lock.lock();
556 try {
557 int result = readBytes(entry, buffer);
558 return result;
559 }
560 finally {
561 lock.unlock();
562 }
563 }
564
565 private byte[] readBytes(Entry entry) throws IOException {
566 byte[] header = getHeader(entry);
567 int csize = entry.compressedSize;
568 byte[] cbuf = new byte[csize];
569 zipRandomFile.skipBytes(get2ByteLittleEndian(header, 26) + get2ByteLittleEndian(header, 28));
570 zipRandomFile.readFully(cbuf, 0, csize);
571
572 // is this compressed - offset 8 in the ZipEntry header
573 if (get2ByteLittleEndian(header, 8) == 0)
574 return cbuf;
575
576 int size = entry.size;
577 byte[] buf = new byte[size];
578 if (inflate(cbuf, buf) != size)
579 throw new ZipException("corrupted zip file");
580
581 return buf;
582 }
583
584 /**
585 *
586 */
587 private int readBytes(Entry entry, byte[] buffer) throws IOException {
588 byte[] header = getHeader(entry);
589
590 // entry is not compressed?
591 if (get2ByteLittleEndian(header, 8) == 0) {
592 zipRandomFile.skipBytes(get2ByteLittleEndian(header, 26) + get2ByteLittleEndian(header, 28));
593 int offset = 0;
594 int size = buffer.length;
595 while (offset < size) {
596 int count = zipRandomFile.read(buffer, offset, size - offset);
597 if (count == -1)
598 break;
599 offset += count;
600 }
601 return entry.size;
602 }
603
604 int csize = entry.compressedSize;
605 byte[] cbuf = new byte[csize];
606 zipRandomFile.skipBytes(get2ByteLittleEndian(header, 26) + get2ByteLittleEndian(header, 28));
607 zipRandomFile.readFully(cbuf, 0, csize);
608
609 int count = inflate(cbuf, buffer);
610 if (count == -1)
611 throw new ZipException("corrupted zip file");
612
613 return entry.size;
614 }
615
616 //----------------------------------------------------------------------------
617 // Zip utilities
618 //----------------------------------------------------------------------------
619
620 private byte[] getHeader(Entry entry) throws IOException {
621 zipRandomFile.seek(entry.offset);
622 byte[] header = new byte[30];
623 zipRandomFile.readFully(header);
624 if (get4ByteLittleEndian(header, 0) != 0x04034b50)
625 throw new ZipException("corrupted zip file");
626 if ((get2ByteLittleEndian(header, 6) & 1) != 0)
627 throw new ZipException("encrypted zip file"); // offset 6 in the header of the ZipFileEntry
628 return header;
629 }
630
631 /*
632 * Inflate using the java.util.zip.Inflater class
633 */
634 private static Inflater inflater;
635 private int inflate(byte[] src, byte[] dest) {
636
637 // construct the inflater object or reuse an existing one
638 if (inflater == null)
639 inflater = new Inflater(true);
640
641 synchronized (inflater) {
642 inflater.reset();
643 inflater.setInput(src);
644 try {
645 return inflater.inflate(dest);
646 } catch (DataFormatException ex) {
647 return -1;
648 }
649 }
650 }
651
652 /**
653 * return the two bytes buf[pos], buf[pos+1] as an unsigned integer in little
654 * endian format.
655 */
656 private static int get2ByteLittleEndian(byte[] buf, int pos) {
657 return (buf[pos] & 0xFF) + ((buf[pos+1] & 0xFF) << 8);
658 }
659
660 /**
661 * return the 4 bytes buf[i..i+3] as an integer in little endian format.
662 */
663 private static int get4ByteLittleEndian(byte[] buf, int pos) {
664 return (buf[pos] & 0xFF) + ((buf[pos + 1] & 0xFF) << 8) +
665 ((buf[pos + 2] & 0xFF) << 16) + ((buf[pos + 3] & 0xFF) << 24);
666 }
667
668 /* ----------------------------------------------------------------------------
669 * ZipDirectory
670 * ----------------------------------------------------------------------------*/
671
672 private class ZipDirectory {
673 private RelativeDirectory lastDir;
674 private int lastStart;
675 private int lastLen;
676
677 byte[] zipDir;
678 RandomAccessFile zipRandomFile = null;
679 ZipFileIndex zipFileIndex = null;
680
681 public ZipDirectory(RandomAccessFile zipRandomFile, long start, long end, ZipFileIndex index) throws IOException {
682 this.zipRandomFile = zipRandomFile;
683 this.zipFileIndex = index;
684
685 findCENRecord(start, end);
686 }
687
688 /*
689 * Reads zip file central directory.
690 * For more details see readCEN in zip_util.c from the JDK sources.
691 * This is a Java port of that function.
692 */
693 private void findCENRecord(long start, long end) throws IOException {
694 long totalLength = end - start;
695 int endbuflen = 1024;
696 byte[] endbuf = new byte[endbuflen];
697 long endbufend = end - start;
698
699 // There is a variable-length field after the dir offset record. We need to do consequential search.
700 while (endbufend >= 22) {
701 if (endbufend < endbuflen)
702 endbuflen = (int)endbufend;
703 long endbufpos = endbufend - endbuflen;
704 zipRandomFile.seek(start + endbufpos);
705 zipRandomFile.readFully(endbuf, 0, endbuflen);
706 int i = endbuflen - 22;
707 while (i >= 0 &&
708 !(endbuf[i] == 0x50 &&
709 endbuf[i + 1] == 0x4b &&
710 endbuf[i + 2] == 0x05 &&
711 endbuf[i + 3] == 0x06 &&
712 endbufpos + i + 22 +
713 get2ByteLittleEndian(endbuf, i + 20) == totalLength)) {
714 i--;
715 }
716
717 if (i >= 0) {
718 zipDir = new byte[get4ByteLittleEndian(endbuf, i + 12) + 2];
719 zipDir[0] = endbuf[i + 10];
720 zipDir[1] = endbuf[i + 11];
721 zipRandomFile.seek(start + get4ByteLittleEndian(endbuf, i + 16));
722 zipRandomFile.readFully(zipDir, 2, zipDir.length - 2);
723 return;
724 } else {
725 endbufend = endbufpos + 21;
726 }
727 }
728 throw new ZipException("cannot read zip file");
729 }
730
731 private void buildIndex() throws IOException {
732 int entryCount = get2ByteLittleEndian(zipDir, 0);
733
734 // Add each of the files
735 if (entryCount > 0) {
736 directories = new HashMap<RelativeDirectory, DirectoryEntry>();
737 ArrayList<Entry> entryList = new ArrayList<Entry>();
738 int pos = 2;
739 for (int i = 0; i < entryCount; i++) {
740 pos = readEntry(pos, entryList, directories);
741 }
742
743 // Add the accumulated dirs into the same list
744 for (RelativeDirectory d: directories.keySet()) {
745 // use shared RelativeDirectory objects for parent dirs
746 RelativeDirectory parent = getRelativeDirectory(d.dirname().getPath());
747 String file = d.basename();
748 Entry zipFileIndexEntry = new Entry(parent, file);
749 zipFileIndexEntry.isDir = true;
750 entryList.add(zipFileIndexEntry);
751 }
752
753 entries = entryList.toArray(new Entry[entryList.size()]);
754 Arrays.sort(entries);
755 } else {
756 cleanupState();
757 }
758 }
759
760 private int readEntry(int pos, List<Entry> entryList,
761 Map<RelativeDirectory, DirectoryEntry> directories) throws IOException {
762 if (get4ByteLittleEndian(zipDir, pos) != 0x02014b50) {
763 throw new ZipException("cannot read zip file entry");
764 }
765
766 int dirStart = pos + 46;
767 int fileStart = dirStart;
768 int fileEnd = fileStart + get2ByteLittleEndian(zipDir, pos + 28);
769
770 if (zipFileIndex.symbolFilePrefixLength != 0 &&
771 ((fileEnd - fileStart) >= symbolFilePrefixLength)) {
772 dirStart += zipFileIndex.symbolFilePrefixLength;
773 fileStart += zipFileIndex.symbolFilePrefixLength;
774 }
775 // Force any '\' to '/'. Keep the position of the last separator.
776 for (int index = fileStart; index < fileEnd; index++) {
777 byte nextByte = zipDir[index];
778 if (nextByte == (byte)'\\') {
779 zipDir[index] = (byte)'/';
780 fileStart = index + 1;
781 } else if (nextByte == (byte)'/') {
782 fileStart = index + 1;
783 }
784 }
785
786 RelativeDirectory directory = null;
787 if (fileStart == dirStart)
788 directory = getRelativeDirectory("");
789 else if (lastDir != null && lastLen == fileStart - dirStart - 1) {
790 int index = lastLen - 1;
791 while (zipDir[lastStart + index] == zipDir[dirStart + index]) {
792 if (index == 0) {
793 directory = lastDir;
794 break;
795 }
796 index--;
797 }
798 }
799
800 // Sub directories
801 if (directory == null) {
802 lastStart = dirStart;
803 lastLen = fileStart - dirStart - 1;
804
805 directory = getRelativeDirectory(new String(zipDir, dirStart, lastLen, "UTF-8"));
806 lastDir = directory;
807
808 // Enter also all the parent directories
809 RelativeDirectory tempDirectory = directory;
810
811 while (directories.get(tempDirectory) == null) {
812 directories.put(tempDirectory, new DirectoryEntry(tempDirectory, zipFileIndex));
813 if (tempDirectory.path.indexOf("/") == tempDirectory.path.length() - 1)
814 break;
815 else {
816 // use shared RelativeDirectory objects for parent dirs
817 tempDirectory = getRelativeDirectory(tempDirectory.dirname().getPath());
818 }
819 }
820 }
821 else {
822 if (directories.get(directory) == null) {
823 directories.put(directory, new DirectoryEntry(directory, zipFileIndex));
824 }
825 }
826
827 // For each dir create also a file
828 if (fileStart != fileEnd) {
829 Entry entry = new Entry(directory,
830 new String(zipDir, fileStart, fileEnd - fileStart, "UTF-8"));
831
832 entry.setNativeTime(get4ByteLittleEndian(zipDir, pos + 12));
833 entry.compressedSize = get4ByteLittleEndian(zipDir, pos + 20);
834 entry.size = get4ByteLittleEndian(zipDir, pos + 24);
835 entry.offset = get4ByteLittleEndian(zipDir, pos + 42);
836 entryList.add(entry);
837 }
838
839 return pos + 46 +
840 get2ByteLittleEndian(zipDir, pos + 28) +
841 get2ByteLittleEndian(zipDir, pos + 30) +
842 get2ByteLittleEndian(zipDir, pos + 32);
843 }
844 }
845
846 /**
847 * Returns the last modified timestamp of a zip file.
848 * @return long
849 */
850 public long getZipFileLastModified() throws IOException {
851 lock.lock();
852 try {
853 checkIndex();
854 return zipFileLastModified;
855 }
856 finally {
857 lock.unlock();
858 }
859 }
860
861 /** ------------------------------------------------------------------------
862 * DirectoryEntry class
863 * -------------------------------------------------------------------------*/
864
865 static class DirectoryEntry {
866 private boolean filesInited;
867 private boolean directoriesInited;
868 private boolean zipFileEntriesInited;
869 private boolean entriesInited;
870
871 private long writtenOffsetOffset = 0;
872
873 private RelativeDirectory dirName;
874
875 private com.sun.tools.javac.util.List<String> zipFileEntriesFiles = com.sun.tools.javac.util.List.<String>nil();
876 private com.sun.tools.javac.util.List<String> zipFileEntriesDirectories = com.sun.tools.javac.util.List.<String>nil();
877 private com.sun.tools.javac.util.List<Entry> zipFileEntries = com.sun.tools.javac.util.List.<Entry>nil();
878
879 private List<Entry> entries = new ArrayList<Entry>();
880
881 private ZipFileIndex zipFileIndex;
882
883 private int numEntries;
884
885 DirectoryEntry(RelativeDirectory dirName, ZipFileIndex index) {
886 filesInited = false;
887 directoriesInited = false;
888 entriesInited = false;
889
890 this.dirName = dirName;
891 this.zipFileIndex = index;
892 }
893
894 private com.sun.tools.javac.util.List<String> getFiles() {
895 if (!filesInited) {
896 initEntries();
897 for (Entry e : entries) {
898 if (!e.isDir) {
899 zipFileEntriesFiles = zipFileEntriesFiles.append(e.name);
900 }
901 }
902 filesInited = true;
903 }
904 return zipFileEntriesFiles;
905 }
906
907 private com.sun.tools.javac.util.List<String> getDirectories() {
908 if (!directoriesInited) {
909 initEntries();
910 for (Entry e : entries) {
911 if (e.isDir) {
912 zipFileEntriesDirectories = zipFileEntriesDirectories.append(e.name);
913 }
914 }
915 directoriesInited = true;
916 }
917 return zipFileEntriesDirectories;
918 }
919
920 private com.sun.tools.javac.util.List<Entry> getEntries() {
921 if (!zipFileEntriesInited) {
922 initEntries();
923 zipFileEntries = com.sun.tools.javac.util.List.nil();
924 for (Entry zfie : entries) {
925 zipFileEntries = zipFileEntries.append(zfie);
926 }
927 zipFileEntriesInited = true;
928 }
929 return zipFileEntries;
930 }
931
932 private Entry getEntry(String rootName) {
933 initEntries();
934 int index = Collections.binarySearch(entries, new Entry(dirName, rootName));
935 if (index < 0) {
936 return null;
937 }
938
939 return entries.get(index);
940 }
941
942 private void initEntries() {
943 if (entriesInited) {
944 return;
945 }
946
947 if (!zipFileIndex.readFromIndex) {
948 int from = -Arrays.binarySearch(zipFileIndex.entries,
949 new Entry(dirName, ZipFileIndex.MIN_CHAR)) - 1;
950 int to = -Arrays.binarySearch(zipFileIndex.entries,
951 new Entry(dirName, MAX_CHAR)) - 1;
952
953 for (int i = from; i < to; i++) {
954 entries.add(zipFileIndex.entries[i]);
955 }
956 } else {
957 File indexFile = zipFileIndex.getIndexFile();
958 if (indexFile != null) {
959 RandomAccessFile raf = null;
960 try {
961 raf = new RandomAccessFile(indexFile, "r");
962 raf.seek(writtenOffsetOffset);
963
964 for (int nFiles = 0; nFiles < numEntries; nFiles++) {
965 // Read the name bytes
966 int zfieNameBytesLen = raf.readInt();
967 byte [] zfieNameBytes = new byte[zfieNameBytesLen];
968 raf.read(zfieNameBytes);
969 String eName = new String(zfieNameBytes, "UTF-8");
970
971 // Read isDir
972 boolean eIsDir = raf.readByte() == (byte)0 ? false : true;
973
974 // Read offset of bytes in the real Jar/Zip file
975 int eOffset = raf.readInt();
976
977 // Read size of the file in the real Jar/Zip file
978 int eSize = raf.readInt();
979
980 // Read compressed size of the file in the real Jar/Zip file
981 int eCsize = raf.readInt();
982
983 // Read java time stamp of the file in the real Jar/Zip file
984 long eJavaTimestamp = raf.readLong();
985
986 Entry rfie = new Entry(dirName, eName);
987 rfie.isDir = eIsDir;
988 rfie.offset = eOffset;
989 rfie.size = eSize;
990 rfie.compressedSize = eCsize;
991 rfie.javatime = eJavaTimestamp;
992 entries.add(rfie);
993 }
994 } catch (Throwable t) {
995 // Do nothing
996 } finally {
997 try {
998 if (raf == null) {
999 raf.close();
1000 }
1001 } catch (Throwable t) {
1002 // Do nothing
1003 }
1004 }
1005 }
1006 }
1007
1008 entriesInited = true;
1009 }
1010
1011 List<Entry> getEntriesAsCollection() {
1012 initEntries();
1013
1014 return entries;
1015 }
1016 }
1017
1018 private boolean readIndex() {
1019 if (triedToReadIndex || !usePreindexedCache) {
1020 return false;
1021 }
1022
1023 boolean ret = false;
1024 lock.lock();
1025 try {
1026 triedToReadIndex = true;
1027 RandomAccessFile raf = null;
1028 try {
1029 File indexFileName = getIndexFile();
1030 raf = new RandomAccessFile(indexFileName, "r");
1031
1032 long fileStamp = raf.readLong();
1033 if (zipFile.lastModified() != fileStamp) {
1034 ret = false;
1035 } else {
1036 directories = new HashMap<RelativeDirectory, DirectoryEntry>();
1037 int numDirs = raf.readInt();
1038 for (int nDirs = 0; nDirs < numDirs; nDirs++) {
1039 int dirNameBytesLen = raf.readInt();
1040 byte [] dirNameBytes = new byte[dirNameBytesLen];
1041 raf.read(dirNameBytes);
1042
1043 RelativeDirectory dirNameStr = getRelativeDirectory(new String(dirNameBytes, "UTF-8"));
1044 DirectoryEntry de = new DirectoryEntry(dirNameStr, this);
1045 de.numEntries = raf.readInt();
1046 de.writtenOffsetOffset = raf.readLong();
1047 directories.put(dirNameStr, de);
1048 }
1049 ret = true;
1050 zipFileLastModified = fileStamp;
1051 }
1052 } catch (Throwable t) {
1053 // Do nothing
1054 } finally {
1055 if (raf != null) {
1056 try {
1057 raf.close();
1058 } catch (Throwable tt) {
1059 // Do nothing
1060 }
1061 }
1062 }
1063 if (ret == true) {
1064 readFromIndex = true;
1065 }
1066 }
1067 finally {
1068 lock.unlock();
1069 }
1070
1071 return ret;
1072 }
1073
1074 private boolean writeIndex() {
1075 boolean ret = false;
1076 if (readFromIndex || !usePreindexedCache) {
1077 return true;
1078 }
1079
1080 if (!writeIndex) {
1081 return true;
1082 }
1083
1084 File indexFile = getIndexFile();
1085 if (indexFile == null) {
1086 return false;
1087 }
1088
1089 RandomAccessFile raf = null;
1090 long writtenSoFar = 0;
1091 try {
1092 raf = new RandomAccessFile(indexFile, "rw");
1093
1094 raf.writeLong(zipFileLastModified);
1095 writtenSoFar += 8;
1096
1097 List<DirectoryEntry> directoriesToWrite = new ArrayList<DirectoryEntry>();
1098 Map<RelativeDirectory, Long> offsets = new HashMap<RelativeDirectory, Long>();
1099 raf.writeInt(directories.keySet().size());
1100 writtenSoFar += 4;
1101
1102 for (RelativeDirectory dirName: directories.keySet()) {
1103 DirectoryEntry dirEntry = directories.get(dirName);
1104
1105 directoriesToWrite.add(dirEntry);
1106
1107 // Write the dir name bytes
1108 byte [] dirNameBytes = dirName.getPath().getBytes("UTF-8");
1109 int dirNameBytesLen = dirNameBytes.length;
1110 raf.writeInt(dirNameBytesLen);
1111 writtenSoFar += 4;
1112
1113 raf.write(dirNameBytes);
1114 writtenSoFar += dirNameBytesLen;
1115
1116 // Write the number of files in the dir
1117 List<Entry> dirEntries = dirEntry.getEntriesAsCollection();
1118 raf.writeInt(dirEntries.size());
1119 writtenSoFar += 4;
1120
1121 offsets.put(dirName, new Long(writtenSoFar));
1122
1123 // Write the offset of the file's data in the dir
1124 dirEntry.writtenOffsetOffset = 0L;
1125 raf.writeLong(0L);
1126 writtenSoFar += 8;
1127 }
1128
1129 for (DirectoryEntry de : directoriesToWrite) {
1130 // Fix up the offset in the directory table
1131 long currFP = raf.getFilePointer();
1132
1133 long offsetOffset = offsets.get(de.dirName).longValue();
1134 raf.seek(offsetOffset);
1135 raf.writeLong(writtenSoFar);
1136
1137 raf.seek(currFP);
1138
1139 // Now write each of the files in the DirectoryEntry
1140 List<Entry> entries = de.getEntriesAsCollection();
1141 for (Entry zfie : entries) {
1142 // Write the name bytes
1143 byte [] zfieNameBytes = zfie.name.getBytes("UTF-8");
1144 int zfieNameBytesLen = zfieNameBytes.length;
1145 raf.writeInt(zfieNameBytesLen);
1146 writtenSoFar += 4;
1147 raf.write(zfieNameBytes);
1148 writtenSoFar += zfieNameBytesLen;
1149
1150 // Write isDir
1151 raf.writeByte(zfie.isDir ? (byte)1 : (byte)0);
1152 writtenSoFar += 1;
1153
1154 // Write offset of bytes in the real Jar/Zip file
1155 raf.writeInt(zfie.offset);
1156 writtenSoFar += 4;
1157
1158 // Write size of the file in the real Jar/Zip file
1159 raf.writeInt(zfie.size);
1160 writtenSoFar += 4;
1161
1162 // Write compressed size of the file in the real Jar/Zip file
1163 raf.writeInt(zfie.compressedSize);
1164 writtenSoFar += 4;
1165
1166 // Write java time stamp of the file in the real Jar/Zip file
1167 raf.writeLong(zfie.getLastModified());
1168 writtenSoFar += 8;
1169 }
1170 }
1171 } catch (Throwable t) {
1172 // Do nothing
1173 } finally {
1174 try {
1175 if (raf != null) {
1176 raf.close();
1177 }
1178 } catch(IOException ioe) {
1179 // Do nothing
1180 }
1181 }
1182
1183 return ret;
1184 }
1185
1186 public boolean writeZipIndex() {
1187 lock.lock();
1188 try {
1189 return writeIndex();
1190 }
1191 finally {
1192 lock.unlock();
1193 }
1194 }
1195
1196 private File getIndexFile() {
1197 if (zipIndexFile == null) {
1198 if (zipFile == null) {
1199 return null;
1200 }
1201
1202 zipIndexFile = new File((preindexedCacheLocation == null ? "" : preindexedCacheLocation) +
1203 zipFile.getName() + ".index");
1204 }
1205
1206 return zipIndexFile;
1207 }
1208
1209 public File getZipFile() {
1210 return zipFile;
1211 }
1212
1213 private RelativeDirectory getRelativeDirectory(String path) {
1214 RelativeDirectory rd;
1215 SoftReference<RelativeDirectory> ref = relativeDirectoryCache.get(path);
1216 if (ref != null) {
1217 rd = ref.get();
1218 if (rd != null)
1219 return rd;
1220 }
1221 rd = new RelativeDirectory(path);
1222 relativeDirectoryCache.put(path, new SoftReference<RelativeDirectory>(rd));
1223 return rd;
1224 }
1225
1226 static class Entry implements Comparable<Entry> {
1227 public static final Entry[] EMPTY_ARRAY = {};
1228
1229 // Directory related
1230 RelativeDirectory dir;
1231 boolean isDir;
1232
1233 // File related
1234 String name;
1235
1236 int offset;
1237 int size;
1238 int compressedSize;
1239 long javatime;
1240
1241 private int nativetime;
1242
1243 public Entry(RelativePath path) {
1244 this(path.dirname(), path.basename());
1245 }
1246
1247 public Entry(RelativeDirectory directory, String name) {
1248 this.dir = directory;
1249 this.name = name;
1250 }
1251
1252 public String getName() {
1253 return new RelativeFile(dir, name).getPath();
1254 }
1255
1256 public String getFileName() {
1257 return name;
1258 }
1259
1260 public long getLastModified() {
1261 if (javatime == 0) {
1262 javatime = dosToJavaTime(nativetime);
1263 }
1264 return javatime;
1265 }
1266
1267 // based on dosToJavaTime in java.util.Zip, but avoiding the
1268 // use of deprecated Date constructor
1269 private static long dosToJavaTime(int dtime) {
1270 Calendar c = Calendar.getInstance();
1271 c.set(Calendar.YEAR, ((dtime >> 25) & 0x7f) + 1980);
1272 c.set(Calendar.MONTH, ((dtime >> 21) & 0x0f) - 1);
1273 c.set(Calendar.DATE, ((dtime >> 16) & 0x1f));
1274 c.set(Calendar.HOUR_OF_DAY, ((dtime >> 11) & 0x1f));
1275 c.set(Calendar.MINUTE, ((dtime >> 5) & 0x3f));
1276 c.set(Calendar.SECOND, ((dtime << 1) & 0x3e));
1277 c.set(Calendar.MILLISECOND, 0);
1278 return c.getTimeInMillis();
1279 }
1280
1281 void setNativeTime(int natTime) {
1282 nativetime = natTime;
1283 }
1284
1285 public boolean isDirectory() {
1286 return isDir;
1287 }
1288
1289 public int compareTo(Entry other) {
1290 RelativeDirectory otherD = other.dir;
1291 if (dir != otherD) {
1292 int c = dir.compareTo(otherD);
1293 if (c != 0)
1294 return c;
1295 }
1296 return name.compareTo(other.name);
1297 }
1298
1299 @Override
1300 public boolean equals(Object o) {
1301 if (!(o instanceof Entry))
1302 return false;
1303 Entry other = (Entry) o;
1304 return dir.equals(other.dir) && name.equals(other.name);
1305 }
1306
1307 @Override
1308 public int hashCode() {
1309 int hash = 7;
1310 hash = 97 * hash + (this.dir != null ? this.dir.hashCode() : 0);
1311 hash = 97 * hash + (this.name != null ? this.name.hashCode() : 0);
1312 return hash;
1313 }
1314
1315
1316 public String toString() {
1317 return isDir ? ("Dir:" + dir + " : " + name) :
1318 (dir + ":" + name);
1319 }
1320 }
1321
1322 }