jjg@36: /* jjg@36: * Copyright 2007-2008 Sun Microsystems, Inc. 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 jjg@36: * published by the Free Software Foundation. Sun designates this jjg@36: * particular file as subject to the "Classpath" exception as provided jjg@36: * by Sun 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: * jjg@36: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, jjg@36: * CA 95054 USA or visit www.sun.com if you need additional information or jjg@36: * have any 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; jjg@50: import java.util.HashMap; jjg@50: import java.util.HashSet; jjg@50: import java.util.Iterator; duke@1: import java.util.List; jjg@50: import java.util.Map; jjg@50: import java.util.Set; duke@1: import java.util.concurrent.locks.ReentrantLock; 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: duke@1: /** This class implements building of index of a zip archive and access to it's context. duke@1: * It also uses prebuild index if available. It supports invocations where it will duke@1: * serialize an optimized zip index file to disk. duke@1: * duke@1: * In oreder to use secondary index file make sure the option "usezipindex" is in the Options object, duke@1: * when JavacFileManager is invoked. (You can pass "-XDusezipindex" on the command line. duke@1: * duke@1: * Location where to look for/generate optimized zip index files can be provided using duke@1: * "-XDcachezipindexdir=". If this flag is not provided, the dfault location is duke@1: * the value of the "java.io.tmpdir" system property. duke@1: * duke@1: * If key "-XDwritezipindexfiles" is specified, there will be new optimized index file duke@1: * created for each archive, used by the compiler for compilation, at location, duke@1: * specified by "cachezipindexdir" option. duke@1: * duke@1: * If nonBatchMode option is specified (-XDnonBatchMode) the compiler will use timestamp duke@1: * checking to reindex the zip files if it is needed. In batch mode the timestamps are not checked duke@1: * and the compiler uses the cached indexes. jjg@333: * jjg@333: *

This is NOT part of any API supported by Sun Microsystems. 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: private static Map zipFileIndexCache = new HashMap(); duke@1: private static ReentrantLock lock = new ReentrantLock(); duke@1: duke@1: private static boolean NON_BATCH_MODE = System.getProperty("nonBatchMode") != null;// TODO: Use -XD compiler switch for this. duke@1: jjg@103: private Map directories = Collections.emptyMap(); jjg@103: private Set allDirs = Collections.emptySet(); duke@1: duke@1: // ZipFileIndex data entries duke@1: private File zipFile; jjg@424: private Reference absFileRef; duke@1: private 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; duke@1: private int symbolFilePrefixLength = 0; duke@1: private boolean hasPopulatedData = false; duke@1: private long lastReferenceTimeStamp = NOT_MODIFIED; duke@1: duke@1: private boolean usePreindexedCache = false; duke@1: private String preindexedCacheLocation = null; duke@1: duke@1: private boolean writeIndex = false; duke@1: jjg@103: private Map > relativeDirectoryCache = jjg@103: new HashMap>(); jjg@103: duke@1: /** duke@1: * Returns a list of all ZipFileIndex entries duke@1: * duke@1: * @return A list of ZipFileIndex entries, or an empty list duke@1: */ duke@1: public static List getZipFileIndexes() { duke@1: return getZipFileIndexes(false); duke@1: } duke@1: duke@1: /** duke@1: * Returns a list of all ZipFileIndex entries duke@1: * duke@1: * @param openedOnly If true it returns a list of only opened ZipFileIndex entries, otherwise duke@1: * all ZipFileEntry(s) are included into the list. duke@1: * @return A list of ZipFileIndex entries, or an empty list duke@1: */ duke@1: public static List getZipFileIndexes(boolean openedOnly) { duke@1: List zipFileIndexes = new ArrayList(); duke@1: lock.lock(); duke@1: try { duke@1: zipFileIndexes.addAll(zipFileIndexCache.values()); duke@1: duke@1: if (openedOnly) { duke@1: for(ZipFileIndex elem : zipFileIndexes) { duke@1: if (!elem.isOpen()) { duke@1: zipFileIndexes.remove(elem); duke@1: } duke@1: } duke@1: } duke@1: } duke@1: finally { duke@1: lock.unlock(); duke@1: } duke@1: return zipFileIndexes; duke@1: } duke@1: duke@1: public boolean isOpen() { duke@1: lock.lock(); duke@1: try { duke@1: return zipRandomFile != null; duke@1: } duke@1: finally { duke@1: lock.unlock(); duke@1: } duke@1: } duke@1: jjg@103: public static ZipFileIndex getZipFileIndex(File zipFile, jjg@103: RelativeDirectory symbolFilePrefix, jjg@103: boolean useCache, String cacheLocation, jjg@103: boolean writeIndex) throws IOException { duke@1: ZipFileIndex zi = null; duke@1: lock.lock(); duke@1: try { duke@1: zi = getExistingZipIndex(zipFile); duke@1: duke@1: if (zi == null || (zi != null && zipFile.lastModified() != zi.zipFileLastModified)) { jjg@57: zi = new ZipFileIndex(zipFile, symbolFilePrefix, writeIndex, duke@1: useCache, cacheLocation); duke@1: zipFileIndexCache.put(zipFile, zi); duke@1: } duke@1: } duke@1: finally { duke@1: lock.unlock(); duke@1: } duke@1: return zi; duke@1: } duke@1: duke@1: public static ZipFileIndex getExistingZipIndex(File zipFile) { duke@1: lock.lock(); duke@1: try { duke@1: return zipFileIndexCache.get(zipFile); duke@1: } duke@1: finally { duke@1: lock.unlock(); duke@1: } duke@1: } duke@1: duke@1: public static void clearCache() { duke@1: lock.lock(); duke@1: try { duke@1: zipFileIndexCache.clear(); duke@1: } duke@1: finally { duke@1: lock.unlock(); duke@1: } duke@1: } duke@1: duke@1: public static void clearCache(long timeNotUsed) { duke@1: lock.lock(); duke@1: try { duke@1: Iterator cachedFileIterator = zipFileIndexCache.keySet().iterator(); duke@1: while (cachedFileIterator.hasNext()) { duke@1: File cachedFile = cachedFileIterator.next(); duke@1: ZipFileIndex cachedZipIndex = zipFileIndexCache.get(cachedFile); duke@1: if (cachedZipIndex != null) { duke@1: long timeToTest = cachedZipIndex.lastReferenceTimeStamp + timeNotUsed; duke@1: if (timeToTest < cachedZipIndex.lastReferenceTimeStamp || // Overflow... duke@1: System.currentTimeMillis() > timeToTest) { duke@1: zipFileIndexCache.remove(cachedFile); duke@1: } duke@1: } duke@1: } duke@1: } duke@1: finally { duke@1: lock.unlock(); duke@1: } duke@1: } duke@1: duke@1: public static void removeFromCache(File file) { duke@1: lock.lock(); duke@1: try { duke@1: zipFileIndexCache.remove(file); duke@1: } duke@1: finally { duke@1: lock.unlock(); duke@1: } duke@1: } duke@1: duke@1: /** Sets already opened list of ZipFileIndexes from an outside client duke@1: * of the compiler. This functionality should be used in a non-batch clients of the compiler. duke@1: */ duke@1: public static void setOpenedIndexes(Listindexes) throws IllegalStateException { duke@1: lock.lock(); duke@1: try { duke@1: if (zipFileIndexCache.isEmpty()) { duke@1: throw new IllegalStateException("Setting opened indexes should be called only when the ZipFileCache is empty. Call JavacFileManager.flush() before calling this method."); duke@1: } duke@1: duke@1: for (ZipFileIndex zfi : indexes) { duke@1: zipFileIndexCache.put(zfi.zipFile, zfi); duke@1: } duke@1: } duke@1: finally { duke@1: lock.unlock(); duke@1: } duke@1: } duke@1: jjg@103: private 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: duke@1: public String toString() { jjg@103: return "ZipFileIndex[" + zipFile + "]"; duke@1: } duke@1: duke@1: // Just in case... duke@1: protected void finalize() { duke@1: closeFile(); duke@1: } duke@1: duke@1: private boolean isUpToDate() { duke@1: if (zipFile != null && duke@1: ((!NON_BATCH_MODE) || zipFileLastModified == zipFile.lastModified()) && duke@1: 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: duke@1: public void close() { duke@1: lock.lock(); duke@1: try { duke@1: writeIndex(); duke@1: closeFile(); duke@1: } duke@1: finally { duke@1: lock.unlock(); duke@1: } 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: /** duke@1: * Returns the ZipFileIndexEntry for an absolute path, if there is one. duke@1: */ jjg@103: Entry getZipIndexEntry(RelativePath path) { duke@1: lock.lock(); duke@1: try { duke@1: checkIndex(); jjg@103: DirectoryEntry de = directories.get(path.dirname()); jjg@103: String lookFor = path.basename(); duke@1: return de == null ? null : de.getEntry(lookFor); duke@1: } duke@1: catch (IOException e) { duke@1: return null; duke@1: } duke@1: finally { duke@1: lock.unlock(); duke@1: } duke@1: } duke@1: duke@1: /** duke@1: * Returns a javac List of filenames within an absolute path in the ZipFileIndex. duke@1: */ jjg@103: public com.sun.tools.javac.util.List getFiles(RelativeDirectory path) { duke@1: lock.lock(); 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: finally { duke@1: lock.unlock(); duke@1: } duke@1: } duke@1: jjg@103: public List getDirectories(RelativeDirectory path) { duke@1: lock.lock(); 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: finally { duke@1: lock.unlock(); duke@1: } duke@1: } duke@1: jjg@103: public Set getAllDirectories() { duke@1: lock.lock(); duke@1: try { duke@1: checkIndex(); duke@1: if (allDirs == Collections.EMPTY_SET) { jjg@103: allDirs = new HashSet(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: finally { duke@1: lock.unlock(); 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@103: public boolean contains(RelativePath path) { duke@1: lock.lock(); 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: } duke@1: finally { duke@1: lock.unlock(); duke@1: } duke@1: } duke@1: jjg@103: public boolean isDirectory(RelativePath path) throws IOException { duke@1: lock.lock(); duke@1: try { duke@1: // The top level in a zip file is always a directory. jjg@103: if (path.getPath().length() == 0) { duke@1: lastReferenceTimeStamp = System.currentTimeMillis(); duke@1: return true; duke@1: } duke@1: duke@1: checkIndex(); duke@1: return directories.get(path) != null; duke@1: } duke@1: finally { duke@1: lock.unlock(); duke@1: } duke@1: } duke@1: jjg@103: public long getLastModified(RelativeFile path) throws IOException { duke@1: lock.lock(); duke@1: try { jjg@57: Entry entry = getZipIndexEntry(path); duke@1: if (entry == null) duke@1: throw new FileNotFoundException(); duke@1: return entry.getLastModified(); duke@1: } duke@1: finally { duke@1: lock.unlock(); duke@1: } duke@1: } duke@1: jjg@103: public int length(RelativeFile path) throws IOException { duke@1: lock.lock(); duke@1: try { jjg@57: Entry entry = getZipIndexEntry(path); duke@1: if (entry == null) duke@1: throw new FileNotFoundException(); duke@1: duke@1: if (entry.isDir) { duke@1: return 0; duke@1: } duke@1: duke@1: byte[] header = getHeader(entry); duke@1: // entry is not compressed? duke@1: if (get2ByteLittleEndian(header, 8) == 0) { duke@1: return entry.compressedSize; duke@1: } else { duke@1: return entry.size; duke@1: } duke@1: } duke@1: finally { duke@1: lock.unlock(); duke@1: } duke@1: } duke@1: jjg@103: public byte[] read(RelativeFile path) throws IOException { duke@1: lock.lock(); duke@1: try { jjg@57: Entry entry = getZipIndexEntry(path); duke@1: if (entry == null) jjg@103: throw new FileNotFoundException("Path not found in ZIP: " + path.path); duke@1: return read(entry); duke@1: } duke@1: finally { duke@1: lock.unlock(); duke@1: } duke@1: } duke@1: jjg@57: byte[] read(Entry entry) throws IOException { duke@1: lock.lock(); duke@1: try { duke@1: openFile(); duke@1: byte[] result = readBytes(entry); duke@1: closeFile(); duke@1: return result; duke@1: } duke@1: finally { duke@1: lock.unlock(); duke@1: } duke@1: } duke@1: jjg@103: public int read(RelativeFile path, byte[] buffer) throws IOException { duke@1: lock.lock(); duke@1: try { jjg@57: Entry entry = getZipIndexEntry(path); duke@1: if (entry == null) duke@1: throw new FileNotFoundException(); duke@1: return read(entry, buffer); duke@1: } duke@1: finally { duke@1: lock.unlock(); duke@1: } duke@1: } duke@1: jjg@57: int read(Entry entry, byte[] buffer) duke@1: throws IOException { duke@1: lock.lock(); duke@1: try { duke@1: int result = readBytes(entry, buffer); duke@1: return result; duke@1: } duke@1: finally { duke@1: lock.unlock(); duke@1: } 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: */ duke@1: private static Inflater inflater; duke@1: private int inflate(byte[] src, byte[] dest) { duke@1: duke@1: // construct the inflater object or reuse an existing one duke@1: if (inflater == null) duke@1: inflater = new Inflater(true); duke@1: duke@1: synchronized (inflater) { duke@1: inflater.reset(); duke@1: inflater.setInput(src); duke@1: try { duke@1: return inflater.inflate(dest); duke@1: } catch (DataFormatException ex) { duke@1: return -1; duke@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; duke@1: duke@1: findCENRecord(start, end); 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]; duke@1: zipRandomFile.seek(start + get4ByteLittleEndian(endbuf, i + 16)); 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) { jjg@103: directories = new HashMap(); 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 { duke@1: lock.lock(); duke@1: try { duke@1: checkIndex(); duke@1: return zipFileLastModified; duke@1: } duke@1: finally { duke@1: lock.unlock(); 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; duke@1: lock.lock(); duke@1: try { 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 { jjg@103: directories = new HashMap(); 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: finally { duke@1: lock.unlock(); 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@57: List entries = de.getEntriesAsCollection(); jjg@57: for (Entry zfie : entries) { 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() { duke@1: lock.lock(); duke@1: try { duke@1: return writeIndex(); duke@1: } duke@1: finally { duke@1: lock.unlock(); 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@57: jjg@57: public String toString() { jjg@57: return isDir ? ("Dir:" + dir + " : " + name) : jjg@57: (dir + ":" + name); jjg@57: } jjg@57: } jjg@57: duke@1: }