0001: package com.sun.tools.javac.zip;
0002:
0003: import java.io.*;
0004: import java.text.MessageFormat;
0005: import java.util.*;
0006: import java.util.List;
0007: import java.util.concurrent.locks.ReentrantLock;
0008: import java.util.zip.*;
0009:
0010: /** This class implements building of index of a zip archive and access to it's context.
0011: * It also uses prebuild index if available. It supports invocations where it will
0012: * serialize an optimized zip index file to disk.
0013: *
0014: * In oreder to use secondary index file make sure the option "usezipindex" is in the Options object,
0015: * when JavacFileManager is invoked. (You can pass "-XDusezipindex" on the command line.
0016: *
0017: * Location where to look for/generate optimized zip index files can be provided using
0018: * "-XDcachezipindexdir=<directory>". If this flag is not provided, the dfault location is
0019: * the value of the "java.io.tmpdir" system property.
0020: *
0021: * If key "-XDwritezipindexfiles" is specified, there will be new optimized index file
0022: * created for each archive, used by the compiler for compilation, at location,
0023: * specified by "cachezipindexdir" option.
0024: *
0025: * If nonBatchMode option is specified (-XDnonBatchMode) the compiler will use timestamp
0026: * checking to reindex the zip files if it is needed. In batch mode the timestamps are not checked
0027: * and the compiler uses the cached indexes.
0028: */
0029: public class ZipFileIndex {
0030: private static final String MIN_CHAR = String
0031: .valueOf(Character.MIN_VALUE);
0032: private static final String MAX_CHAR = String
0033: .valueOf(Character.MAX_VALUE);
0034:
0035: public final static long NOT_MODIFIED = Long.MIN_VALUE;
0036: private final static com.sun.tools.javac.util.List EMPTY_LIST = com.sun.tools.javac.util.List
0037: .nil();
0038:
0039: private static Map<File, ZipFileIndex> zipFileIndexCache = new HashMap<File, ZipFileIndex>();
0040: private static ReentrantLock lock = new ReentrantLock();
0041:
0042: private static boolean NON_BATCH_MODE = System
0043: .getProperty("nonBatchMode") != null;// TODO: Use -XD compiler switch for this.
0044:
0045: private Map<String, DirectoryEntry> directories = Collections.EMPTY_MAP;
0046: private Set<String> allDirs = Collections.EMPTY_SET;
0047:
0048: // ZipFileIndex data entries
0049: private File zipFile;
0050: private long zipFileLastModified = NOT_MODIFIED;
0051: private RandomAccessFile zipRandomFile;
0052: private ZipFileIndexEntry[] entries;
0053:
0054: private boolean readFromIndex = false;
0055: private File zipIndexFile = null;
0056: private boolean triedToReadIndex = false;
0057: private int symbolFilePrefixLength = 0;
0058: private boolean hasPopulatedData = false;
0059: private long lastReferenceTimeStamp = NOT_MODIFIED;
0060:
0061: private boolean usePreindexedCache = false;
0062: private String preindexedCacheLocation = null;
0063:
0064: private boolean writeIndex = false;
0065:
0066: /**
0067: * Returns a list of all ZipFileIndex entries
0068: *
0069: * @return A list of ZipFileIndex entries, or an empty list
0070: */
0071: public static List<ZipFileIndex> getZipFileIndexes() {
0072: return getZipFileIndexes(false);
0073: }
0074:
0075: /**
0076: * Returns a list of all ZipFileIndex entries
0077: *
0078: * @param openedOnly If true it returns a list of only opened ZipFileIndex entries, otherwise
0079: * all ZipFileEntry(s) are included into the list.
0080: * @return A list of ZipFileIndex entries, or an empty list
0081: */
0082: public static List<ZipFileIndex> getZipFileIndexes(
0083: boolean openedOnly) {
0084: List<ZipFileIndex> zipFileIndexes = new ArrayList<ZipFileIndex>();
0085: lock.lock();
0086: try {
0087: zipFileIndexes.addAll(zipFileIndexCache.values());
0088:
0089: if (openedOnly) {
0090: for (ZipFileIndex elem : zipFileIndexes) {
0091: if (!elem.isOpen()) {
0092: zipFileIndexes.remove(elem);
0093: }
0094: }
0095: }
0096: } finally {
0097: lock.unlock();
0098: }
0099: return zipFileIndexes;
0100: }
0101:
0102: public boolean isOpen() {
0103: lock.lock();
0104: try {
0105: return zipRandomFile != null;
0106: } finally {
0107: lock.unlock();
0108: }
0109: }
0110:
0111: public static ZipFileIndex getZipFileIndex(File zipFile,
0112: int symbolFilePrefixLen, boolean useCache,
0113: String cacheLocation, boolean writeIndex)
0114: throws IOException {
0115: ZipFileIndex zi = null;
0116: lock.lock();
0117: try {
0118: zi = getExistingZipIndex(zipFile);
0119:
0120: if (zi == null
0121: || (zi != null && zipFile.lastModified() != zi.zipFileLastModified)) {
0122: zi = new ZipFileIndex(zipFile);
0123: zi.writeIndex = writeIndex;
0124: zi.symbolFilePrefixLength = symbolFilePrefixLen;
0125: zi.usePreindexedCache = useCache;
0126: zi.preindexedCacheLocation = cacheLocation;
0127: zipFileIndexCache.put(zipFile, zi);
0128: }
0129: } finally {
0130: lock.unlock();
0131: }
0132: return zi;
0133: }
0134:
0135: public static ZipFileIndex getExistingZipIndex(File zipFile) {
0136: lock.lock();
0137: try {
0138: return zipFileIndexCache.get(zipFile);
0139: } finally {
0140: lock.unlock();
0141: }
0142: }
0143:
0144: public static void clearCache() {
0145: lock.lock();
0146: try {
0147: zipFileIndexCache.clear();
0148: } finally {
0149: lock.unlock();
0150: }
0151: }
0152:
0153: public static void clearCache(long timeNotUsed) {
0154: lock.lock();
0155: try {
0156: Iterator<File> cachedFileIterator = zipFileIndexCache
0157: .keySet().iterator();
0158: while (cachedFileIterator.hasNext()) {
0159: File cachedFile = cachedFileIterator.next();
0160: ZipFileIndex cachedZipIndex = zipFileIndexCache
0161: .get(cachedFile);
0162: if (cachedZipIndex != null) {
0163: long timeToTest = cachedZipIndex.lastReferenceTimeStamp
0164: + timeNotUsed;
0165: if (timeToTest < cachedZipIndex.lastReferenceTimeStamp
0166: || // Overflow...
0167: System.currentTimeMillis() > timeToTest) {
0168: zipFileIndexCache.remove(cachedFile);
0169: }
0170: }
0171: }
0172: } finally {
0173: lock.unlock();
0174: }
0175: }
0176:
0177: public static void removeFromCache(File file) {
0178: lock.lock();
0179: try {
0180: zipFileIndexCache.remove(file);
0181: } finally {
0182: lock.unlock();
0183: }
0184: }
0185:
0186: /** Sets already opened list of ZipFileIndexes from an outside client
0187: * of the compiler. This functionality should be used in a non-batch clients of the compiler.
0188: */
0189: public static void setOpenedIndexes(List<ZipFileIndex> indexes)
0190: throws IllegalStateException {
0191: lock.lock();
0192: try {
0193: if (zipFileIndexCache.isEmpty()) {
0194: throw new IllegalStateException(
0195: "Setting opened indexes should be called only when the ZipFileCache is empty. Call JavacFileManager.flush() before calling this method.");
0196: }
0197:
0198: for (ZipFileIndex zfi : indexes) {
0199: zipFileIndexCache.put(zfi.zipFile, zfi);
0200: }
0201: } finally {
0202: lock.unlock();
0203: }
0204: }
0205:
0206: private ZipFileIndex(File zipFile) {
0207: this .zipFile = zipFile;
0208: if (zipFile != null) {
0209: this .zipFileLastModified = zipFile.lastModified();
0210: }
0211: }
0212:
0213: public String toString() {
0214: return "ZipFileIndex of file:(" + zipFile + ")";
0215: }
0216:
0217: // Just in case...
0218: protected void finalize() {
0219: closeFile();
0220: }
0221:
0222: private boolean isUpToDate() {
0223: if (zipFile != null
0224: && ((!NON_BATCH_MODE) || zipFileLastModified == zipFile
0225: .lastModified()) && hasPopulatedData) {
0226: return true;
0227: }
0228:
0229: return false;
0230: }
0231:
0232: /**
0233: * Here we need to make sure that the ZipFileIndex is valid. Check the timestamp of the file and
0234: * if its the same as the one at the time the index was build we don't need to reopen anything.
0235: */
0236: private void checkIndex() {
0237: boolean isUpToDate = true;
0238: if (!isUpToDate()) {
0239: closeFile();
0240: isUpToDate = false;
0241: }
0242:
0243: if (zipRandomFile != null || isUpToDate) {
0244: lastReferenceTimeStamp = System.currentTimeMillis();
0245: return;
0246: }
0247:
0248: hasPopulatedData = true;
0249:
0250: if (readIndex()) {
0251: lastReferenceTimeStamp = System.currentTimeMillis();
0252: return;
0253: }
0254:
0255: directories = Collections.EMPTY_MAP;
0256: allDirs = Collections.EMPTY_SET;
0257:
0258: try {
0259: openFile();
0260: long totalLength = zipRandomFile.length();
0261: ZipDirectory directory = new ZipDirectory(zipRandomFile,
0262: 0L, totalLength, this );
0263: directory.buildIndex();
0264: } catch (IOException ex) {
0265: } finally {
0266: if (zipRandomFile != null) {
0267: closeFile();
0268: }
0269: }
0270:
0271: lastReferenceTimeStamp = System.currentTimeMillis();
0272: }
0273:
0274: private void openFile() throws FileNotFoundException {
0275: if (zipRandomFile == null && zipFile != null) {
0276: zipRandomFile = new RandomAccessFile(zipFile, "r");
0277: }
0278: }
0279:
0280: private void cleanupState() {
0281: // Make sure there is a valid but empty index if the file doesn't exist
0282: entries = ZipFileIndexEntry.EMPTY_ARRAY;
0283: directories = Collections.EMPTY_MAP;
0284: zipFileLastModified = NOT_MODIFIED;
0285: allDirs = Collections.EMPTY_SET;
0286: }
0287:
0288: public void close() {
0289: lock.lock();
0290: try {
0291: writeIndex();
0292: closeFile();
0293: } finally {
0294: lock.unlock();
0295: }
0296: }
0297:
0298: private void closeFile() {
0299: if (zipRandomFile != null) {
0300: try {
0301: zipRandomFile.close();
0302: } catch (IOException ex) {
0303: }
0304: zipRandomFile = null;
0305: }
0306: }
0307:
0308: /**
0309: * Returns the ZipFileIndexEntry for an absolute path, if there is one.
0310: */
0311: public ZipFileIndexEntry getZipIndexEntry(String path) {
0312: if (File.separatorChar != '/') {
0313: path = path.replace('/', File.separatorChar);
0314: }
0315: lock.lock();
0316: try {
0317: checkIndex();
0318: String lookFor = "";
0319: int lastSepIndex = path.lastIndexOf(File.separatorChar);
0320: boolean noSeparator = false;
0321: if (lastSepIndex == -1) {
0322: noSeparator = true;
0323: }
0324:
0325: DirectoryEntry de = directories.get(noSeparator ? "" : path
0326: .substring(0, lastSepIndex));
0327:
0328: lookFor = path
0329: .substring(noSeparator ? 0 : lastSepIndex + 1);
0330:
0331: return de == null ? null : de.getEntry(lookFor);
0332: } finally {
0333: lock.unlock();
0334: }
0335: }
0336:
0337: /**
0338: * Returns a javac List of filenames within an absolute path in the ZipFileIndex.
0339: */
0340: public com.sun.tools.javac.util.List<String> getFiles(String path) {
0341: if (File.separatorChar != '/') {
0342: path = path.replace('/', File.separatorChar);
0343: }
0344:
0345: lock.lock();
0346: try {
0347: checkIndex();
0348:
0349: DirectoryEntry de = directories.get(path);
0350: com.sun.tools.javac.util.List<String> ret = de == null ? null
0351: : de.getFiles();
0352:
0353: if (ret == null) {
0354: return EMPTY_LIST;
0355: }
0356: return ret;
0357: } finally {
0358: lock.unlock();
0359: }
0360: }
0361:
0362: public List<String> getAllDirectories(String path) {
0363:
0364: if (File.separatorChar != '/') {
0365: path = path.replace('/', File.separatorChar);
0366: }
0367:
0368: lock.lock();
0369: try {
0370: checkIndex();
0371: path = path.intern();
0372:
0373: DirectoryEntry de = directories.get(path);
0374: com.sun.tools.javac.util.List<String> ret = de == null ? null
0375: : de.getDirectories();
0376:
0377: if (ret == null) {
0378: return EMPTY_LIST;
0379: }
0380:
0381: return ret;
0382: } finally {
0383: lock.unlock();
0384: }
0385: }
0386:
0387: public Set<String> getAllDirectories() {
0388: lock.lock();
0389: try {
0390: checkIndex();
0391: if (allDirs == Collections.EMPTY_SET) {
0392: Set<String> alldirs = new HashSet<String>();
0393: Iterator<String> dirsIter = directories.keySet()
0394: .iterator();
0395: while (dirsIter.hasNext()) {
0396: alldirs.add(new String(dirsIter.next()));
0397: }
0398:
0399: allDirs = alldirs;
0400: }
0401:
0402: return allDirs;
0403: } finally {
0404: lock.unlock();
0405: }
0406: }
0407:
0408: /**
0409: * Tests if a specific path exists in the zip. This method will return true
0410: * for file entries and directories.
0411: *
0412: * @param path A path within the zip.
0413: * @return True if the path is a file or dir, false otherwise.
0414: */
0415: public boolean contains(String path) {
0416: lock.lock();
0417: try {
0418: checkIndex();
0419: return getZipIndexEntry(path) != null;
0420: } finally {
0421: lock.unlock();
0422: }
0423: }
0424:
0425: public boolean isDirectory(String path) {
0426: lock.lock();
0427: try {
0428: // The top level in a zip file is always a directory.
0429: if (path.length() == 0) {
0430: lastReferenceTimeStamp = System.currentTimeMillis();
0431: return true;
0432: }
0433:
0434: if (File.separatorChar != '/')
0435: path = path.replace('/', File.separatorChar);
0436: checkIndex();
0437: return directories.get(path) != null;
0438: } finally {
0439: lock.unlock();
0440: }
0441: }
0442:
0443: public long getLastModified(String path) throws IOException {
0444: lock.lock();
0445: try {
0446: ZipFileIndexEntry entry = getZipIndexEntry(path);
0447: if (entry == null)
0448: throw new FileNotFoundException();
0449: return entry.getLastModified();
0450: } finally {
0451: lock.unlock();
0452: }
0453: }
0454:
0455: public int length(String path) throws IOException {
0456: lock.lock();
0457: try {
0458: ZipFileIndexEntry entry = getZipIndexEntry(path);
0459: if (entry == null)
0460: throw new FileNotFoundException();
0461:
0462: if (entry.isDir) {
0463: return 0;
0464: }
0465:
0466: byte[] header = getHeader(entry);
0467: // entry is not compressed?
0468: if (get2ByteLittleEndian(header, 8) == 0) {
0469: return entry.compressedSize;
0470: } else {
0471: return entry.size;
0472: }
0473: } finally {
0474: lock.unlock();
0475: }
0476: }
0477:
0478: public byte[] read(String path) throws IOException {
0479: lock.lock();
0480: try {
0481: ZipFileIndexEntry entry = getZipIndexEntry(path);
0482: if (entry == null)
0483: throw new FileNotFoundException(MessageFormat.format(
0484: "Path not found in ZIP: {0}", path));
0485: return read(entry);
0486: } finally {
0487: lock.unlock();
0488: }
0489: }
0490:
0491: public byte[] read(ZipFileIndexEntry entry) throws IOException {
0492: lock.lock();
0493: try {
0494: openFile();
0495: byte[] result = readBytes(entry);
0496: closeFile();
0497: return result;
0498: } finally {
0499: lock.unlock();
0500: }
0501: }
0502:
0503: public int read(String path, byte[] buffer) throws IOException {
0504: lock.lock();
0505: try {
0506: ZipFileIndexEntry entry = getZipIndexEntry(path);
0507: if (entry == null)
0508: throw new FileNotFoundException();
0509: return read(entry, buffer);
0510: } finally {
0511: lock.unlock();
0512: }
0513: }
0514:
0515: public int read(ZipFileIndexEntry entry, byte[] buffer)
0516: throws IOException {
0517: lock.lock();
0518: try {
0519: int result = readBytes(entry, buffer);
0520: return result;
0521: } finally {
0522: lock.unlock();
0523: }
0524: }
0525:
0526: private byte[] readBytes(ZipFileIndexEntry entry)
0527: throws IOException {
0528: byte[] header = getHeader(entry);
0529: int csize = entry.compressedSize;
0530: byte[] cbuf = new byte[csize];
0531: zipRandomFile.skipBytes(get2ByteLittleEndian(header, 26)
0532: + get2ByteLittleEndian(header, 28));
0533: zipRandomFile.readFully(cbuf, 0, csize);
0534:
0535: // is this compressed - offset 8 in the ZipEntry header
0536: if (get2ByteLittleEndian(header, 8) == 0)
0537: return cbuf;
0538:
0539: int size = entry.size;
0540: byte[] buf = new byte[size];
0541: if (inflate(cbuf, buf) != size)
0542: throw new IOException("zip file is corrupted");
0543:
0544: return buf;
0545: }
0546:
0547: /**
0548: *
0549: */
0550: private int readBytes(ZipFileIndexEntry entry, byte[] buffer)
0551: throws IOException {
0552: byte[] header = getHeader(entry);
0553:
0554: // entry is not compressed?
0555: if (get2ByteLittleEndian(header, 8) == 0) {
0556: zipRandomFile.skipBytes(get2ByteLittleEndian(header, 26)
0557: + get2ByteLittleEndian(header, 28));
0558: int offset = 0;
0559: int size = buffer.length;
0560: while (offset < size) {
0561: int count = zipRandomFile.read(buffer, offset, size
0562: - offset);
0563: if (count == -1)
0564: break;
0565: offset += count;
0566: }
0567: return entry.size;
0568: }
0569:
0570: int csize = entry.compressedSize;
0571: byte[] cbuf = new byte[csize];
0572: zipRandomFile.skipBytes(get2ByteLittleEndian(header, 26)
0573: + get2ByteLittleEndian(header, 28));
0574: zipRandomFile.readFully(cbuf, 0, csize);
0575:
0576: int count = inflate(cbuf, buffer);
0577: if (count == -1)
0578: throw new IOException("zip file is corrupted");
0579: return entry.size;
0580: }
0581:
0582: //----------------------------------------------------------------------------
0583: // Zip utilities
0584: //----------------------------------------------------------------------------
0585:
0586: private byte[] getHeader(ZipFileIndexEntry entry)
0587: throws IOException {
0588: zipRandomFile.seek(entry.offset);
0589: byte[] header = new byte[30];
0590: zipRandomFile.readFully(header);
0591: if (get4ByteLittleEndian(header, 0) != 0x04034b50)
0592: throw new IOException("zip file is corrupted");
0593: if ((get2ByteLittleEndian(header, 6) & 1) != 0)
0594: throw new IOException("zip file is encrypted"); // offset 6 in the header of the ZipFileEntry
0595: return header;
0596: }
0597:
0598: /*
0599: * Inflate using the java.util.zip.Inflater class
0600: */
0601: private static Inflater inflater;
0602:
0603: private int inflate(byte[] src, byte[] dest) {
0604:
0605: // construct the inflater object or reuse an existing one
0606: if (inflater == null)
0607: inflater = new Inflater(true);
0608:
0609: synchronized (inflater) {
0610: inflater.reset();
0611: inflater.setInput(src);
0612: try {
0613: return inflater.inflate(dest);
0614: } catch (DataFormatException ex) {
0615: return -1;
0616: }
0617: }
0618: }
0619:
0620: /**
0621: * return the two bytes buf[pos], buf[pos+1] as an unsigned integer in little
0622: * endian format.
0623: */
0624: private static int get2ByteLittleEndian(byte[] buf, int pos) {
0625: return (buf[pos] & 0xFF) + ((buf[pos + 1] & 0xFF) << 8);
0626: }
0627:
0628: /**
0629: * return the 4 bytes buf[i..i+3] as an integer in little endian format.
0630: */
0631: private static int get4ByteLittleEndian(byte[] buf, int pos) {
0632: return (buf[pos] & 0xFF) + ((buf[pos + 1] & 0xFF) << 8)
0633: + ((buf[pos + 2] & 0xFF) << 16)
0634: + ((buf[pos + 3] & 0xFF) << 24);
0635: }
0636:
0637: /* ----------------------------------------------------------------------------
0638: * ZipDirectory
0639: * ----------------------------------------------------------------------------*/
0640:
0641: private class ZipDirectory {
0642: private String lastDir;
0643: private int lastStart;
0644: private int lastLen;
0645:
0646: byte[] zipDir;
0647: RandomAccessFile zipRandomFile = null;
0648: ZipFileIndex zipFileIndex = null;
0649:
0650: public ZipDirectory(RandomAccessFile zipRandomFile, long start,
0651: long end, ZipFileIndex index) throws IOException {
0652: this .zipRandomFile = zipRandomFile;
0653: this .zipFileIndex = index;
0654:
0655: findCENRecord(start, end);
0656: }
0657:
0658: /*
0659: * Reads zip file central directory.
0660: * For more details see readCEN in zip_util.c from the JDK sources.
0661: * This is a Java port of that function.
0662: */
0663: private void findCENRecord(long start, long end)
0664: throws IOException {
0665: long totalLength = end - start;
0666: int endbuflen = 1024;
0667: byte[] endbuf = new byte[endbuflen];
0668: long endbufend = end - start;
0669:
0670: // There is a variable-length field after the dir offset record. We need to do consequential search.
0671: while (endbufend >= 22) {
0672: if (endbufend < endbuflen)
0673: endbuflen = (int) endbufend;
0674: long endbufpos = endbufend - endbuflen;
0675: zipRandomFile.seek(start + endbufpos);
0676: zipRandomFile.readFully(endbuf, 0, endbuflen);
0677: int i = endbuflen - 22;
0678: while (i >= 0
0679: && !(endbuf[i] == 0x50 && endbuf[i + 1] == 0x4b
0680: && endbuf[i + 2] == 0x05
0681: && endbuf[i + 3] == 0x06 && endbufpos
0682: + i + 22
0683: + get2ByteLittleEndian(endbuf, i + 20) == totalLength)) {
0684: i--;
0685: }
0686:
0687: if (i >= 0) {
0688: zipDir = new byte[get4ByteLittleEndian(endbuf,
0689: i + 12) + 2];
0690: zipDir[0] = endbuf[i + 10];
0691: zipDir[1] = endbuf[i + 11];
0692: zipRandomFile.seek(start
0693: + get4ByteLittleEndian(endbuf, i + 16));
0694: zipRandomFile.readFully(zipDir, 2,
0695: zipDir.length - 2);
0696: return;
0697: } else {
0698: endbufend = endbufpos + 21;
0699: }
0700: }
0701: throw new IOException("bad zip file");
0702: }
0703:
0704: private void buildIndex() throws IOException {
0705: int entryCount = get2ByteLittleEndian(zipDir, 0);
0706:
0707: entries = new ZipFileIndexEntry[entryCount];
0708: // Add each of the files
0709: if (entryCount > 0) {
0710: directories = new HashMap();
0711: ArrayList<ZipFileIndexEntry> entryList = new ArrayList<ZipFileIndexEntry>();
0712: int pos = 2;
0713: for (int i = 0; i < entryCount; i++) {
0714: pos = readEntry(pos, entryList, directories);
0715: }
0716:
0717: // Add the accumulated dirs into the same list
0718: Iterator i = directories.keySet().iterator();
0719: while (i.hasNext()) {
0720: ZipFileIndexEntry zipFileIndexEntry = new ZipFileIndexEntry(
0721: (String) i.next());
0722: zipFileIndexEntry.isDir = true;
0723: entryList.add(zipFileIndexEntry);
0724: }
0725:
0726: entries = entryList
0727: .toArray(new ZipFileIndexEntry[entryList.size()]);
0728: Arrays.sort(entries);
0729: } else {
0730: cleanupState();
0731: }
0732: }
0733:
0734: private int readEntry(int pos, List entryList, Map directories)
0735: throws IOException {
0736: if (get4ByteLittleEndian(zipDir, pos) != 0x02014b50) {
0737: throw new IOException("bad folder entry");
0738: }
0739:
0740: int dirStart = pos + 46;
0741: int fileStart = dirStart;
0742: int fileEnd = fileStart
0743: + get2ByteLittleEndian(zipDir, pos + 28);
0744:
0745: if (zipFileIndex.symbolFilePrefixLength != 0
0746: && ((fileEnd - fileStart) >= symbolFilePrefixLength)) {
0747: dirStart += zipFileIndex.symbolFilePrefixLength;
0748: fileStart += zipFileIndex.symbolFilePrefixLength;
0749: }
0750:
0751: // Use the OS's path separator. Keep the position of the last one.
0752: for (int index = fileStart; index < fileEnd; index++) {
0753: byte nextByte = zipDir[index];
0754: if (nextByte == (byte) '\\' || nextByte == (byte) '/') {
0755: zipDir[index] = (byte) File.separatorChar;
0756: fileStart = index + 1;
0757: }
0758: }
0759:
0760: String directory = null;
0761: if (fileStart == dirStart)
0762: directory = "";
0763: else if (lastDir != null
0764: && lastLen == fileStart - dirStart - 1) {
0765: int index = lastLen - 1;
0766: while (zipDir[lastStart + index] == zipDir[dirStart
0767: + index]) {
0768: if (index == 0) {
0769: directory = lastDir;
0770: break;
0771: }
0772: index--;
0773: }
0774: }
0775:
0776: // Sub directories
0777: if (directory == null) {
0778: lastStart = dirStart;
0779: lastLen = fileStart - dirStart - 1;
0780:
0781: directory = new String(zipDir, dirStart, lastLen,
0782: "UTF-8").intern();
0783: lastDir = directory;
0784:
0785: // Enter also all the parent directories
0786: String tempDirectory = directory;
0787:
0788: while (directories.get(tempDirectory) == null) {
0789: directories.put(tempDirectory, new DirectoryEntry(
0790: tempDirectory, zipFileIndex));
0791: int separator = tempDirectory
0792: .lastIndexOf(File.separatorChar);
0793: if (separator == -1)
0794: break;
0795: tempDirectory = tempDirectory.substring(0,
0796: separator);
0797: }
0798: } else {
0799: directory = directory.intern();
0800: if (directories.get(directory) == null) {
0801: directories.put(directory, new DirectoryEntry(
0802: directory, zipFileIndex));
0803: }
0804: }
0805:
0806: // For each dir create also a file
0807: if (fileStart != fileEnd) {
0808: ZipFileIndexEntry entry = new ZipFileIndexEntry(
0809: directory, new String(zipDir, fileStart,
0810: fileEnd - fileStart, "UTF-8"));
0811:
0812: entry.setNativeTime(get4ByteLittleEndian(zipDir,
0813: pos + 12));
0814: entry.compressedSize = get4ByteLittleEndian(zipDir,
0815: pos + 20);
0816: entry.size = get4ByteLittleEndian(zipDir, pos + 24);
0817: entry.offset = get4ByteLittleEndian(zipDir, pos + 42);
0818: entryList.add(entry);
0819: }
0820:
0821: return pos + 46 + get2ByteLittleEndian(zipDir, pos + 28)
0822: + get2ByteLittleEndian(zipDir, pos + 30)
0823: + get2ByteLittleEndian(zipDir, pos + 32);
0824: }
0825: }
0826:
0827: /**
0828: * Returns the last modified timestamp of a zip file.
0829: * @return long
0830: */
0831: public long getZipFileLastModified() {
0832: lock.lock();
0833: try {
0834: checkIndex();
0835: return zipFileLastModified;
0836: } finally {
0837: lock.unlock();
0838: }
0839: }
0840:
0841: /** ------------------------------------------------------------------------
0842: * DirectoryEntry class
0843: * -------------------------------------------------------------------------*/
0844: static class DirectoryEntry {
0845: private boolean filesInited;
0846: private boolean directoriesInited;
0847: private boolean zipFileEntriesInited;
0848: private boolean entriesInited;
0849:
0850: private long writtenOffsetOffset = 0;
0851:
0852: private String dirName;
0853:
0854: private com.sun.tools.javac.util.List<String> zipFileEntriesFiles = ZipFileIndex.EMPTY_LIST;
0855: private com.sun.tools.javac.util.List<String> zipFileEntriesDirectories = ZipFileIndex.EMPTY_LIST;
0856: private com.sun.tools.javac.util.List<ZipFileIndexEntry> zipFileEntries = ZipFileIndex.EMPTY_LIST;
0857:
0858: private List<ZipFileIndexEntry> entries = new ArrayList<ZipFileIndexEntry>();
0859:
0860: private ZipFileIndex zipFileIndex;
0861:
0862: private int numEntries;
0863:
0864: DirectoryEntry(String dirName, ZipFileIndex index) {
0865: filesInited = false;
0866: directoriesInited = false;
0867: entriesInited = false;
0868:
0869: if (File.separatorChar == '/') {
0870: dirName.replace('\\', '/');
0871: } else {
0872: dirName.replace('/', '\\');
0873: }
0874:
0875: this .dirName = dirName.intern();
0876: this .zipFileIndex = index;
0877: }
0878:
0879: private com.sun.tools.javac.util.List<String> getFiles() {
0880: if (filesInited) {
0881: return zipFileEntriesFiles;
0882: }
0883:
0884: initEntries();
0885:
0886: for (ZipFileIndexEntry e : entries) {
0887: if (!e.isDir) {
0888: zipFileEntriesFiles = zipFileEntriesFiles
0889: .append(e.name);
0890: }
0891: }
0892: filesInited = true;
0893: return zipFileEntriesFiles;
0894: }
0895:
0896: private com.sun.tools.javac.util.List<String> getDirectories() {
0897: if (directoriesInited) {
0898: return zipFileEntriesFiles;
0899: }
0900:
0901: initEntries();
0902:
0903: for (ZipFileIndexEntry e : entries) {
0904: if (e.isDir) {
0905: zipFileEntriesDirectories = zipFileEntriesDirectories
0906: .append(e.name);
0907: }
0908: }
0909:
0910: directoriesInited = true;
0911:
0912: return zipFileEntriesDirectories;
0913: }
0914:
0915: private com.sun.tools.javac.util.List<ZipFileIndexEntry> getEntries() {
0916: if (zipFileEntriesInited) {
0917: return zipFileEntries;
0918: }
0919:
0920: initEntries();
0921:
0922: zipFileEntries = com.sun.tools.javac.util.List.nil();
0923: for (ZipFileIndexEntry zfie : entries) {
0924: zipFileEntries = zipFileEntries.append(zfie);
0925: }
0926:
0927: zipFileEntriesInited = true;
0928:
0929: return zipFileEntries;
0930: }
0931:
0932: private ZipFileIndexEntry getEntry(String rootName) {
0933: initEntries();
0934: int index = Collections.binarySearch(entries,
0935: new ZipFileIndexEntry(dirName, rootName));
0936: if (index < 0) {
0937: return null;
0938: }
0939:
0940: return entries.get(index);
0941: }
0942:
0943: private void initEntries() {
0944: if (entriesInited) {
0945: return;
0946: }
0947:
0948: if (!zipFileIndex.readFromIndex) {
0949: int from = -Arrays.binarySearch(zipFileIndex.entries,
0950: new ZipFileIndexEntry(dirName,
0951: ZipFileIndex.MIN_CHAR)) - 1;
0952: int to = -Arrays.binarySearch(zipFileIndex.entries,
0953: new ZipFileIndexEntry(dirName, MAX_CHAR)) - 1;
0954:
0955: boolean emptyList = false;
0956:
0957: for (int i = from; i < to; i++) {
0958: entries.add(zipFileIndex.entries[i]);
0959: }
0960: } else {
0961: File indexFile = zipFileIndex.getIndexFile();
0962: if (indexFile != null) {
0963: RandomAccessFile raf = null;
0964: try {
0965: raf = new RandomAccessFile(indexFile, "r");
0966: raf.seek(writtenOffsetOffset);
0967:
0968: for (int nFiles = 0; nFiles < numEntries; nFiles++) {
0969: // Read the name bytes
0970: int zfieNameBytesLen = raf.readInt();
0971: byte[] zfieNameBytes = new byte[zfieNameBytesLen];
0972: raf.read(zfieNameBytes);
0973: String eName = new String(zfieNameBytes,
0974: "UTF-8");
0975:
0976: // Read isDir
0977: boolean eIsDir = raf.readByte() == (byte) 0 ? false
0978: : true;
0979:
0980: // Read offset of bytes in the real Jar/Zip file
0981: int eOffset = raf.readInt();
0982:
0983: // Read size of the file in the real Jar/Zip file
0984: int eSize = raf.readInt();
0985:
0986: // Read compressed size of the file in the real Jar/Zip file
0987: int eCsize = raf.readInt();
0988:
0989: // Read java time stamp of the file in the real Jar/Zip file
0990: long eJavaTimestamp = raf.readLong();
0991:
0992: ZipFileIndexEntry rfie = new ZipFileIndexEntry(
0993: dirName, eName);
0994: rfie.isDir = eIsDir;
0995: rfie.offset = eOffset;
0996: rfie.size = eSize;
0997: rfie.compressedSize = eCsize;
0998: rfie.javatime = eJavaTimestamp;
0999: entries.add(rfie);
1000: }
1001: } catch (Throwable t) {
1002: // Do nothing
1003: } finally {
1004: try {
1005: if (raf == null) {
1006: raf.close();
1007: }
1008: } catch (Throwable t) {
1009: // Do nothing
1010: }
1011: }
1012: }
1013: }
1014:
1015: entriesInited = true;
1016: }
1017:
1018: List<ZipFileIndexEntry> getEntriesAsCollection() {
1019: initEntries();
1020:
1021: return entries;
1022: }
1023: }
1024:
1025: private boolean readIndex() {
1026: if (triedToReadIndex || !usePreindexedCache) {
1027: return false;
1028: }
1029:
1030: boolean ret = false;
1031: lock.lock();
1032: try {
1033: triedToReadIndex = true;
1034: RandomAccessFile raf = null;
1035: try {
1036: File indexFileName = getIndexFile();
1037: raf = new RandomAccessFile(indexFileName, "r");
1038:
1039: long fileStamp = raf.readLong();
1040: if (zipFile.lastModified() != fileStamp) {
1041: ret = false;
1042: } else {
1043: directories = new HashMap<String, DirectoryEntry>();
1044: int numDirs = raf.readInt();
1045: for (int nDirs = 0; nDirs < numDirs; nDirs++) {
1046: int dirNameBytesLen = raf.readInt();
1047: byte[] dirNameBytes = new byte[dirNameBytesLen];
1048: raf.read(dirNameBytes);
1049:
1050: String dirNameStr = new String(dirNameBytes,
1051: "UTF-8");
1052: DirectoryEntry de = new DirectoryEntry(
1053: dirNameStr, this );
1054: de.numEntries = raf.readInt();
1055: de.writtenOffsetOffset = raf.readLong();
1056: directories.put(dirNameStr, de);
1057: }
1058: ret = true;
1059: zipFileLastModified = fileStamp;
1060: }
1061: } catch (Throwable t) {
1062: // Do nothing
1063: } finally {
1064: if (raf != null) {
1065: try {
1066: raf.close();
1067: } catch (Throwable tt) {
1068: // Do nothing
1069: }
1070: }
1071: }
1072: if (ret == true) {
1073: readFromIndex = true;
1074: }
1075: } finally {
1076: lock.unlock();
1077: }
1078:
1079: return ret;
1080: }
1081:
1082: private boolean writeIndex() {
1083: boolean ret = false;
1084: if (readFromIndex || !usePreindexedCache) {
1085: return true;
1086: }
1087:
1088: if (!writeIndex) {
1089: return true;
1090: }
1091:
1092: File indexFile = getIndexFile();
1093: if (indexFile == null) {
1094: return false;
1095: }
1096:
1097: RandomAccessFile raf = null;
1098: long writtenSoFar = 0;
1099: try {
1100: raf = new RandomAccessFile(indexFile, "rw");
1101:
1102: raf.writeLong(zipFileLastModified);
1103: writtenSoFar += 8;
1104:
1105: Iterator<String> iterDirName = directories.keySet()
1106: .iterator();
1107: List<DirectoryEntry> directoriesToWrite = new ArrayList<DirectoryEntry>();
1108: Map<String, Long> offsets = new HashMap<String, Long>();
1109: raf.writeInt(directories.keySet().size());
1110: writtenSoFar += 4;
1111:
1112: while (iterDirName.hasNext()) {
1113: String dirName = iterDirName.next();
1114: DirectoryEntry dirEntry = directories.get(dirName);
1115:
1116: directoriesToWrite.add(dirEntry);
1117:
1118: // Write the dir name bytes
1119: byte[] dirNameBytes = dirName.getBytes("UTF-8");
1120: int dirNameBytesLen = dirNameBytes.length;
1121: raf.writeInt(dirNameBytesLen);
1122: writtenSoFar += 4;
1123:
1124: raf.write(dirNameBytes);
1125: writtenSoFar += dirNameBytesLen;
1126:
1127: // Write the number of files in the dir
1128: List dirEntries = dirEntry.getEntriesAsCollection();
1129: raf.writeInt(dirEntries.size());
1130: writtenSoFar += 4;
1131:
1132: offsets.put(dirName, new Long(writtenSoFar));
1133:
1134: // Write the offset of the file's data in the dir
1135: dirEntry.writtenOffsetOffset = 0L;
1136: raf.writeLong(0L);
1137: writtenSoFar += 8;
1138: }
1139:
1140: for (DirectoryEntry de : directoriesToWrite) {
1141: // Fix up the offset in the directory table
1142: long currFP = raf.getFilePointer();
1143:
1144: long offsetOffset = offsets.get(de.dirName).longValue();
1145: raf.seek(offsetOffset);
1146: raf.writeLong(writtenSoFar);
1147:
1148: raf.seek(currFP);
1149:
1150: // Now write each of the files in the DirectoryEntry
1151: List<ZipFileIndexEntry> entries = de
1152: .getEntriesAsCollection();
1153: for (ZipFileIndexEntry zfie : entries) {
1154: // Write the name bytes
1155: byte[] zfieNameBytes = zfie.name.getBytes("UTF-8");
1156: int zfieNameBytesLen = zfieNameBytes.length;
1157: raf.writeInt(zfieNameBytesLen);
1158: writtenSoFar += 4;
1159: raf.write(zfieNameBytes);
1160: writtenSoFar += zfieNameBytesLen;
1161:
1162: // Write isDir
1163: raf.writeByte(zfie.isDir ? (byte) 1 : (byte) 0);
1164: writtenSoFar += 1;
1165:
1166: // Write offset of bytes in the real Jar/Zip file
1167: raf.writeInt(zfie.offset);
1168: writtenSoFar += 4;
1169:
1170: // Write size of the file in the real Jar/Zip file
1171: raf.writeInt(zfie.size);
1172: writtenSoFar += 4;
1173:
1174: // Write compressed size of the file in the real Jar/Zip file
1175: raf.writeInt(zfie.compressedSize);
1176: writtenSoFar += 4;
1177:
1178: // Write java time stamp of the file in the real Jar/Zip file
1179: raf.writeLong(zfie.getLastModified());
1180: writtenSoFar += 8;
1181: }
1182: }
1183: } catch (Throwable t) {
1184: // Do nothing
1185: } finally {
1186: try {
1187: if (raf != null) {
1188: raf.close();
1189: }
1190: } catch (IOException ioe) {
1191: // Do nothing
1192: }
1193: }
1194:
1195: return ret;
1196: }
1197:
1198: public boolean writeZipIndex() {
1199: lock.lock();
1200: try {
1201: return writeIndex();
1202: } finally {
1203: lock.unlock();
1204: }
1205: }
1206:
1207: private File getIndexFile() {
1208: if (zipIndexFile == null) {
1209: if (zipFile == null) {
1210: return null;
1211: }
1212:
1213: zipIndexFile = new File(
1214: (preindexedCacheLocation == null ? ""
1215: : preindexedCacheLocation)
1216: + zipFile.getName() + ".index");
1217: }
1218:
1219: return zipIndexFile;
1220: }
1221:
1222: public File getZipFile() {
1223: return zipFile;
1224: }
1225: }
|