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

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