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