src/share/classes/com/sun/tools/javac/file/ZipFileIndex.java

Fri, 11 Jul 2008 14:59:48 -0700

author
jjg
date
Fri, 11 Jul 2008 14:59:48 -0700
changeset 71
41fb91c70d47
parent 57
aa67a5da66e3
child 103
e571266ae14f
permissions
-rw-r--r--

6725036: javac returns incorrect value for lastModifiedTime() when source is a zip file archive
Reviewed-by: darcy

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

mercurial