1 /* 2 * Copyright (c) 2007, 2012, 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.LinkedHashMap; 40 import java.util.HashMap; 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 "{@code -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 final 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 isOpen()117 public synchronized boolean isOpen() { 118 return (zipRandomFile != null); 119 } 120 ZipFileIndex(File zipFile, RelativeDirectory symbolFilePrefix, boolean writeIndex, boolean useCache, String cacheLocation)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 toString()140 public String toString() { 141 return "ZipFileIndex[" + zipFile + "]"; 142 } 143 144 // Just in case... 145 @Override finalize()146 protected void finalize() throws Throwable { 147 closeFile(); 148 super.finalize(); 149 } 150 isUpToDate()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 */ checkIndex()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 openFile()201 private void openFile() throws FileNotFoundException { 202 if (zipRandomFile == null && zipFile != null) { 203 zipRandomFile = new RandomAccessFile(zipFile, "r"); 204 } 205 } 206 cleanupState()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 close()215 public synchronized void close() { 216 writeIndex(); 217 closeFile(); 218 } 219 closeFile()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 */ getZipIndexEntry(RelativePath path)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 */ getFiles(RelativeDirectory path)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 getDirectories(RelativeDirectory path)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 getAllDirectories()283 public synchronized Set<RelativeDirectory> getAllDirectories() { 284 try { 285 checkIndex(); 286 if (allDirs == Collections.EMPTY_SET) { 287 allDirs = new java.util.LinkedHashSet<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 */ contains(RelativePath path)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 isDirectory(RelativePath path)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 getLastModified(RelativeFile path)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 length(RelativeFile path)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 read(RelativeFile path)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 read(Entry entry)357 synchronized byte[] read(Entry entry) throws IOException { 358 openFile(); 359 byte[] result = readBytes(entry); 360 closeFile(); 361 return result; 362 } 363 read(RelativeFile path, byte[] buffer)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 read(Entry entry, byte[] buffer)371 synchronized int read(Entry entry, byte[] buffer) 372 throws IOException { 373 int result = readBytes(entry, buffer); 374 return result; 375 } 376 readBytes(Entry entry)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 */ readBytes(Entry entry, byte[] buffer)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 getHeader(Entry entry)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; inflate(byte[] src, byte[] dest)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 */ get2ByteLittleEndian(byte[] buf, int pos)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 */ get4ByteLittleEndian(byte[] buf, int pos)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 ZipDirectory(RandomAccessFile zipRandomFile, long start, long end, ZipFileIndex index)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 */ hasValidHeader()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 */ findCENRecord(long start, long end)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)]; 552 int sz = get4ByteLittleEndian(endbuf, i + 16); 553 // a negative offset or the entries field indicates a 554 // potential zip64 archive 555 if (sz < 0 || get2ByteLittleEndian(endbuf, i + 10) == 0xffff) { 556 throw new ZipFormatException("detected a zip64 archive"); 557 } 558 zipRandomFile.seek(start + sz); 559 zipRandomFile.readFully(zipDir, 0, zipDir.length); 560 return; 561 } else { 562 endbufend = endbufpos + 21; 563 } 564 } 565 throw new ZipException("cannot read zip file"); 566 } 567 buildIndex()568 private void buildIndex() throws IOException { 569 int len = zipDir.length; 570 571 // Add each of the files 572 if (len > 0) { 573 directories = new LinkedHashMap<RelativeDirectory, DirectoryEntry>(); 574 ArrayList<Entry> entryList = new ArrayList<Entry>(); 575 for (int pos = 0; pos < len; ) { 576 pos = readEntry(pos, entryList, directories); 577 } 578 579 // Add the accumulated dirs into the same list 580 for (RelativeDirectory d: directories.keySet()) { 581 // use shared RelativeDirectory objects for parent dirs 582 RelativeDirectory parent = getRelativeDirectory(d.dirname().getPath()); 583 String file = d.basename(); 584 Entry zipFileIndexEntry = new Entry(parent, file); 585 zipFileIndexEntry.isDir = true; 586 entryList.add(zipFileIndexEntry); 587 } 588 589 entries = entryList.toArray(new Entry[entryList.size()]); 590 Arrays.sort(entries); 591 } else { 592 cleanupState(); 593 } 594 } 595 readEntry(int pos, List<Entry> entryList, Map<RelativeDirectory, DirectoryEntry> directories)596 private int readEntry(int pos, List<Entry> entryList, 597 Map<RelativeDirectory, DirectoryEntry> directories) throws IOException { 598 if (get4ByteLittleEndian(zipDir, pos) != 0x02014b50) { 599 throw new ZipException("cannot read zip file entry"); 600 } 601 602 int dirStart = pos + 46; 603 int fileStart = dirStart; 604 int fileEnd = fileStart + get2ByteLittleEndian(zipDir, pos + 28); 605 606 if (zipFileIndex.symbolFilePrefixLength != 0 && 607 ((fileEnd - fileStart) >= symbolFilePrefixLength)) { 608 dirStart += zipFileIndex.symbolFilePrefixLength; 609 fileStart += zipFileIndex.symbolFilePrefixLength; 610 } 611 // Force any '\' to '/'. Keep the position of the last separator. 612 for (int index = fileStart; index < fileEnd; index++) { 613 byte nextByte = zipDir[index]; 614 if (nextByte == (byte)'\\') { 615 zipDir[index] = (byte)'/'; 616 fileStart = index + 1; 617 } else if (nextByte == (byte)'/') { 618 fileStart = index + 1; 619 } 620 } 621 622 RelativeDirectory directory = null; 623 if (fileStart == dirStart) 624 directory = getRelativeDirectory(""); 625 else if (lastDir != null && lastLen == fileStart - dirStart - 1) { 626 int index = lastLen - 1; 627 while (zipDir[lastStart + index] == zipDir[dirStart + index]) { 628 if (index == 0) { 629 directory = lastDir; 630 break; 631 } 632 index--; 633 } 634 } 635 636 // Sub directories 637 if (directory == null) { 638 lastStart = dirStart; 639 lastLen = fileStart - dirStart - 1; 640 641 directory = getRelativeDirectory(new String(zipDir, dirStart, lastLen, "UTF-8")); 642 lastDir = directory; 643 644 // Enter also all the parent directories 645 RelativeDirectory tempDirectory = directory; 646 647 while (directories.get(tempDirectory) == null) { 648 directories.put(tempDirectory, new DirectoryEntry(tempDirectory, zipFileIndex)); 649 if (tempDirectory.path.indexOf("/") == tempDirectory.path.length() - 1) 650 break; 651 else { 652 // use shared RelativeDirectory objects for parent dirs 653 tempDirectory = getRelativeDirectory(tempDirectory.dirname().getPath()); 654 } 655 } 656 } 657 else { 658 if (directories.get(directory) == null) { 659 directories.put(directory, new DirectoryEntry(directory, zipFileIndex)); 660 } 661 } 662 663 // For each dir create also a file 664 if (fileStart != fileEnd) { 665 Entry entry = new Entry(directory, 666 new String(zipDir, fileStart, fileEnd - fileStart, "UTF-8")); 667 668 entry.setNativeTime(get4ByteLittleEndian(zipDir, pos + 12)); 669 entry.compressedSize = get4ByteLittleEndian(zipDir, pos + 20); 670 entry.size = get4ByteLittleEndian(zipDir, pos + 24); 671 entry.offset = get4ByteLittleEndian(zipDir, pos + 42); 672 entryList.add(entry); 673 } 674 675 return pos + 46 + 676 get2ByteLittleEndian(zipDir, pos + 28) + 677 get2ByteLittleEndian(zipDir, pos + 30) + 678 get2ByteLittleEndian(zipDir, pos + 32); 679 } 680 } 681 682 /** 683 * Returns the last modified timestamp of a zip file. 684 * @return long 685 */ getZipFileLastModified()686 public long getZipFileLastModified() throws IOException { 687 synchronized (this) { 688 checkIndex(); 689 return zipFileLastModified; 690 } 691 } 692 693 /** ------------------------------------------------------------------------ 694 * DirectoryEntry class 695 * -------------------------------------------------------------------------*/ 696 697 static class DirectoryEntry { 698 private boolean filesInited; 699 private boolean directoriesInited; 700 private boolean zipFileEntriesInited; 701 private boolean entriesInited; 702 703 private long writtenOffsetOffset = 0; 704 705 private RelativeDirectory dirName; 706 707 private com.sun.tools.javac.util.List<String> zipFileEntriesFiles = com.sun.tools.javac.util.List.<String>nil(); 708 private com.sun.tools.javac.util.List<String> zipFileEntriesDirectories = com.sun.tools.javac.util.List.<String>nil(); 709 private com.sun.tools.javac.util.List<Entry> zipFileEntries = com.sun.tools.javac.util.List.<Entry>nil(); 710 711 private List<Entry> entries = new ArrayList<Entry>(); 712 713 private ZipFileIndex zipFileIndex; 714 715 private int numEntries; 716 DirectoryEntry(RelativeDirectory dirName, ZipFileIndex index)717 DirectoryEntry(RelativeDirectory dirName, ZipFileIndex index) { 718 filesInited = false; 719 directoriesInited = false; 720 entriesInited = false; 721 722 this.dirName = dirName; 723 this.zipFileIndex = index; 724 } 725 getFiles()726 private com.sun.tools.javac.util.List<String> getFiles() { 727 if (!filesInited) { 728 initEntries(); 729 for (Entry e : entries) { 730 if (!e.isDir) { 731 zipFileEntriesFiles = zipFileEntriesFiles.append(e.name); 732 } 733 } 734 filesInited = true; 735 } 736 return zipFileEntriesFiles; 737 } 738 getDirectories()739 private com.sun.tools.javac.util.List<String> getDirectories() { 740 if (!directoriesInited) { 741 initEntries(); 742 for (Entry e : entries) { 743 if (e.isDir) { 744 zipFileEntriesDirectories = zipFileEntriesDirectories.append(e.name); 745 } 746 } 747 directoriesInited = true; 748 } 749 return zipFileEntriesDirectories; 750 } 751 getEntries()752 private com.sun.tools.javac.util.List<Entry> getEntries() { 753 if (!zipFileEntriesInited) { 754 initEntries(); 755 zipFileEntries = com.sun.tools.javac.util.List.nil(); 756 for (Entry zfie : entries) { 757 zipFileEntries = zipFileEntries.append(zfie); 758 } 759 zipFileEntriesInited = true; 760 } 761 return zipFileEntries; 762 } 763 getEntry(String rootName)764 private Entry getEntry(String rootName) { 765 initEntries(); 766 int index = Collections.binarySearch(entries, new Entry(dirName, rootName)); 767 if (index < 0) { 768 return null; 769 } 770 771 return entries.get(index); 772 } 773 initEntries()774 private void initEntries() { 775 if (entriesInited) { 776 return; 777 } 778 779 if (!zipFileIndex.readFromIndex) { 780 int from = -Arrays.binarySearch(zipFileIndex.entries, 781 new Entry(dirName, ZipFileIndex.MIN_CHAR)) - 1; 782 int to = -Arrays.binarySearch(zipFileIndex.entries, 783 new Entry(dirName, MAX_CHAR)) - 1; 784 785 for (int i = from; i < to; i++) { 786 entries.add(zipFileIndex.entries[i]); 787 } 788 } else { 789 File indexFile = zipFileIndex.getIndexFile(); 790 if (indexFile != null) { 791 RandomAccessFile raf = null; 792 try { 793 raf = new RandomAccessFile(indexFile, "r"); 794 raf.seek(writtenOffsetOffset); 795 796 for (int nFiles = 0; nFiles < numEntries; nFiles++) { 797 // Read the name bytes 798 int zfieNameBytesLen = raf.readInt(); 799 byte [] zfieNameBytes = new byte[zfieNameBytesLen]; 800 raf.read(zfieNameBytes); 801 String eName = new String(zfieNameBytes, "UTF-8"); 802 803 // Read isDir 804 boolean eIsDir = raf.readByte() == (byte)0 ? false : true; 805 806 // Read offset of bytes in the real Jar/Zip file 807 int eOffset = raf.readInt(); 808 809 // Read size of the file in the real Jar/Zip file 810 int eSize = raf.readInt(); 811 812 // Read compressed size of the file in the real Jar/Zip file 813 int eCsize = raf.readInt(); 814 815 // Read java time stamp of the file in the real Jar/Zip file 816 long eJavaTimestamp = raf.readLong(); 817 818 Entry rfie = new Entry(dirName, eName); 819 rfie.isDir = eIsDir; 820 rfie.offset = eOffset; 821 rfie.size = eSize; 822 rfie.compressedSize = eCsize; 823 rfie.javatime = eJavaTimestamp; 824 entries.add(rfie); 825 } 826 } catch (Throwable t) { 827 // Do nothing 828 } finally { 829 try { 830 if (raf != null) { 831 raf.close(); 832 } 833 } catch (Throwable t) { 834 // Do nothing 835 } 836 } 837 } 838 } 839 840 entriesInited = true; 841 } 842 getEntriesAsCollection()843 List<Entry> getEntriesAsCollection() { 844 initEntries(); 845 846 return entries; 847 } 848 } 849 readIndex()850 private boolean readIndex() { 851 if (triedToReadIndex || !usePreindexedCache) { 852 return false; 853 } 854 855 boolean ret = false; 856 synchronized (this) { 857 triedToReadIndex = true; 858 RandomAccessFile raf = null; 859 try { 860 File indexFileName = getIndexFile(); 861 raf = new RandomAccessFile(indexFileName, "r"); 862 863 long fileStamp = raf.readLong(); 864 if (zipFile.lastModified() != fileStamp) { 865 ret = false; 866 } else { 867 directories = new LinkedHashMap<RelativeDirectory, DirectoryEntry>(); 868 int numDirs = raf.readInt(); 869 for (int nDirs = 0; nDirs < numDirs; nDirs++) { 870 int dirNameBytesLen = raf.readInt(); 871 byte [] dirNameBytes = new byte[dirNameBytesLen]; 872 raf.read(dirNameBytes); 873 874 RelativeDirectory dirNameStr = getRelativeDirectory(new String(dirNameBytes, "UTF-8")); 875 DirectoryEntry de = new DirectoryEntry(dirNameStr, this); 876 de.numEntries = raf.readInt(); 877 de.writtenOffsetOffset = raf.readLong(); 878 directories.put(dirNameStr, de); 879 } 880 ret = true; 881 zipFileLastModified = fileStamp; 882 } 883 } catch (Throwable t) { 884 // Do nothing 885 } finally { 886 if (raf != null) { 887 try { 888 raf.close(); 889 } catch (Throwable tt) { 890 // Do nothing 891 } 892 } 893 } 894 if (ret == true) { 895 readFromIndex = true; 896 } 897 } 898 899 return ret; 900 } 901 writeIndex()902 private boolean writeIndex() { 903 boolean ret = false; 904 if (readFromIndex || !usePreindexedCache) { 905 return true; 906 } 907 908 if (!writeIndex) { 909 return true; 910 } 911 912 File indexFile = getIndexFile(); 913 if (indexFile == null) { 914 return false; 915 } 916 917 RandomAccessFile raf = null; 918 long writtenSoFar = 0; 919 try { 920 raf = new RandomAccessFile(indexFile, "rw"); 921 922 raf.writeLong(zipFileLastModified); 923 writtenSoFar += 8; 924 925 List<DirectoryEntry> directoriesToWrite = new ArrayList<DirectoryEntry>(); 926 Map<RelativeDirectory, Long> offsets = new HashMap<RelativeDirectory, Long>(); 927 raf.writeInt(directories.keySet().size()); 928 writtenSoFar += 4; 929 930 for (RelativeDirectory dirName: directories.keySet()) { 931 DirectoryEntry dirEntry = directories.get(dirName); 932 933 directoriesToWrite.add(dirEntry); 934 935 // Write the dir name bytes 936 byte [] dirNameBytes = dirName.getPath().getBytes("UTF-8"); 937 int dirNameBytesLen = dirNameBytes.length; 938 raf.writeInt(dirNameBytesLen); 939 writtenSoFar += 4; 940 941 raf.write(dirNameBytes); 942 writtenSoFar += dirNameBytesLen; 943 944 // Write the number of files in the dir 945 List<Entry> dirEntries = dirEntry.getEntriesAsCollection(); 946 raf.writeInt(dirEntries.size()); 947 writtenSoFar += 4; 948 949 offsets.put(dirName, new Long(writtenSoFar)); 950 951 // Write the offset of the file's data in the dir 952 dirEntry.writtenOffsetOffset = 0L; 953 raf.writeLong(0L); 954 writtenSoFar += 8; 955 } 956 957 for (DirectoryEntry de : directoriesToWrite) { 958 // Fix up the offset in the directory table 959 long currFP = raf.getFilePointer(); 960 961 long offsetOffset = offsets.get(de.dirName).longValue(); 962 raf.seek(offsetOffset); 963 raf.writeLong(writtenSoFar); 964 965 raf.seek(currFP); 966 967 // Now write each of the files in the DirectoryEntry 968 List<Entry> list = de.getEntriesAsCollection(); 969 for (Entry zfie : list) { 970 // Write the name bytes 971 byte [] zfieNameBytes = zfie.name.getBytes("UTF-8"); 972 int zfieNameBytesLen = zfieNameBytes.length; 973 raf.writeInt(zfieNameBytesLen); 974 writtenSoFar += 4; 975 raf.write(zfieNameBytes); 976 writtenSoFar += zfieNameBytesLen; 977 978 // Write isDir 979 raf.writeByte(zfie.isDir ? (byte)1 : (byte)0); 980 writtenSoFar += 1; 981 982 // Write offset of bytes in the real Jar/Zip file 983 raf.writeInt(zfie.offset); 984 writtenSoFar += 4; 985 986 // Write size of the file in the real Jar/Zip file 987 raf.writeInt(zfie.size); 988 writtenSoFar += 4; 989 990 // Write compressed size of the file in the real Jar/Zip file 991 raf.writeInt(zfie.compressedSize); 992 writtenSoFar += 4; 993 994 // Write java time stamp of the file in the real Jar/Zip file 995 raf.writeLong(zfie.getLastModified()); 996 writtenSoFar += 8; 997 } 998 } 999 } catch (Throwable t) { 1000 // Do nothing 1001 } finally { 1002 try { 1003 if (raf != null) { 1004 raf.close(); 1005 } 1006 } catch(IOException ioe) { 1007 // Do nothing 1008 } 1009 } 1010 1011 return ret; 1012 } 1013 writeZipIndex()1014 public boolean writeZipIndex() { 1015 synchronized (this) { 1016 return writeIndex(); 1017 } 1018 } 1019 getIndexFile()1020 private File getIndexFile() { 1021 if (zipIndexFile == null) { 1022 if (zipFile == null) { 1023 return null; 1024 } 1025 1026 zipIndexFile = new File((preindexedCacheLocation == null ? "" : preindexedCacheLocation) + 1027 zipFile.getName() + ".index"); 1028 } 1029 1030 return zipIndexFile; 1031 } 1032 getZipFile()1033 public File getZipFile() { 1034 return zipFile; 1035 } 1036 getAbsoluteFile()1037 File getAbsoluteFile() { 1038 File absFile = (absFileRef == null ? null : absFileRef.get()); 1039 if (absFile == null) { 1040 absFile = zipFile.getAbsoluteFile(); 1041 absFileRef = new SoftReference<File>(absFile); 1042 } 1043 return absFile; 1044 } 1045 getRelativeDirectory(String path)1046 private RelativeDirectory getRelativeDirectory(String path) { 1047 RelativeDirectory rd; 1048 SoftReference<RelativeDirectory> ref = relativeDirectoryCache.get(path); 1049 if (ref != null) { 1050 rd = ref.get(); 1051 if (rd != null) 1052 return rd; 1053 } 1054 rd = new RelativeDirectory(path); 1055 relativeDirectoryCache.put(path, new SoftReference<RelativeDirectory>(rd)); 1056 return rd; 1057 } 1058 1059 static class Entry implements Comparable<Entry> { 1060 public static final Entry[] EMPTY_ARRAY = {}; 1061 1062 // Directory related 1063 RelativeDirectory dir; 1064 boolean isDir; 1065 1066 // File related 1067 String name; 1068 1069 int offset; 1070 int size; 1071 int compressedSize; 1072 long javatime; 1073 1074 private int nativetime; 1075 Entry(RelativePath path)1076 public Entry(RelativePath path) { 1077 this(path.dirname(), path.basename()); 1078 } 1079 Entry(RelativeDirectory directory, String name)1080 public Entry(RelativeDirectory directory, String name) { 1081 this.dir = directory; 1082 this.name = name; 1083 } 1084 getName()1085 public String getName() { 1086 return new RelativeFile(dir, name).getPath(); 1087 } 1088 getFileName()1089 public String getFileName() { 1090 return name; 1091 } 1092 getLastModified()1093 public long getLastModified() { 1094 if (javatime == 0) { 1095 javatime = dosToJavaTime(nativetime); 1096 } 1097 return javatime; 1098 } 1099 1100 // based on dosToJavaTime in java.util.Zip, but avoiding the 1101 // use of deprecated Date constructor dosToJavaTime(int dtime)1102 private static long dosToJavaTime(int dtime) { 1103 Calendar c = Calendar.getInstance(); 1104 c.set(Calendar.YEAR, ((dtime >> 25) & 0x7f) + 1980); 1105 c.set(Calendar.MONTH, ((dtime >> 21) & 0x0f) - 1); 1106 c.set(Calendar.DATE, ((dtime >> 16) & 0x1f)); 1107 c.set(Calendar.HOUR_OF_DAY, ((dtime >> 11) & 0x1f)); 1108 c.set(Calendar.MINUTE, ((dtime >> 5) & 0x3f)); 1109 c.set(Calendar.SECOND, ((dtime << 1) & 0x3e)); 1110 c.set(Calendar.MILLISECOND, 0); 1111 return c.getTimeInMillis(); 1112 } 1113 setNativeTime(int natTime)1114 void setNativeTime(int natTime) { 1115 nativetime = natTime; 1116 } 1117 isDirectory()1118 public boolean isDirectory() { 1119 return isDir; 1120 } 1121 compareTo(Entry other)1122 public int compareTo(Entry other) { 1123 RelativeDirectory otherD = other.dir; 1124 if (dir != otherD) { 1125 int c = dir.compareTo(otherD); 1126 if (c != 0) 1127 return c; 1128 } 1129 return name.compareTo(other.name); 1130 } 1131 1132 @Override equals(Object o)1133 public boolean equals(Object o) { 1134 if (!(o instanceof Entry)) 1135 return false; 1136 Entry other = (Entry) o; 1137 return dir.equals(other.dir) && name.equals(other.name); 1138 } 1139 1140 @Override hashCode()1141 public int hashCode() { 1142 int hash = 7; 1143 hash = 97 * hash + (this.dir != null ? this.dir.hashCode() : 0); 1144 hash = 97 * hash + (this.name != null ? this.name.hashCode() : 0); 1145 return hash; 1146 } 1147 1148 @Override toString()1149 public String toString() { 1150 return isDir ? ("Dir:" + dir + " : " + name) : 1151 (dir + ":" + name); 1152 } 1153 } 1154 1155 /* 1156 * Exception primarily used to implement a failover, used exclusively here. 1157 */ 1158 1159 static final class ZipFormatException extends IOException { 1160 private static final long serialVersionUID = 8000196834066748623L; ZipFormatException(String message)1161 protected ZipFormatException(String message) { 1162 super(message); 1163 } 1164 ZipFormatException(String message, Throwable cause)1165 protected ZipFormatException(String message, Throwable cause) { 1166 super(message, cause); 1167 } 1168 } 1169 } 1170