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