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