jjg@36: /* jjh@1305: * Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved. jjg@36: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. jjg@36: * jjg@36: * This code is free software; you can redistribute it and/or modify it jjg@36: * under the terms of the GNU General Public License version 2 only, as ohair@554: * published by the Free Software Foundation. Oracle designates this jjg@36: * particular file as subject to the "Classpath" exception as provided ohair@554: * by Oracle in the LICENSE file that accompanied this code. jjg@36: * jjg@36: * This code is distributed in the hope that it will be useful, but WITHOUT jjg@36: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or jjg@36: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License jjg@36: * version 2 for more details (a copy is included in the LICENSE file that jjg@36: * accompanied this code). jjg@36: * jjg@36: * You should have received a copy of the GNU General Public License version jjg@36: * 2 along with this work; if not, write to the Free Software Foundation, jjg@36: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. jjg@36: * ohair@554: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA ohair@554: * or visit www.oracle.com if you need additional information or have any ohair@554: * questions. jjg@36: */ jjg@36: jjg@50: package com.sun.tools.javac.file; duke@1: jjg@103: jjg@50: import java.io.File; jjg@50: import java.io.FileNotFoundException; jjg@50: import java.io.IOException; jjg@50: import java.io.RandomAccessFile; jjg@424: import java.lang.ref.Reference; jjg@103: import java.lang.ref.SoftReference; jjg@50: import java.util.ArrayList; jjg@50: import java.util.Arrays; jjg@71: import java.util.Calendar; jjg@50: import java.util.Collections; mcimadamore@1279: import java.util.LinkedHashMap; jjg@50: import java.util.HashMap; duke@1: import java.util.List; jjg@50: import java.util.Map; jjg@50: import java.util.Set; jjg@50: import java.util.zip.DataFormatException; jjg@50: import java.util.zip.Inflater; jjg@50: import java.util.zip.ZipException; duke@1: jjg@103: import com.sun.tools.javac.file.RelativePath.RelativeDirectory; jjg@103: import com.sun.tools.javac.file.RelativePath.RelativeFile; jjg@103: jjg@839: /** jjg@839: * This class implements the building of index of a zip archive and access to jjg@839: * its context. It also uses a prebuilt index if available. jjg@839: * It supports invocations where it will serialize an optimized zip index file jjg@839: * to disk. duke@1: * jjg@839: * In order to use a secondary index file, set "usezipindex" in the Options jjg@839: * object when JavacFileManager is invoked. (You can pass "-XDusezipindex" on jjg@839: * the command line.) duke@1: * jjg@839: * Location where to look for/generate optimized zip index files can be jjg@1358: * provided using "{@code -XDcachezipindexdir=}". If this flag is not jjg@839: * provided, the default location is the value of the "java.io.tmpdir" system jjg@839: * property. duke@1: * jjg@839: * If "-XDwritezipindexfiles" is specified, there will be new optimized index jjg@839: * file created for each archive, used by the compiler for compilation, at the jjg@839: * location specified by the "cachezipindexdir" option. duke@1: * jjg@839: * If system property nonBatchMode option is specified the compiler will use jjg@839: * timestamp checking to reindex the zip files if it is needed. In batch mode jjg@839: * the timestamps are not checked and the compiler uses the cached indexes. jjg@333: * jjg@581: *

This is NOT part of any supported API. jjg@333: * If you write code that depends on this, you do so at your own risk. jjg@333: * This code and its internal interfaces are subject to change or jjg@333: * deletion without notice. duke@1: */ duke@1: public class ZipFileIndex { duke@1: private static final String MIN_CHAR = String.valueOf(Character.MIN_VALUE); duke@1: private static final String MAX_CHAR = String.valueOf(Character.MAX_VALUE); duke@1: duke@1: public final static long NOT_MODIFIED = Long.MIN_VALUE; duke@1: duke@1: vromero@1442: private static final boolean NON_BATCH_MODE = System.getProperty("nonBatchMode") != null;// TODO: Use -XD compiler switch for this. duke@1: jjg@839: private Map directories = jjg@839: Collections.emptyMap(); jjg@839: private Set allDirs = jjg@839: Collections.emptySet(); duke@1: duke@1: // ZipFileIndex data entries jjg@839: final File zipFile; jjg@424: private Reference absFileRef; jjg@839: long zipFileLastModified = NOT_MODIFIED; duke@1: private RandomAccessFile zipRandomFile; jjg@57: private Entry[] entries; duke@1: duke@1: private boolean readFromIndex = false; duke@1: private File zipIndexFile = null; duke@1: private boolean triedToReadIndex = false; jjg@103: final RelativeDirectory symbolFilePrefix; jjg@839: private final int symbolFilePrefixLength; duke@1: private boolean hasPopulatedData = false; jjg@839: long lastReferenceTimeStamp = NOT_MODIFIED; duke@1: jjg@839: private final boolean usePreindexedCache; jjg@839: private final String preindexedCacheLocation; duke@1: duke@1: private boolean writeIndex = false; duke@1: jjg@839: private Map> relativeDirectoryCache = jjg@103: new HashMap>(); jjg@103: jjg@839: jjg@839: public synchronized boolean isOpen() { jjg@839: return (zipRandomFile != null); duke@1: } duke@1: jjg@839: ZipFileIndex(File zipFile, RelativeDirectory symbolFilePrefix, boolean writeIndex, duke@1: boolean useCache, String cacheLocation) throws IOException { duke@1: this.zipFile = zipFile; jjg@57: this.symbolFilePrefix = symbolFilePrefix; jjg@57: this.symbolFilePrefixLength = (symbolFilePrefix == null ? 0 : jjg@103: symbolFilePrefix.getPath().getBytes("UTF-8").length); duke@1: this.writeIndex = writeIndex; duke@1: this.usePreindexedCache = useCache; duke@1: this.preindexedCacheLocation = cacheLocation; duke@1: duke@1: if (zipFile != null) { duke@1: this.zipFileLastModified = zipFile.lastModified(); duke@1: } duke@1: duke@1: // Validate integrity of the zip file duke@1: checkIndex(); duke@1: } duke@1: jjg@839: @Override duke@1: public String toString() { jjg@103: return "ZipFileIndex[" + zipFile + "]"; duke@1: } duke@1: duke@1: // Just in case... jjg@839: @Override jjg@839: protected void finalize() throws Throwable { duke@1: closeFile(); jjg@839: super.finalize(); duke@1: } duke@1: duke@1: private boolean isUpToDate() { jjg@839: if (zipFile != null jjg@839: && ((!NON_BATCH_MODE) || zipFileLastModified == zipFile.lastModified()) jjg@839: && hasPopulatedData) { duke@1: return true; duke@1: } duke@1: duke@1: return false; duke@1: } duke@1: duke@1: /** duke@1: * Here we need to make sure that the ZipFileIndex is valid. Check the timestamp of the file and duke@1: * if its the same as the one at the time the index was build we don't need to reopen anything. duke@1: */ duke@1: private void checkIndex() throws IOException { duke@1: boolean isUpToDate = true; duke@1: if (!isUpToDate()) { duke@1: closeFile(); duke@1: isUpToDate = false; duke@1: } duke@1: duke@1: if (zipRandomFile != null || isUpToDate) { duke@1: lastReferenceTimeStamp = System.currentTimeMillis(); duke@1: return; duke@1: } duke@1: duke@1: hasPopulatedData = true; duke@1: duke@1: if (readIndex()) { duke@1: lastReferenceTimeStamp = System.currentTimeMillis(); duke@1: return; duke@1: } duke@1: jjg@103: directories = Collections.emptyMap(); jjg@103: allDirs = Collections.emptySet(); duke@1: duke@1: try { duke@1: openFile(); duke@1: long totalLength = zipRandomFile.length(); duke@1: ZipDirectory directory = new ZipDirectory(zipRandomFile, 0L, totalLength, this); duke@1: directory.buildIndex(); duke@1: } finally { duke@1: if (zipRandomFile != null) { duke@1: closeFile(); duke@1: } duke@1: } duke@1: duke@1: lastReferenceTimeStamp = System.currentTimeMillis(); duke@1: } duke@1: duke@1: private void openFile() throws FileNotFoundException { duke@1: if (zipRandomFile == null && zipFile != null) { duke@1: zipRandomFile = new RandomAccessFile(zipFile, "r"); duke@1: } duke@1: } duke@1: duke@1: private void cleanupState() { duke@1: // Make sure there is a valid but empty index if the file doesn't exist jjg@57: entries = Entry.EMPTY_ARRAY; jjg@103: directories = Collections.emptyMap(); duke@1: zipFileLastModified = NOT_MODIFIED; jjg@103: allDirs = Collections.emptySet(); duke@1: } duke@1: jjg@839: public synchronized void close() { jjg@839: writeIndex(); jjg@839: closeFile(); duke@1: } duke@1: duke@1: private void closeFile() { duke@1: if (zipRandomFile != null) { duke@1: try { duke@1: zipRandomFile.close(); duke@1: } catch (IOException ex) { duke@1: } duke@1: zipRandomFile = null; duke@1: } duke@1: } duke@1: duke@1: /** jjg@839: * Returns the ZipFileIndexEntry for a path, if there is one. duke@1: */ jjg@839: synchronized Entry getZipIndexEntry(RelativePath path) { duke@1: try { duke@1: checkIndex(); jjg@103: DirectoryEntry de = directories.get(path.dirname()); jjg@103: String lookFor = path.basename(); jjg@839: return (de == null) ? null : de.getEntry(lookFor); duke@1: } duke@1: catch (IOException e) { duke@1: return null; duke@1: } duke@1: } duke@1: duke@1: /** jjg@839: * Returns a javac List of filenames within a directory in the ZipFileIndex. duke@1: */ jjg@839: public synchronized com.sun.tools.javac.util.List getFiles(RelativeDirectory path) { duke@1: try { duke@1: checkIndex(); duke@1: duke@1: DirectoryEntry de = directories.get(path); duke@1: com.sun.tools.javac.util.List ret = de == null ? null : de.getFiles(); duke@1: duke@1: if (ret == null) { duke@1: return com.sun.tools.javac.util.List.nil(); duke@1: } duke@1: return ret; duke@1: } duke@1: catch (IOException e) { duke@1: return com.sun.tools.javac.util.List.nil(); duke@1: } duke@1: } duke@1: jjg@839: public synchronized List getDirectories(RelativeDirectory path) { duke@1: try { duke@1: checkIndex(); duke@1: duke@1: DirectoryEntry de = directories.get(path); duke@1: com.sun.tools.javac.util.List ret = de == null ? null : de.getDirectories(); duke@1: duke@1: if (ret == null) { duke@1: return com.sun.tools.javac.util.List.nil(); duke@1: } duke@1: duke@1: return ret; duke@1: } duke@1: catch (IOException e) { duke@1: return com.sun.tools.javac.util.List.nil(); duke@1: } duke@1: } duke@1: jjg@839: public synchronized Set getAllDirectories() { duke@1: try { duke@1: checkIndex(); duke@1: if (allDirs == Collections.EMPTY_SET) { mcimadamore@1279: allDirs = new java.util.LinkedHashSet(directories.keySet()); duke@1: } duke@1: duke@1: return allDirs; duke@1: } duke@1: catch (IOException e) { jjg@103: return Collections.emptySet(); duke@1: } duke@1: } duke@1: duke@1: /** duke@1: * Tests if a specific path exists in the zip. This method will return true duke@1: * for file entries and directories. duke@1: * duke@1: * @param path A path within the zip. duke@1: * @return True if the path is a file or dir, false otherwise. duke@1: */ jjg@839: public synchronized boolean contains(RelativePath path) { duke@1: try { duke@1: checkIndex(); duke@1: return getZipIndexEntry(path) != null; duke@1: } duke@1: catch (IOException e) { duke@1: return false; duke@1: } jjg@839: } jjg@839: jjg@839: public synchronized boolean isDirectory(RelativePath path) throws IOException { jjg@839: // The top level in a zip file is always a directory. jjg@839: if (path.getPath().length() == 0) { jjg@839: lastReferenceTimeStamp = System.currentTimeMillis(); jjg@839: return true; jjg@839: } jjg@839: jjg@839: checkIndex(); jjg@839: return directories.get(path) != null; jjg@839: } jjg@839: jjg@839: public synchronized long getLastModified(RelativeFile path) throws IOException { jjg@839: Entry entry = getZipIndexEntry(path); jjg@839: if (entry == null) jjg@839: throw new FileNotFoundException(); jjg@839: return entry.getLastModified(); jjg@839: } jjg@839: jjg@839: public synchronized int length(RelativeFile path) throws IOException { jjg@839: Entry entry = getZipIndexEntry(path); jjg@839: if (entry == null) jjg@839: throw new FileNotFoundException(); jjg@839: jjg@839: if (entry.isDir) { jjg@839: return 0; jjg@839: } jjg@839: jjg@839: byte[] header = getHeader(entry); jjg@839: // entry is not compressed? jjg@839: if (get2ByteLittleEndian(header, 8) == 0) { jjg@839: return entry.compressedSize; jjg@839: } else { jjg@839: return entry.size; duke@1: } duke@1: } duke@1: jjg@839: public synchronized byte[] read(RelativeFile path) throws IOException { jjg@839: Entry entry = getZipIndexEntry(path); jjg@839: if (entry == null) jjg@839: throw new FileNotFoundException("Path not found in ZIP: " + path.path); jjg@839: return read(entry); duke@1: } duke@1: jjg@839: synchronized byte[] read(Entry entry) throws IOException { jjg@839: openFile(); jjg@839: byte[] result = readBytes(entry); jjg@839: closeFile(); jjg@839: return result; duke@1: } duke@1: jjg@839: public synchronized int read(RelativeFile path, byte[] buffer) throws IOException { jjg@839: Entry entry = getZipIndexEntry(path); jjg@839: if (entry == null) jjg@839: throw new FileNotFoundException(); jjg@839: return read(entry, buffer); duke@1: } duke@1: jjg@839: synchronized int read(Entry entry, byte[] buffer) duke@1: throws IOException { jjg@839: int result = readBytes(entry, buffer); jjg@839: return result; duke@1: } duke@1: jjg@57: private byte[] readBytes(Entry entry) throws IOException { duke@1: byte[] header = getHeader(entry); duke@1: int csize = entry.compressedSize; duke@1: byte[] cbuf = new byte[csize]; duke@1: zipRandomFile.skipBytes(get2ByteLittleEndian(header, 26) + get2ByteLittleEndian(header, 28)); duke@1: zipRandomFile.readFully(cbuf, 0, csize); duke@1: duke@1: // is this compressed - offset 8 in the ZipEntry header duke@1: if (get2ByteLittleEndian(header, 8) == 0) duke@1: return cbuf; duke@1: duke@1: int size = entry.size; duke@1: byte[] buf = new byte[size]; duke@1: if (inflate(cbuf, buf) != size) duke@1: throw new ZipException("corrupted zip file"); duke@1: duke@1: return buf; duke@1: } duke@1: duke@1: /** duke@1: * duke@1: */ jjg@57: private int readBytes(Entry entry, byte[] buffer) throws IOException { duke@1: byte[] header = getHeader(entry); duke@1: duke@1: // entry is not compressed? duke@1: if (get2ByteLittleEndian(header, 8) == 0) { duke@1: zipRandomFile.skipBytes(get2ByteLittleEndian(header, 26) + get2ByteLittleEndian(header, 28)); duke@1: int offset = 0; duke@1: int size = buffer.length; duke@1: while (offset < size) { duke@1: int count = zipRandomFile.read(buffer, offset, size - offset); duke@1: if (count == -1) duke@1: break; duke@1: offset += count; duke@1: } duke@1: return entry.size; duke@1: } duke@1: duke@1: int csize = entry.compressedSize; duke@1: byte[] cbuf = new byte[csize]; duke@1: zipRandomFile.skipBytes(get2ByteLittleEndian(header, 26) + get2ByteLittleEndian(header, 28)); duke@1: zipRandomFile.readFully(cbuf, 0, csize); duke@1: duke@1: int count = inflate(cbuf, buffer); duke@1: if (count == -1) duke@1: throw new ZipException("corrupted zip file"); duke@1: duke@1: return entry.size; duke@1: } duke@1: duke@1: //---------------------------------------------------------------------------- duke@1: // Zip utilities duke@1: //---------------------------------------------------------------------------- duke@1: jjg@57: private byte[] getHeader(Entry entry) throws IOException { duke@1: zipRandomFile.seek(entry.offset); duke@1: byte[] header = new byte[30]; duke@1: zipRandomFile.readFully(header); duke@1: if (get4ByteLittleEndian(header, 0) != 0x04034b50) duke@1: throw new ZipException("corrupted zip file"); duke@1: if ((get2ByteLittleEndian(header, 6) & 1) != 0) duke@1: throw new ZipException("encrypted zip file"); // offset 6 in the header of the ZipFileEntry duke@1: return header; duke@1: } duke@1: duke@1: /* duke@1: * Inflate using the java.util.zip.Inflater class duke@1: */ jjg@839: private SoftReference inflaterRef; duke@1: private int inflate(byte[] src, byte[] dest) { jjg@839: Inflater inflater = (inflaterRef == null ? null : inflaterRef.get()); duke@1: duke@1: // construct the inflater object or reuse an existing one duke@1: if (inflater == null) jjg@839: inflaterRef = new SoftReference(inflater = new Inflater(true)); duke@1: jjg@839: inflater.reset(); jjg@839: inflater.setInput(src); jjg@839: try { jjg@839: return inflater.inflate(dest); jjg@839: } catch (DataFormatException ex) { jjg@839: return -1; duke@1: } duke@1: } duke@1: duke@1: /** duke@1: * return the two bytes buf[pos], buf[pos+1] as an unsigned integer in little duke@1: * endian format. duke@1: */ duke@1: private static int get2ByteLittleEndian(byte[] buf, int pos) { duke@1: return (buf[pos] & 0xFF) + ((buf[pos+1] & 0xFF) << 8); duke@1: } duke@1: duke@1: /** duke@1: * return the 4 bytes buf[i..i+3] as an integer in little endian format. duke@1: */ duke@1: private static int get4ByteLittleEndian(byte[] buf, int pos) { duke@1: return (buf[pos] & 0xFF) + ((buf[pos + 1] & 0xFF) << 8) + duke@1: ((buf[pos + 2] & 0xFF) << 16) + ((buf[pos + 3] & 0xFF) << 24); duke@1: } duke@1: duke@1: /* ---------------------------------------------------------------------------- duke@1: * ZipDirectory duke@1: * ----------------------------------------------------------------------------*/ duke@1: duke@1: private class ZipDirectory { jjg@103: private RelativeDirectory lastDir; duke@1: private int lastStart; duke@1: private int lastLen; duke@1: duke@1: byte[] zipDir; duke@1: RandomAccessFile zipRandomFile = null; duke@1: ZipFileIndex zipFileIndex = null; duke@1: duke@1: public ZipDirectory(RandomAccessFile zipRandomFile, long start, long end, ZipFileIndex index) throws IOException { duke@1: this.zipRandomFile = zipRandomFile; duke@1: this.zipFileIndex = index; ksrini@923: hasValidHeader(); ksrini@923: findCENRecord(start, end); ksrini@923: } duke@1: ksrini@923: /* ksrini@923: * the zip entry signature should be at offset 0, otherwise allow the ksrini@923: * calling logic to take evasive action by throwing ZipFormatException. ksrini@923: */ ksrini@923: private boolean hasValidHeader() throws IOException { ksrini@923: final long pos = zipRandomFile.getFilePointer(); ksrini@923: try { ksrini@923: if (zipRandomFile.read() == 'P') { ksrini@923: if (zipRandomFile.read() == 'K') { ksrini@923: if (zipRandomFile.read() == 0x03) { ksrini@923: if (zipRandomFile.read() == 0x04) { ksrini@923: return true; ksrini@923: } ksrini@923: } ksrini@923: } ksrini@923: } ksrini@923: } finally { ksrini@923: zipRandomFile.seek(pos); ksrini@923: } ksrini@923: throw new ZipFormatException("invalid zip magic"); duke@1: } duke@1: duke@1: /* duke@1: * Reads zip file central directory. duke@1: * For more details see readCEN in zip_util.c from the JDK sources. duke@1: * This is a Java port of that function. duke@1: */ duke@1: private void findCENRecord(long start, long end) throws IOException { duke@1: long totalLength = end - start; duke@1: int endbuflen = 1024; duke@1: byte[] endbuf = new byte[endbuflen]; duke@1: long endbufend = end - start; duke@1: duke@1: // There is a variable-length field after the dir offset record. We need to do consequential search. duke@1: while (endbufend >= 22) { duke@1: if (endbufend < endbuflen) duke@1: endbuflen = (int)endbufend; duke@1: long endbufpos = endbufend - endbuflen; duke@1: zipRandomFile.seek(start + endbufpos); duke@1: zipRandomFile.readFully(endbuf, 0, endbuflen); duke@1: int i = endbuflen - 22; duke@1: while (i >= 0 && duke@1: !(endbuf[i] == 0x50 && duke@1: endbuf[i + 1] == 0x4b && duke@1: endbuf[i + 2] == 0x05 && duke@1: endbuf[i + 3] == 0x06 && duke@1: endbufpos + i + 22 + duke@1: get2ByteLittleEndian(endbuf, i + 20) == totalLength)) { duke@1: i--; duke@1: } duke@1: duke@1: if (i >= 0) { duke@1: zipDir = new byte[get4ByteLittleEndian(endbuf, i + 12) + 2]; duke@1: zipDir[0] = endbuf[i + 10]; duke@1: zipDir[1] = endbuf[i + 11]; ksrini@923: int sz = get4ByteLittleEndian(endbuf, i + 16); ksrini@923: // a negative offset or the entries field indicates a ksrini@923: // potential zip64 archive ksrini@923: if (sz < 0 || get2ByteLittleEndian(zipDir, 0) == 0xffff) { ksrini@923: throw new ZipFormatException("detected a zip64 archive"); ksrini@923: } ksrini@923: zipRandomFile.seek(start + sz); duke@1: zipRandomFile.readFully(zipDir, 2, zipDir.length - 2); duke@1: return; duke@1: } else { duke@1: endbufend = endbufpos + 21; duke@1: } duke@1: } duke@1: throw new ZipException("cannot read zip file"); duke@1: } jjg@103: duke@1: private void buildIndex() throws IOException { duke@1: int entryCount = get2ByteLittleEndian(zipDir, 0); duke@1: duke@1: // Add each of the files duke@1: if (entryCount > 0) { mcimadamore@1279: directories = new LinkedHashMap(); jjg@57: ArrayList entryList = new ArrayList(); duke@1: int pos = 2; duke@1: for (int i = 0; i < entryCount; i++) { duke@1: pos = readEntry(pos, entryList, directories); duke@1: } duke@1: duke@1: // Add the accumulated dirs into the same list jjg@103: for (RelativeDirectory d: directories.keySet()) { jjg@103: // use shared RelativeDirectory objects for parent dirs jjg@103: RelativeDirectory parent = getRelativeDirectory(d.dirname().getPath()); jjg@103: String file = d.basename(); jjg@103: Entry zipFileIndexEntry = new Entry(parent, file); duke@1: zipFileIndexEntry.isDir = true; duke@1: entryList.add(zipFileIndexEntry); duke@1: } duke@1: jjg@57: entries = entryList.toArray(new Entry[entryList.size()]); duke@1: Arrays.sort(entries); duke@1: } else { duke@1: cleanupState(); duke@1: } duke@1: } duke@1: jjg@57: private int readEntry(int pos, List entryList, jjg@103: Map directories) throws IOException { duke@1: if (get4ByteLittleEndian(zipDir, pos) != 0x02014b50) { duke@1: throw new ZipException("cannot read zip file entry"); duke@1: } duke@1: duke@1: int dirStart = pos + 46; duke@1: int fileStart = dirStart; duke@1: int fileEnd = fileStart + get2ByteLittleEndian(zipDir, pos + 28); duke@1: duke@1: if (zipFileIndex.symbolFilePrefixLength != 0 && duke@1: ((fileEnd - fileStart) >= symbolFilePrefixLength)) { duke@1: dirStart += zipFileIndex.symbolFilePrefixLength; duke@1: fileStart += zipFileIndex.symbolFilePrefixLength; duke@1: } jjg@103: // Force any '\' to '/'. Keep the position of the last separator. duke@1: for (int index = fileStart; index < fileEnd; index++) { duke@1: byte nextByte = zipDir[index]; jjg@103: if (nextByte == (byte)'\\') { jjg@103: zipDir[index] = (byte)'/'; jjg@103: fileStart = index + 1; jjg@103: } else if (nextByte == (byte)'/') { duke@1: fileStart = index + 1; duke@1: } duke@1: } duke@1: jjg@103: RelativeDirectory directory = null; duke@1: if (fileStart == dirStart) jjg@103: directory = getRelativeDirectory(""); duke@1: else if (lastDir != null && lastLen == fileStart - dirStart - 1) { duke@1: int index = lastLen - 1; duke@1: while (zipDir[lastStart + index] == zipDir[dirStart + index]) { duke@1: if (index == 0) { duke@1: directory = lastDir; duke@1: break; duke@1: } duke@1: index--; duke@1: } duke@1: } duke@1: duke@1: // Sub directories duke@1: if (directory == null) { duke@1: lastStart = dirStart; duke@1: lastLen = fileStart - dirStart - 1; duke@1: jjg@103: directory = getRelativeDirectory(new String(zipDir, dirStart, lastLen, "UTF-8")); duke@1: lastDir = directory; duke@1: duke@1: // Enter also all the parent directories jjg@103: RelativeDirectory tempDirectory = directory; duke@1: duke@1: while (directories.get(tempDirectory) == null) { duke@1: directories.put(tempDirectory, new DirectoryEntry(tempDirectory, zipFileIndex)); jjg@103: if (tempDirectory.path.indexOf("/") == tempDirectory.path.length() - 1) duke@1: break; jjg@103: else { jjg@103: // use shared RelativeDirectory objects for parent dirs jjg@103: tempDirectory = getRelativeDirectory(tempDirectory.dirname().getPath()); jjg@103: } duke@1: } duke@1: } duke@1: else { duke@1: if (directories.get(directory) == null) { duke@1: directories.put(directory, new DirectoryEntry(directory, zipFileIndex)); duke@1: } duke@1: } duke@1: duke@1: // For each dir create also a file duke@1: if (fileStart != fileEnd) { jjg@57: Entry entry = new Entry(directory, duke@1: new String(zipDir, fileStart, fileEnd - fileStart, "UTF-8")); duke@1: duke@1: entry.setNativeTime(get4ByteLittleEndian(zipDir, pos + 12)); duke@1: entry.compressedSize = get4ByteLittleEndian(zipDir, pos + 20); duke@1: entry.size = get4ByteLittleEndian(zipDir, pos + 24); duke@1: entry.offset = get4ByteLittleEndian(zipDir, pos + 42); duke@1: entryList.add(entry); duke@1: } duke@1: duke@1: return pos + 46 + duke@1: get2ByteLittleEndian(zipDir, pos + 28) + duke@1: get2ByteLittleEndian(zipDir, pos + 30) + duke@1: get2ByteLittleEndian(zipDir, pos + 32); duke@1: } duke@1: } duke@1: duke@1: /** duke@1: * Returns the last modified timestamp of a zip file. duke@1: * @return long duke@1: */ duke@1: public long getZipFileLastModified() throws IOException { jjg@839: synchronized (this) { duke@1: checkIndex(); duke@1: return zipFileLastModified; duke@1: } duke@1: } duke@1: duke@1: /** ------------------------------------------------------------------------ duke@1: * DirectoryEntry class duke@1: * -------------------------------------------------------------------------*/ jjg@57: duke@1: static class DirectoryEntry { duke@1: private boolean filesInited; duke@1: private boolean directoriesInited; duke@1: private boolean zipFileEntriesInited; duke@1: private boolean entriesInited; duke@1: duke@1: private long writtenOffsetOffset = 0; duke@1: jjg@103: private RelativeDirectory dirName; duke@1: duke@1: private com.sun.tools.javac.util.List zipFileEntriesFiles = com.sun.tools.javac.util.List.nil(); duke@1: private com.sun.tools.javac.util.List zipFileEntriesDirectories = com.sun.tools.javac.util.List.nil(); jjg@57: private com.sun.tools.javac.util.List zipFileEntries = com.sun.tools.javac.util.List.nil(); duke@1: jjg@57: private List entries = new ArrayList(); duke@1: duke@1: private ZipFileIndex zipFileIndex; duke@1: duke@1: private int numEntries; duke@1: jjg@103: DirectoryEntry(RelativeDirectory dirName, ZipFileIndex index) { jjg@103: filesInited = false; duke@1: directoriesInited = false; duke@1: entriesInited = false; duke@1: jjg@103: this.dirName = dirName; duke@1: this.zipFileIndex = index; duke@1: } duke@1: duke@1: private com.sun.tools.javac.util.List getFiles() { jjg@103: if (!filesInited) { jjg@103: initEntries(); jjg@103: for (Entry e : entries) { jjg@103: if (!e.isDir) { jjg@103: zipFileEntriesFiles = zipFileEntriesFiles.append(e.name); jjg@103: } jjg@103: } jjg@103: filesInited = true; duke@1: } duke@1: return zipFileEntriesFiles; duke@1: } duke@1: duke@1: private com.sun.tools.javac.util.List getDirectories() { jjg@103: if (!directoriesInited) { jjg@103: initEntries(); jjg@103: for (Entry e : entries) { jjg@103: if (e.isDir) { jjg@103: zipFileEntriesDirectories = zipFileEntriesDirectories.append(e.name); jjg@103: } jjg@103: } jjg@103: directoriesInited = true; duke@1: } duke@1: return zipFileEntriesDirectories; duke@1: } duke@1: jjg@57: private com.sun.tools.javac.util.List getEntries() { jjg@103: if (!zipFileEntriesInited) { jjg@103: initEntries(); jjg@103: zipFileEntries = com.sun.tools.javac.util.List.nil(); jjg@103: for (Entry zfie : entries) { jjg@103: zipFileEntries = zipFileEntries.append(zfie); jjg@103: } jjg@103: zipFileEntriesInited = true; duke@1: } duke@1: return zipFileEntries; duke@1: } duke@1: jjg@57: private Entry getEntry(String rootName) { duke@1: initEntries(); jjg@57: int index = Collections.binarySearch(entries, new Entry(dirName, rootName)); duke@1: if (index < 0) { duke@1: return null; duke@1: } duke@1: duke@1: return entries.get(index); duke@1: } duke@1: duke@1: private void initEntries() { duke@1: if (entriesInited) { duke@1: return; duke@1: } duke@1: duke@1: if (!zipFileIndex.readFromIndex) { duke@1: int from = -Arrays.binarySearch(zipFileIndex.entries, jjg@57: new Entry(dirName, ZipFileIndex.MIN_CHAR)) - 1; duke@1: int to = -Arrays.binarySearch(zipFileIndex.entries, jjg@57: new Entry(dirName, MAX_CHAR)) - 1; duke@1: duke@1: for (int i = from; i < to; i++) { duke@1: entries.add(zipFileIndex.entries[i]); duke@1: } duke@1: } else { duke@1: File indexFile = zipFileIndex.getIndexFile(); duke@1: if (indexFile != null) { duke@1: RandomAccessFile raf = null; duke@1: try { duke@1: raf = new RandomAccessFile(indexFile, "r"); duke@1: raf.seek(writtenOffsetOffset); duke@1: duke@1: for (int nFiles = 0; nFiles < numEntries; nFiles++) { duke@1: // Read the name bytes duke@1: int zfieNameBytesLen = raf.readInt(); duke@1: byte [] zfieNameBytes = new byte[zfieNameBytesLen]; duke@1: raf.read(zfieNameBytes); duke@1: String eName = new String(zfieNameBytes, "UTF-8"); duke@1: duke@1: // Read isDir duke@1: boolean eIsDir = raf.readByte() == (byte)0 ? false : true; duke@1: duke@1: // Read offset of bytes in the real Jar/Zip file duke@1: int eOffset = raf.readInt(); duke@1: duke@1: // Read size of the file in the real Jar/Zip file duke@1: int eSize = raf.readInt(); duke@1: duke@1: // Read compressed size of the file in the real Jar/Zip file duke@1: int eCsize = raf.readInt(); duke@1: duke@1: // Read java time stamp of the file in the real Jar/Zip file duke@1: long eJavaTimestamp = raf.readLong(); duke@1: jjg@57: Entry rfie = new Entry(dirName, eName); duke@1: rfie.isDir = eIsDir; duke@1: rfie.offset = eOffset; duke@1: rfie.size = eSize; duke@1: rfie.compressedSize = eCsize; duke@1: rfie.javatime = eJavaTimestamp; duke@1: entries.add(rfie); duke@1: } duke@1: } catch (Throwable t) { duke@1: // Do nothing duke@1: } finally { duke@1: try { jjg@444: if (raf != null) { duke@1: raf.close(); duke@1: } duke@1: } catch (Throwable t) { duke@1: // Do nothing duke@1: } duke@1: } duke@1: } duke@1: } duke@1: duke@1: entriesInited = true; duke@1: } duke@1: jjg@57: List getEntriesAsCollection() { duke@1: initEntries(); duke@1: duke@1: return entries; duke@1: } duke@1: } duke@1: duke@1: private boolean readIndex() { duke@1: if (triedToReadIndex || !usePreindexedCache) { duke@1: return false; duke@1: } duke@1: duke@1: boolean ret = false; jjg@839: synchronized (this) { duke@1: triedToReadIndex = true; duke@1: RandomAccessFile raf = null; duke@1: try { duke@1: File indexFileName = getIndexFile(); duke@1: raf = new RandomAccessFile(indexFileName, "r"); duke@1: duke@1: long fileStamp = raf.readLong(); duke@1: if (zipFile.lastModified() != fileStamp) { duke@1: ret = false; duke@1: } else { mcimadamore@1279: directories = new LinkedHashMap(); duke@1: int numDirs = raf.readInt(); duke@1: for (int nDirs = 0; nDirs < numDirs; nDirs++) { duke@1: int dirNameBytesLen = raf.readInt(); duke@1: byte [] dirNameBytes = new byte[dirNameBytesLen]; duke@1: raf.read(dirNameBytes); duke@1: jjg@103: RelativeDirectory dirNameStr = getRelativeDirectory(new String(dirNameBytes, "UTF-8")); duke@1: DirectoryEntry de = new DirectoryEntry(dirNameStr, this); duke@1: de.numEntries = raf.readInt(); duke@1: de.writtenOffsetOffset = raf.readLong(); duke@1: directories.put(dirNameStr, de); duke@1: } duke@1: ret = true; duke@1: zipFileLastModified = fileStamp; duke@1: } duke@1: } catch (Throwable t) { duke@1: // Do nothing duke@1: } finally { duke@1: if (raf != null) { duke@1: try { duke@1: raf.close(); duke@1: } catch (Throwable tt) { duke@1: // Do nothing duke@1: } duke@1: } duke@1: } duke@1: if (ret == true) { duke@1: readFromIndex = true; duke@1: } duke@1: } duke@1: duke@1: return ret; duke@1: } duke@1: duke@1: private boolean writeIndex() { duke@1: boolean ret = false; duke@1: if (readFromIndex || !usePreindexedCache) { duke@1: return true; duke@1: } duke@1: duke@1: if (!writeIndex) { duke@1: return true; duke@1: } duke@1: duke@1: File indexFile = getIndexFile(); duke@1: if (indexFile == null) { duke@1: return false; duke@1: } duke@1: duke@1: RandomAccessFile raf = null; duke@1: long writtenSoFar = 0; duke@1: try { duke@1: raf = new RandomAccessFile(indexFile, "rw"); duke@1: duke@1: raf.writeLong(zipFileLastModified); duke@1: writtenSoFar += 8; duke@1: duke@1: List directoriesToWrite = new ArrayList(); jjg@103: Map offsets = new HashMap(); duke@1: raf.writeInt(directories.keySet().size()); duke@1: writtenSoFar += 4; duke@1: jjg@103: for (RelativeDirectory dirName: directories.keySet()) { duke@1: DirectoryEntry dirEntry = directories.get(dirName); duke@1: duke@1: directoriesToWrite.add(dirEntry); duke@1: duke@1: // Write the dir name bytes jjg@103: byte [] dirNameBytes = dirName.getPath().getBytes("UTF-8"); duke@1: int dirNameBytesLen = dirNameBytes.length; duke@1: raf.writeInt(dirNameBytesLen); duke@1: writtenSoFar += 4; duke@1: duke@1: raf.write(dirNameBytes); duke@1: writtenSoFar += dirNameBytesLen; duke@1: duke@1: // Write the number of files in the dir mcimadamore@184: List dirEntries = dirEntry.getEntriesAsCollection(); duke@1: raf.writeInt(dirEntries.size()); duke@1: writtenSoFar += 4; duke@1: duke@1: offsets.put(dirName, new Long(writtenSoFar)); duke@1: duke@1: // Write the offset of the file's data in the dir duke@1: dirEntry.writtenOffsetOffset = 0L; duke@1: raf.writeLong(0L); duke@1: writtenSoFar += 8; duke@1: } duke@1: duke@1: for (DirectoryEntry de : directoriesToWrite) { duke@1: // Fix up the offset in the directory table duke@1: long currFP = raf.getFilePointer(); duke@1: duke@1: long offsetOffset = offsets.get(de.dirName).longValue(); duke@1: raf.seek(offsetOffset); duke@1: raf.writeLong(writtenSoFar); duke@1: duke@1: raf.seek(currFP); duke@1: duke@1: // Now write each of the files in the DirectoryEntry jjg@839: List list = de.getEntriesAsCollection(); jjg@839: for (Entry zfie : list) { duke@1: // Write the name bytes duke@1: byte [] zfieNameBytes = zfie.name.getBytes("UTF-8"); duke@1: int zfieNameBytesLen = zfieNameBytes.length; duke@1: raf.writeInt(zfieNameBytesLen); duke@1: writtenSoFar += 4; duke@1: raf.write(zfieNameBytes); duke@1: writtenSoFar += zfieNameBytesLen; duke@1: duke@1: // Write isDir duke@1: raf.writeByte(zfie.isDir ? (byte)1 : (byte)0); duke@1: writtenSoFar += 1; duke@1: duke@1: // Write offset of bytes in the real Jar/Zip file duke@1: raf.writeInt(zfie.offset); duke@1: writtenSoFar += 4; duke@1: duke@1: // Write size of the file in the real Jar/Zip file duke@1: raf.writeInt(zfie.size); duke@1: writtenSoFar += 4; duke@1: duke@1: // Write compressed size of the file in the real Jar/Zip file duke@1: raf.writeInt(zfie.compressedSize); duke@1: writtenSoFar += 4; duke@1: duke@1: // Write java time stamp of the file in the real Jar/Zip file duke@1: raf.writeLong(zfie.getLastModified()); duke@1: writtenSoFar += 8; duke@1: } duke@1: } duke@1: } catch (Throwable t) { duke@1: // Do nothing duke@1: } finally { duke@1: try { duke@1: if (raf != null) { duke@1: raf.close(); duke@1: } duke@1: } catch(IOException ioe) { duke@1: // Do nothing duke@1: } duke@1: } duke@1: duke@1: return ret; duke@1: } duke@1: duke@1: public boolean writeZipIndex() { jjg@839: synchronized (this) { duke@1: return writeIndex(); duke@1: } duke@1: } duke@1: duke@1: private File getIndexFile() { duke@1: if (zipIndexFile == null) { duke@1: if (zipFile == null) { duke@1: return null; duke@1: } duke@1: duke@1: zipIndexFile = new File((preindexedCacheLocation == null ? "" : preindexedCacheLocation) + duke@1: zipFile.getName() + ".index"); duke@1: } duke@1: duke@1: return zipIndexFile; duke@1: } duke@1: duke@1: public File getZipFile() { duke@1: return zipFile; duke@1: } jjg@57: jjg@424: File getAbsoluteFile() { jjg@424: File absFile = (absFileRef == null ? null : absFileRef.get()); jjg@424: if (absFile == null) { jjg@424: absFile = zipFile.getAbsoluteFile(); jjg@424: absFileRef = new SoftReference(absFile); jjg@424: } jjg@424: return absFile; jjg@424: } jjg@424: jjg@103: private RelativeDirectory getRelativeDirectory(String path) { jjg@103: RelativeDirectory rd; jjg@103: SoftReference ref = relativeDirectoryCache.get(path); jjg@103: if (ref != null) { jjg@103: rd = ref.get(); jjg@103: if (rd != null) jjg@103: return rd; jjg@103: } jjg@103: rd = new RelativeDirectory(path); jjg@103: relativeDirectoryCache.put(path, new SoftReference(rd)); jjg@103: return rd; jjg@103: } jjg@57: jjg@57: static class Entry implements Comparable { jjg@57: public static final Entry[] EMPTY_ARRAY = {}; jjg@57: jjg@57: // Directory related jjg@103: RelativeDirectory dir; jjg@57: boolean isDir; jjg@57: jjg@57: // File related jjg@57: String name; jjg@57: jjg@57: int offset; jjg@57: int size; jjg@57: int compressedSize; jjg@57: long javatime; jjg@57: jjg@57: private int nativetime; jjg@57: jjg@103: public Entry(RelativePath path) { jjg@103: this(path.dirname(), path.basename()); jjg@57: } jjg@57: jjg@103: public Entry(RelativeDirectory directory, String name) { jjg@103: this.dir = directory; jjg@57: this.name = name; jjg@57: } jjg@57: jjg@57: public String getName() { jjg@103: return new RelativeFile(dir, name).getPath(); jjg@57: } jjg@57: jjg@57: public String getFileName() { jjg@57: return name; jjg@57: } jjg@57: jjg@57: public long getLastModified() { jjg@57: if (javatime == 0) { jjg@57: javatime = dosToJavaTime(nativetime); jjg@57: } jjg@57: return javatime; jjg@57: } jjg@57: jjg@71: // based on dosToJavaTime in java.util.Zip, but avoiding the jjg@71: // use of deprecated Date constructor jjg@71: private static long dosToJavaTime(int dtime) { jjg@71: Calendar c = Calendar.getInstance(); jjg@71: c.set(Calendar.YEAR, ((dtime >> 25) & 0x7f) + 1980); jjg@71: c.set(Calendar.MONTH, ((dtime >> 21) & 0x0f) - 1); jjg@71: c.set(Calendar.DATE, ((dtime >> 16) & 0x1f)); jjg@71: c.set(Calendar.HOUR_OF_DAY, ((dtime >> 11) & 0x1f)); jjg@71: c.set(Calendar.MINUTE, ((dtime >> 5) & 0x3f)); jjg@71: c.set(Calendar.SECOND, ((dtime << 1) & 0x3e)); jjg@71: c.set(Calendar.MILLISECOND, 0); jjg@71: return c.getTimeInMillis(); jjg@57: } jjg@57: jjg@57: void setNativeTime(int natTime) { jjg@57: nativetime = natTime; jjg@57: } jjg@57: jjg@57: public boolean isDirectory() { jjg@57: return isDir; jjg@57: } jjg@57: jjg@57: public int compareTo(Entry other) { jjg@103: RelativeDirectory otherD = other.dir; jjg@57: if (dir != otherD) { jjg@57: int c = dir.compareTo(otherD); jjg@57: if (c != 0) jjg@57: return c; jjg@57: } jjg@57: return name.compareTo(other.name); jjg@57: } jjg@57: jjg@103: @Override jjg@103: public boolean equals(Object o) { jjg@103: if (!(o instanceof Entry)) jjg@103: return false; jjg@103: Entry other = (Entry) o; jjg@103: return dir.equals(other.dir) && name.equals(other.name); jjg@103: } jjg@103: jjg@103: @Override jjg@103: public int hashCode() { jjg@103: int hash = 7; jjg@103: hash = 97 * hash + (this.dir != null ? this.dir.hashCode() : 0); jjg@103: hash = 97 * hash + (this.name != null ? this.name.hashCode() : 0); jjg@103: return hash; jjg@103: } jjg@103: jjg@839: @Override jjg@57: public String toString() { jjg@57: return isDir ? ("Dir:" + dir + " : " + name) : jjg@57: (dir + ":" + name); jjg@57: } jjg@57: } jjg@57: ksrini@923: /* ksrini@923: * Exception primarily used to implement a failover, used exclusively here. ksrini@923: */ ksrini@923: ksrini@923: static final class ZipFormatException extends IOException { ksrini@923: private static final long serialVersionUID = 8000196834066748623L; ksrini@923: protected ZipFormatException(String message) { ksrini@923: super(message); ksrini@923: } ksrini@923: ksrini@923: protected ZipFormatException(String message, Throwable cause) { ksrini@923: super(message, cause); ksrini@923: } ksrini@923: } duke@1: }